Browse Source

Working on Entity sorting in Views

Chun-Huai Cheng 8 years ago
parent
commit
1e44d09637

+ 38 - 2
tripal/includes/TripalFieldQuery.inc

@@ -19,7 +19,7 @@ class TripalFieldQuery {
   // An associative array of the filters to apply.
   protected $conditions = array();
   // An associative array of the sorting.
-  protected $orderBy = array();
+  protected $order = array();
 
   /**
    *
@@ -61,6 +61,36 @@ class TripalFieldQuery {
     return $this;
   }
 
+  /**
+   * Orders the result set by a given field column.
+   * 
+   * @param $field
+   * @param $column
+   * @param $direction
+   * @throws EntityFieldQueryException
+   * @return TripalFieldQuery
+   */
+  public function fieldOrderBy($field_name, $direction = 'ASC') {
+    if (is_scalar($field_name)) {
+      $field_definition = field_info_field($field_name);
+      if (empty($field_definition)) {
+        throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field_name)));
+      }
+      $field_name = $field_definition;
+    }
+    // Save the index used for the new field, for later use in field storage.
+    $index = count($this->fields);
+    $this->fields[$index] = $field_name;
+    $this->order[] = array(
+      'type' => 'field',
+      'specifier' => array(
+        'field' => $field_name,
+        'index' => $index,
+      ),
+      'direction' => $direction,
+    );
+    return $this;
+  }
 
   /**
    * Executes the query and returns results.
@@ -79,7 +109,8 @@ class TripalFieldQuery {
     // Are there any conditions?  If so, then let the field storage
     // systems handle the query. If there are no fields then just pull out
     // the list of entities.
-    //dpm($this->conditions);
+    // dpm($this->conditions);
+    // dpm($this->order);
 
     $entity_ids = array();
     if (count($this->conditions) > 0) {
@@ -113,6 +144,11 @@ class TripalFieldQuery {
         $entity_ids[] = $entity_id;
       }
     }
+    
+    // Order the entities by the field
+    if (count($this->order) > 0) {
+      
+    }
 
     // Generate the entities for the keys.
     $return = array();

+ 1 - 0
tripal/includes/tripal.field_storage.inc

@@ -14,6 +14,7 @@ files[] = views_handlers/tripal_views_handler_field_entity.inc
 files[] = views_handlers/tripal_views_handler_field_entity_status.inc
 files[] = views_handlers/tripal_views_handler_field_entity_default_formatter.inc
 files[] = views_handlers/tripal_views_handler_filter_entity_string.inc
+files[] = views_handlers/tripal_views_handler_sort_entity_string.inc
 files[] = tripal_views_query.inc
 
 dependencies[] = views

+ 1 - 1
tripal/tripal.views.inc

@@ -70,7 +70,7 @@ function tripal_entity_views_data() {
       // a default string handler.
       $field_handler = 'tripal_views_handler_field_entity_default_formatter';
       $filter_handler = 'tripal_views_handler_filter_entity_string';
-      $sort_handler = 'views_handler_sort';
+      $sort_handler = 'tripal_views_handler_sort_entity_string';
       $click_sortable = TRUE;
 
       $field_name = $instance['field_name'];

+ 35 - 24
tripal/tripal_views_query.inc

@@ -35,36 +35,47 @@ class tripal_views_query extends views_plugin_query {
     // must be with the content_type field.
     $query = new TripalFieldQuery();
     $query->fieldCondition('content_type', $bundle->id);
+    // Apply filters
     foreach ($view->filter as $field_name => $handler) {
-      $query->fieldCondition($field_name, $handler->value, $handler->operator);
+      if (trim($handler->value)) {
+        $query->fieldCondition($field_name, $handler->value, $handler->operator);
+      }
+    }
+    // Apply sorting
+    foreach ($view->sort as $field_name => $sort) {
+      $options = $sort->options;
+      $query->fieldOrderBy($field_name, $options['order']);
     }
 
     $results = $query->execute();
-    foreach ($results['TripalEntity'] as $entity_id => $stub) {
-      // Begin a new row for Views output.
-      $row = new stdClass;
-
-      // Get the entity object.
-      $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
-      $entity = reset($entity);
 
-      // Iterate through the fields that are added to the view and attach those
-      // to the entity.  After attaching we can get the value and include
-      // it in the output results.
-      foreach($view->field as $field_name => $handler) {
-        $field = field_info_field($field_name);
-        field_attach_load($entity->type, array($entity->id => $entity), FIELD_LOAD_CURRENT,
-            array('field_id' => $field['id']));
-        $items = field_get_items('TripalEntity', $entity, $field_name);
-        $value = $items[0]['value'];
-
-        $row->entity = $entity;
-        $row->$field_name = $value;
+    if (count($results) > 0) {
+      foreach ($results['TripalEntity'] as $entity_id => $stub) {
+        // Begin a new row for Views output.
+        $row = new stdClass;
+  
+        // Get the entity object.
+        $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
+        $entity = reset($entity);
+  
+        // Iterate through the fields that are added to the view and attach those
+        // to the entity.  After attaching we can get the value and include
+        // it in the output results.
+        foreach($view->field as $field_name => $handler) {
+          $field = field_info_field($field_name);
+          field_attach_load($entity->type, array($entity->id => $entity), FIELD_LOAD_CURRENT,
+              array('field_id' => $field['id']));
+          $items = field_get_items('TripalEntity', $entity, $field_name);
+          $value = $items[0]['value'];
+  
+          $row->entity = $entity;
+          $row->$field_name = $value;
+        }
+  
+        // Add the row to the results list.
+        $view->result[] = $row;
+  
       }
-
-      // Add the row to the results list.
-      $view->result[] = $row;
-
     }
 
     $view->total_rows = count($view->result);

+ 8 - 7
tripal/views_handlers/tripal_views_handler_filter_entity_string.inc

@@ -29,6 +29,7 @@ class tripal_views_handler_filter_entity_string extends views_handler_filter {
    * adding/removing items from this array.
    */
   function operators() {
+    // The operators keys must match the ones used in the TripalFieldQuery
     $operators = array(
       '=' => array(
         'title' => t('Is equal to'),
@@ -36,43 +37,43 @@ class tripal_views_handler_filter_entity_string extends views_handler_filter {
         'method' => 'op_equal',
         'values' => 1,
       ),
-      '!=' => array(
+      '<>' => array(
         'title' => t('Is not equal to'),
         'short' => t('!='),
         'method' => 'op_equal',
         'values' => 1,
       ),
-      'contains' => array(
+      'CONTAINS' => array(
         'title' => t('Contains'),
         'short' => t('contains'),
         'method' => 'op_contains',
         'values' => 1,
       ),
-      'starts' => array(
+      'STARTS WITH' => array(
         'title' => t('Starts with'),
         'short' => t('begins'),
         'method' => 'op_starts',
         'values' => 1,
       ),
-      'not_starts' => array(
+      'NOT STARTS' => array(
         'title' => t('Does not start with'),
         'short' => t('not_begins'),
         'method' => 'op_not_starts',
         'values' => 1,
       ),
-      'ends' => array(
+      'ENDS WITH' => array(
         'title' => t('Ends with'),
         'short' => t('ends'),
         'method' => 'op_ends',
         'values' => 1,
       ),
-      'not_ends' => array(
+      'NOT ENDS' => array(
         'title' => t('Does not end with'),
         'short' => t('not_ends'),
         'method' => 'op_not_ends',
         'values' => 1,
       ),
-      'not' => array(
+      'NOT' => array(
         'title' => t('Does not contain'),
         'short' => t('!has'),
         'method' => 'op_not',

+ 237 - 0
tripal/views_handlers/tripal_views_handler_sort_entity_string.inc

@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * @file
+ * @todo.
+ */
+
+/**
+ * @defgroup views_sort_handlers Views sort handlers
+ * @{
+ * Handlers to tell Views how to sort queries.
+ */
+
+/**
+ * Base sort handler that has no options and performs a simple sort.
+ *
+ * @ingroup views_sort_handlers
+ */
+class tripal_views_handler_sort_entity_string extends views_handler {
+
+  /**
+   * Determine if a sort can be exposed.
+   */
+  function can_expose() { return TRUE; }
+
+  /**
+   * Called to add the sort to a query.
+   */
+  function query() {
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['order'] = array('default' => 'ASC');
+    $options['exposed'] = array('default' => FALSE, 'bool' => TRUE);
+    $options['expose'] = array(
+      'contains' => array(
+        'label' => array('default' => '', 'translatable' => TRUE),
+      ),
+    );
+    return $options;
+  }
+
+  /**
+   * Display whether or not the sort order is ascending or descending
+   */
+  function admin_summary() {
+    if (!empty($this->options['exposed'])) {
+      return t('Exposed');
+    }
+    switch ($this->options['order']) {
+      case 'ASC':
+      case 'asc':
+      default:
+        return t('asc');
+        break;
+      case 'DESC';
+      case 'desc';
+        return t('desc');
+        break;
+    }
+  }
+
+  /**
+   * Basic options for all sort criteria
+   */
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    if ($this->can_expose()) {
+      $this->show_expose_button($form, $form_state);
+    }
+    $form['op_val_start'] = array('#value' => '<div class="clearfix">');
+    $this->show_sort_form($form, $form_state);
+    $form['op_val_end'] = array('#value' => '</div>');
+    if ($this->can_expose()) {
+      $this->show_expose_form($form, $form_state);
+    }
+  }
+
+  /**
+   * Shortcut to display the expose/hide button.
+   */
+  function show_expose_button(&$form, &$form_state) {
+    $form['expose_button'] = array(
+      '#prefix' => '<div class="views-expose clearfix">',
+      '#suffix' => '</div>',
+      // Should always come first
+      '#weight' => -1000,
+    );
+
+    // Add a checkbox for JS users, which will have behavior attached to it
+    // so it can replace the button.
+    $form['expose_button']['checkbox'] = array(
+      '#theme_wrappers' => array('container'),
+      '#attributes' => array('class' => array('js-only')),
+    );
+    $form['expose_button']['checkbox']['checkbox'] = array(
+      '#title' => t('Expose this sort to visitors, to allow them to change it'),
+      '#type' => 'checkbox',
+    );
+
+    // Then add the button itself.
+    if (empty($this->options['exposed'])) {
+      $form['expose_button']['markup'] = array(
+        '#markup' => '<div class="description exposed-description" style="float: left; margin-right:10px">' . t('This sort is not exposed. Expose it to allow the users to change it.') . '</div>',
+      );
+      $form['expose_button']['button'] = array(
+        '#limit_validation_errors' => array(),
+        '#type' => 'submit',
+        '#value' => t('Expose sort'),
+        '#submit' => array('views_ui_config_item_form_expose'),
+      );
+      $form['expose_button']['checkbox']['checkbox']['#default_value'] = 0;
+    }
+    else {
+      $form['expose_button']['markup'] = array(
+        '#markup' => '<div class="description exposed-description">' . t('This sort is exposed. If you hide it, users will not be able to change it.') . '</div>',
+      );
+      $form['expose_button']['button'] = array(
+        '#limit_validation_errors' => array(),
+        '#type' => 'submit',
+        '#value' => t('Hide sort'),
+        '#submit' => array('views_ui_config_item_form_expose'),
+      );
+      $form['expose_button']['checkbox']['checkbox']['#default_value'] = 1;
+    }
+  }
+
+  /**
+   * Simple validate handler
+   */
+  function options_validate(&$form, &$form_state) {
+    $this->sort_validate($form, $form_state);
+    if (!empty($this->options['exposed'])) {
+      $this->expose_validate($form, $form_state);
+    }
+
+  }
+
+  /**
+   * Simple submit handler
+   */
+  function options_submit(&$form, &$form_state) {
+    unset($form_state['values']['expose_button']); // don't store this.
+    $this->sort_submit($form, $form_state);
+    if (!empty($this->options['exposed'])) {
+      $this->expose_submit($form, $form_state);
+    }
+  }
+
+  /**
+   * Shortcut to display the value form.
+   */
+  function show_sort_form(&$form, &$form_state) {
+    $options = $this->sort_options();
+    if (!empty($options)) {
+      $form['order'] = array(
+        '#type' => 'radios',
+        '#options' => $options,
+        '#default_value' => $this->options['order'],
+      );
+    }
+  }
+
+  function sort_validate(&$form, &$form_state) { }
+
+  function sort_submit(&$form, &$form_state) { }
+
+  /**
+   * Provide a list of options for the default sort form.
+   * Should be overridden by classes that don't override sort_form
+   */
+  function sort_options() {
+    return array(
+      'ASC' => t('Sort ascending'),
+      'DESC' => t('Sort descending'),
+    );
+  }
+
+  function expose_form(&$form, &$form_state) {
+    // #flatten will move everything from $form['expose'][$key] to $form[$key]
+    // prior to rendering. That's why the pre_render for it needs to run first,
+    // so that when the next pre_render (the one for fieldsets) runs, it gets
+    // the flattened data.
+    array_unshift($form['#pre_render'], 'views_ui_pre_render_flatten_data');
+    $form['expose']['#flatten'] = TRUE;
+
+    $form['expose']['label'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $this->options['expose']['label'],
+      '#title' => t('Label'),
+      '#required' => TRUE,
+      '#size' => 40,
+      '#weight' => -1,
+   );
+  }
+
+  /**
+   * Provide default options for exposed sorts.
+   */
+  function expose_options() {
+    $this->options['expose'] = array(
+      'order' => $this->options['order'],
+      'label' => $this->definition['title'],
+    );
+  }
+}
+
+/**
+ * A special handler to take the place of missing or broken handlers.
+ *
+ * @ingroup views_sort_handlers
+ */
+class tripal_views_handler_sort_entity_string_broken extends  tripal_views_handler_sort_entity_string {
+  function ui_name($short = FALSE) {
+    return t('Broken/missing handler');
+  }
+
+  function ensure_my_table() { /* No table to ensure! */ }
+  function query($group_by = FALSE) { /* No query to run */ }
+  function options_form(&$form, &$form_state) {
+    $form['markup'] = array(
+      '#markup' => '<div class="form-item description">' . t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.') . '</div>',
+    );
+  }
+
+  /**
+   * Determine if the handler is considered 'broken'
+   */
+  function broken() { return TRUE; }
+}
+
+
+/**
+ * @}
+ */

+ 4 - 4
tripal_chado/includes/fields/chado_feature__residues.inc

@@ -402,7 +402,7 @@ class chado_feature__residues extends TripalField {
    * @param unknown $featurelocs
    * @return multitype:|Ambigous <multitype:, an>
    */
-  private function get_featureloc_sequences($feature_id, $featurelocs) {
+  private static function get_featureloc_sequences($feature_id, $featurelocs) {
 
     // if we don't have any featurelocs then no point in continuing
     if (!$featurelocs) {
@@ -546,7 +546,7 @@ class chado_feature__residues extends TripalField {
    *
    * @ingroup tripal_feature
    */
-  private function get_aggregate_relationships($feature_id, $substitute=1,
+  private static function get_aggregate_relationships($feature_id, $substitute=1,
       $levels=0, $base_type_id=NULL, $depth=0) {
 
     // we only want to recurse to as many levels deep as indicated by the
@@ -571,7 +571,7 @@ class chado_feature__residues extends TripalField {
    *
    * @ingroup tripal_feature
    */
-  private function get_relationships($feature_id, $side = 'as_subject') {
+  private static function get_relationships($feature_id, $side = 'as_subject') {
     // get the relationships for this feature.  The query below is used for both
     // querying the object and subject relationships
     $sql = "
@@ -635,7 +635,7 @@ class chado_feature__residues extends TripalField {
    *
    * @ingroup tripal_feature
    */
-  private function get_featurelocs($feature_id, $side = 'as_parent', $aggregate = 1) {
+  private static function get_featurelocs($feature_id, $side = 'as_parent', $aggregate = 1) {
 
     $sql = "
     SELECT

+ 33 - 8
tripal_chado/includes/tripal_chado.field_storage.inc

@@ -448,7 +448,6 @@ function tripal_chado_field_storage_tquery($conditions) {
       $value = $condition['value'];
     }
 
-
     // Use the appropriate operator.
     $operator = $condition['operator'];
     switch ($operator) {
@@ -466,7 +465,7 @@ function tripal_chado_field_storage_tquery($conditions) {
         break;
       case '<>':
         $filters[$chado_table][$chado_column] = array(
-          'op' => 'NOT',
+          'op' => '<>',
           'data' => $value,
         );
         break;
@@ -476,12 +475,36 @@ function tripal_chado_field_storage_tquery($conditions) {
           'data' => '%' . $value . '%',
         );
         break;
+      case 'NOT':
+        $filters[$chado_table][$chado_column] = array(
+        'op' => 'NOT LIKE',
+        'data' => '%' . $value . '%',
+        );
+        break;
       case 'STARTS WITH':
         $filters[$chado_table][$chado_column] = array(
           'op' => 'LIKE',
           'data' => $value . '%',
         );
         break;
+      case 'NOT STARTS':
+        $filters[$chado_table][$chado_column] = array(
+        'op' => 'NOT LIKE',
+        'data' => $value . '%',
+        );
+        break;
+      case 'ENDS WITH':
+        $filters[$chado_table][$chado_column] = array(
+        'op' => 'LIKE',
+        'data' => '%' . $value,
+        );
+        break;
+      case 'NOT ENDS':
+        $filters[$chado_table][$chado_column] = array(
+        'op' => 'NOT LIKE',
+        'data' => '%' . $value,
+        );
+        break;
       default:
         // unrecognized operation.
         break;
@@ -505,12 +528,14 @@ function tripal_chado_field_storage_tquery($conditions) {
 
     // Next look for matching IDs in the chado_entity table.
     $filter_ids = array();
-    $results = db_select('chado_entity', 'CE')
-      ->fields('CE', array('entity_id'))
-      ->condition('record_id', $record_ids)
-      ->execute();
-    while ($result = $results->fetchObject()) {
-      $filter_ids[] = $result->entity_id;
+    if (count($record_ids) > 0) {
+      $results = db_select('chado_entity', 'CE')
+        ->fields('CE', array('entity_id'))
+        ->condition('record_id', $record_ids)
+        ->execute();
+      while ($result = $results->fetchObject()) {
+        $filter_ids[] = $result->entity_id;
+      }
     }
 
     // Take the intersection of IDs in this filter with those in the