Browse Source

Added basic filter functionality for chado fields. Specifically, textfield/string filters and drop-down/foreign key filters. Still need numeric, boolean and date.

Lacey Sanderson 9 years ago
parent
commit
6ad9206eb2

+ 298 - 0
tripal_chado/includes/chado_views_handler_filter.inc

@@ -0,0 +1,298 @@
+<?php
+/**
+ * @file
+ * Contains views filter handlers that provide chado support.
+ */
+
+/**
+ * Adds support for chado foreign key filters.
+ */
+class chado_views_handler_filter_fk extends views_handler_filter {
+
+  /**
+   * {@inheritdoc}
+   */
+  function query() {
+
+    // Adds joins to chado_entity and the chado table this field is from.
+    $alias = _chado_views_add_table_joins($this);
+
+    // Now add the restriction to the chado table as specified by user input.
+    if (sizeof($this->value) == 1) {
+      $value = array_pop($this->value);
+      $field = $alias .'.'. $this->definition['chado_field'];
+      $this->query->add_where($this->options['group'], $field, $value, '=');
+    }
+    elseif (sizeof($this->value) > 1) {
+      $field = $alias .'.'. $this->definition['chado_field'];
+      $this->query->add_where($this->options['group'], $field, $this->value, 'IN');
+    }
+    // @todo handle multiple values.
+     
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function value_form(&$form, &$form_state) {
+    parent::value_form($form, $form_state);
+
+    $this->options['values_form_type'] = (isset($this->options['values_form_type'])) ? $this->options['values_form_type'] : 'textfield';
+
+    if (preg_match('/select/', $this->options['values_form_type'])) {
+
+      //Select List
+      $form['value'] = array(
+        '#type' => 'select',
+        '#title' => t('%label', array('%label' => $this->options['expose']['label'])),
+        '#options' => $this->get_select_options(),
+        '#default_value' => $this->value,
+      );
+
+      //if ($this->options['select_multiple']) {
+        //$form['value']['#multiple'] = TRUE;
+      //}
+    }
+    else {
+
+      $form['value'] = array(
+        '#type' => 'textfield',
+        '#title' => t('%label', array('%label' => $this->options['expose']['label'])),
+        '#default_value' => $this->value,
+      );
+
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function exposed_form(&$form, &$form_state) {
+    parent::exposed_form($form, $form_state);
+
+    if (isset($this->options['select_multiple'])) {
+      if ($this->options['select_multiple']) {
+
+        if (isset($this->options['expose']['identifier'])) {
+          $id = $this->options['expose']['identifier'];
+        }
+        else {
+          $id = $this->options['id'];
+        }
+        $form[$id]['#multiple'] = TRUE;
+      }
+    }
+  }
+  
+  function get_select_options() {
+
+    $name_field = 'common_name';
+        
+    // Using a "Loose Index Scan" to get a list of all the unique values for
+    // the name in the table referenced by the foreign key constraint.
+    // See https://wiki.postgresql.org/wiki/Loose_indexscan
+    $sql = "WITH RECURSIVE t AS (
+          SELECT MIN(filter_table.!id_field) AS col
+            FROM {!filter_table} filter_table
+            LEFT JOIN {!foreign_table} foreign_table ON filter_table.!id_field=foreign_table.!id_field
+          UNION ALL
+          SELECT (
+              SELECT MIN(filter_table.!id_field)
+              FROM {!filter_table} filter_table
+              LEFT JOIN {!foreign_table} foreign_table ON filter_table.!id_field=foreign_table.!id_field
+              WHERE filter_table.!id_field > col
+            )
+            FROM t WHERE col IS NOT NULL
+        )
+        SELECT !id_field as id, !name_field as name
+          FROM {!foreign_table}
+          WHERE !id_field IN (SELECT col FROM t where col IS NOT NULL)
+          ORDER BY !name_field ASC";
+    $sql = format_string($sql, array(
+      '!filter_table' => $this->definition['chado_table'],
+      '!foreign_table' => $this->definition['foreign_key']['right_table'],
+      '!id_field' => $this->definition['chado_field'],
+      '!name_field' => $name_field
+    ));
+
+    $resource = chado_query($sql);
+    $options = array();
+
+    if ($this->options['select_optional']) {
+      $options['All'] = '- Any -';
+    }
+
+    foreach ($resource as $r) {
+      $options[$r->id] = $r->name;
+    }
+
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function has_extra_options() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function extra_options_form(&$form, &$form_state) {
+    parent::extra_options_form($form, $form_state);
+
+    $form['values_form_type'] = array(
+      '#type' => 'radios',
+      '#title' => t('Filter Type'),
+      '#options' => array(
+        'textfield' => 'Text Field',
+        'select' => 'Drop-Down Box',
+      ),
+      '#default_value' => $this->options['values_form_type'],
+    );
+
+    // @todo: implement.
+    //$form['show_all'] = array(
+      //'#type' => 'checkbox',
+      //'#title' => t('Show All'),
+      //'#description' => t('When selected all terms from the controlled vocaulbary used by the table will be shown where the default is to only show those that are used.'),
+      //'#default_value' => $this->options['show_all'],
+    //);
+ 
+    // @todo: implement.
+    //$form['select_multiple'] = array(
+      //'#type' => 'checkbox',
+      //'#title' => t('Select Multiple'),
+      //'#description' => t('Allows more then one option to be selected.'),
+      //'#default_value' => $this->options['select_multiple'],
+    //);
+
+    $form['select_optional'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Optional'),
+      '#description' => t('Adds --Any-- to the available options.'),
+      '#default_value' => $this->options['select_optional'],
+    );
+
+    $form['max_length'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Max Width'),
+      '#description' => t('Specify the maximum width of the select box'),
+      '#default_value' => $this->options['max_length'],
+    );
+
+    $form['note'] = array(
+      '#type' => 'markup',
+      '#value' => t('<strong><font color="red">Note:</font></strong> If another filter exists for the same table then ' . 'the values shown in the drop box will only include those from rows that are not filtered.'),
+    );
+
+    return $form;
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function extra_options_submit($form, &$form_state) {
+    $this->options['values_form_type'] = $form_state['values']['options']['values_form_type'];
+    $this->options['select_multiple'] = $form_state['values']['options']['select_multiple'];
+    $this->options['select_optional'] = $form_state['values']['options']['select_optional'];
+    $this->options['max_length'] = $form_state['values']['options']['max_length'];
+    $this->options['show_all'] = $form_state['values']['options']['show_all'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function extra_options_options() {
+    $this->options['values_form_type'] = 'textfield';
+    $this->options['select_multiple'] = FALSE;
+    $this->options['select_optional'] = FALSE;
+    $this->options['max_length'] = 40;
+    $this->options['show_all'] = FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['values_form_type'] = array(
+      'default' => 'textfield',
+      'export' => TRUE,
+    );
+    $options['select_multiple'] = array(
+      'default' => FALSE,
+      'bool' => TRUE,
+      'export' => TRUE,
+    );
+    $options['select_optional'] = array(
+      'default' => FALSE,
+      'bool' => TRUE,
+      'export' => TRUE,
+    );
+    $options['show_all'] = array(
+      'default' => FALSE,
+      'bool' => TRUE,
+      'export' => TRUE,
+    );
+    $options['max_length'] = array(
+      'default' => 40,
+      'export' => TRUE,
+    );
+
+    return $options;
+  }
+}
+
+/**
+ * Adds chado support to the string filter.
+ */
+class chado_views_handler_filter_string extends views_handler_filter_string {
+
+  function query() {
+    
+    // Adds joins to chado_entity and the chado table this field is from.
+    $alias = _chado_views_add_table_joins($this);
+
+    // Finally add the restriction on the chado table including the value entered by the filter.
+    // Notice that we don't call $query->add_where directly. This is so that the options
+    // specified for the comparison operator are used (ie: contains).
+    $info = $this->operators();
+    $field = $alias .'.'. $this->definition['chado_field'];
+    if (!empty($info[$this->operator]['method'])) {
+      $this->{$info[$this->operator]['method']}($field);
+    }
+  }
+}
+
+/**
+ * A helper function for adding joins in the views_handler::query() method
+ * to connect a TripalEntity with it's chado tables.
+ *
+ * @param object $views_handler
+ *   $this from within the views_handler::query() method.
+ */
+function _chado_views_add_table_joins(&$handler) {
+
+  // First we need to join to the chado_entity table where the link between an 
+  // entity and it's chado record is stored.
+  $join = new views_join();
+  $join->construct('chado_entity', $handler->table, 'id', 'chado_entity_id');
+  $alias = $handler->query->add_relationship('chado_entity', $join, $handler->table_alias, $handler->relationship);
+
+  // Restrict the chado_entity join to only return the table this field is from.
+  $handler->query->add_where(0, $alias .'.data_table', $handler->definition['chado_table'], '=');
+  
+  // Now, we need to join from chado_entity to the chado table needed by this field.
+  // This only works if the field is from the base table.
+  // @todo: fix handler to work with non-base tables.
+  $join = new views_join();
+  $chado_table = 'chado.'.$handler->definition['chado_table'];
+  $join->construct($chado_table, 'chado_entity', 'record_id', $handler->definition['chado_table'].'_id');
+  $alias = $handler->query->add_relationship('chado_'.$handler->definition['chado_table'], $join, $handler->table_alias, $handler->relationship);
+
+  return $alias;
+}

+ 1 - 0
tripal_chado/tripal_chado.info

@@ -6,6 +6,7 @@ package = Tripal
 version = 7.x-2.0
 
 files[] = includes/chado_views_handler_field.inc
+files[] = includes/chado_views_handler_filter.inc
 stylesheets[all][] = theme/css/tripal_chado.css
 
 dependencies[] = tripal

+ 0 - 1
tripal_chado/tripal_chado.module

@@ -25,7 +25,6 @@ require_once "includes/tripal_chado.entity.inc";
 require_once "includes/tripal_chado.schema.inc";
 require_once "includes/tripal_chado.term_storage.inc";
 require_once "includes/tripal_chado.field_storage.inc";
-require_once "includes/chado_views_handler_field.inc";
 
 tripal_chado_set_globals();
 

+ 39 - 1
tripal_chado/tripal_chado.views.inc

@@ -60,6 +60,27 @@ function tripal_chado_add_field_views_data(&$data) {
       if (preg_match('/prop$/', $field['settings']['chado_table'])) {
         continue;
       }
+      
+      // Get some information about the chado table in order to make good
+      // choices for handlers.
+      $table_desc = chado_get_schema($field['settings']['chado_table']);
+      $field_defn = $table_desc['fields'][ $field['settings']['chado_column'] ];
+      
+      // We also need to know if this field is a foreign key.
+      $fk_defn = FALSE;
+      foreach ($table_desc['foreign keys'] as $details) {
+        foreach ($details['columns'] as $left_field => $right_field) {
+          if ($left_field == $field['settings']['chado_column']) {
+            $fk_defn = array(
+              'left_table' => $field['settings']['chado_table'],
+              'left_field' => $left_field,
+              'right_table' => $details['table'],
+              'right_field' => $right_field,
+            );
+          }
+        }
+      }
+      
 
       // Unfortunatly we can't use the field label since that is set at the 
       // instance level and fields are integrated at the field level (independant of bundle).
@@ -78,7 +99,7 @@ function tripal_chado_add_field_views_data(&$data) {
       }
       $data['tripal_entity'][ $field['field_name'] ]['help'] = 'Appears in: TripalEntity:' . implode(', ', $bundle_labels);
 
-      // Finally we define the field.
+      // Define the field.
       $data['tripal_entity'][ $field['field_name'] ]['field']['chado_field'] = $field['settings']['chado_column'];
       $data['tripal_entity'][ $field['field_name'] ]['field']['chado_table'] = $field['settings']['chado_table'];
       $data['tripal_entity'][ $field['field_name'] ]['field']['field_name'] = $field['field_name'];
@@ -87,6 +108,23 @@ function tripal_chado_add_field_views_data(&$data) {
       $data['tripal_entity'][ $field['field_name'] ]['field']['bundles'] = $field['bundles']['TripalEntity'];
       $data['tripal_entity'][ $field['field_name'] ]['field']['handler'] = 'chado_views_handler_field';
       $data['tripal_entity'][ $field['field_name'] ]['field']['click sortable'] = FALSE;
+
+      // Define the Filter.
+      $data['tripal_entity'][ $field['field_name'] ]['filter']['chado_field'] = $field['settings']['chado_column'];
+      $data['tripal_entity'][ $field['field_name'] ]['filter']['chado_table'] = $field['settings']['chado_table'];
+      $data['tripal_entity'][ $field['field_name'] ]['filter']['field_name'] = $field['field_name'];
+      $data['tripal_entity'][ $field['field_name'] ]['filter']['entity_table'] = 'tripal_entity';
+      $data['tripal_entity'][ $field['field_name'] ]['filter']['entity_type'] = 'TripalEntity';
+      $data['tripal_entity'][ $field['field_name'] ]['filter']['bundles'] = $field['bundles']['TripalEntity'];
+      $data['tripal_entity'][ $field['field_name'] ]['filter']['handler'] = 'chado_views_handler_filter_string';
+      
+      // If this is a foreign key field like organism then we want a drop-down.
+      // This requires a special handler.
+      if ($fk_defn) {
+        $data['tripal_entity'][ $field['field_name'] ]['filter']['handler'] = 'chado_views_handler_filter_fk';
+        $data['tripal_entity'][ $field['field_name'] ]['filter']['foreign_key'] = $fk_defn;
+      }
+
     }    
   }
 }