Drupal 7 Views drop-down exposed filter for a text field

John Erhardt

In Views the only option for an exposed filter on a text field is a search box that can be configured for exact or partial word matches. In the case of a term reference, entity reference, or list field, a drop-down form element showing all options is available. This is a much nicer experience for the user when they may not know what options exist, especially if the number of options is relatively short. There don't seem to be any contrib modules that help either.

We recently added functionality to an existing site where we needed an exposed filter on a text field. We were limited by an existing data structure that made implementing taxonomy terms or otherwise moving away from a basic text field a challenge.

It is possible to query all text values and modify the exposed filter in a hook_form_alter. But, actually using the Views API presented a welcome challenge. This post at Stackoverflow was the most helpful to get started.

We need four things to create our custom filter:

  • Declare which views API we're using
  • Define our custom exposed filter in an .inc file
  • Extend the native views filter class
  • Ensure all relevant custom code is included in our module

A hook_views_api() declares the correct Views API in a .module file:

function MODULENAME_views_api() {
  return array(
    'api' => 3,
  );
}

Here we're simply telling views to expect some things in a MODULENAME.view.inc file. If we specify a 'path' in the array we can use a subdirectory such as /view.

Next, hook_views_data_alter() in a .view.inc file to define the filter:

function MODULENAME_views_data_alter(&$data) {
  $data['field_data_field_s_course_ec_tag']['tag_select'] = array(
    'title' => t('Tags select list'),
    'group' => t('Content'),
    'help' => t('Filter by tags field, choosing from dropdown list.'),
    'filter' => array('handler' => 'MODULENAME_filters_handler_filter_tag_select'),
    'real field' => 'field_s_course_ec_tag_value',  
  );
}

Here the important thing to note is the first index of the array. It corresponds with the database table name for the field we are filtering. In our case the field is 'field_s_course_ec_tag' which has a table name of 'field_data_field_s_course_ec_tag'. 'tag_select' is simply a machine name for this filter. We can name it anything unique. 'title' is the readable name of the filter in the Views UI. While one may not care what group to put this filter in, Views requires 'group' to be set as well. 'real_field' corresponds with the value to match in the table. 'filter' is our new custom class to do the filtering which is in another .inc file. The class name can be anything you want, but sticking with Drupal 7 naming conventions keeps it descriptive. To figure all this out, I added a debug statement to get the full $data array to see how things are structured.

class MODULENAME_filters_handler_filter_tag_select extends views_handler_filter_in_operator {
 
  function get_value_options() {
    $tags_list = array();
    $query = db_select('field_data_field_s_course_ec_tag', 'f')
      ->distinct()
      ->fields('f', array('field_s_course_ec_tag_value'))
      ->orderBy('f.field_s_course_ec_tag_value');
    $results = $query->execute();
    foreach($results as $result) {
      $tags_list[$result->field_s_course_ec_tag_value] = $result->field_s_course_ec_tag_value;
 
    }
 
    $this->value_options = $tags_list;
 
    return $tags_list;
  }
}

In this case, we need only override an existing class that performs filtering and give it all the values from our text field. In fact, there's a comment in class views_handler_filter_in_operator for the parent method get_value_options.

  /**
   * Child classes should be used to override this function and set the
   * 'value options'

For text, an associative array is expected where the keys and values are identical.

Finally, don't forget to add your .inc files to your MODULENAME.info file:

files[] = MODULENAME_filters.views.inc
files[] = MODULENAME_filters_handler_filter_tag_select.inc

Enable the module, clear cache (especially views cache) and the filter should be available as an option in the views UI. If not, a good thing to double check is that you haven't misspelled any function names. Now your text field will be available as an exposed filter and will render as a drop-down.