7

I try to create a custom exposed filter in views 3 of Drupal 7

Where can I learn about how to create a custom exposed filter for Views 3 and D7?

Someone put some code example in the URL above, but it seems it is in views 2!? Does any one know how to make it in Views 3.

In the module:

/**
 * Implements hook_views_api().
 */
function modulename_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'modulename') . '/inc',
  );
}

/**
 * Implementation of hook_views_handlers() to register all of the basic handlers
 * views uses.
 */
function modulename_views_handlers() {
  return array(
    'info' => array(
      // path to handler files
      'path' => drupal_get_path('module', 'modulename') . '/inc',
    ),
    'handlers' => array(
      // register our custom filter, with the class/file name and parent class
      'modulename_handler_filter_filtername' => array(
        'parent' => 'views_handler_filter',
      ),
    ),
  );
}

function modulename_views_data() {
  $data = array();

  $data['node']['filtername'] = array(
    'group' => t('Custom'),
    'real field' => 'my_custom_filter_field',
    'title' => t('My custom filter'),
    'help' => t('Some more detailed description if you need it.'),
    'filter' => array(
      'handler' => 'modulename_handler_filter_filtername',
    ),
  );

  return $data;
}

In modulename_handler_filter_filtername.inc

class modulename_handler_filter_filtername extends views_handler_filter {

  /**
   * Options form subform for setting exposed filter options.
   */
  function value_form(&$form, &$form_state) {
    parent::value_form($form, $form_state);

    // get list of years from database
    $query = db_select('node', 'n');
    $query->addExpression("FROM_UNIXTIME(n.created, '%Y')", 'year');
    if (isset($this->view->filter['type'])) {
      $query->condition('n.type', $this->view->filter['type']->value, 'IN');
    }
    $result = $query->orderBy('year', 'ASC')
      ->execute()
      ->fetchAllAssoc('year');

    $years = array(
      '0' => t('All'),
    );
    foreach ($result as $k => $v) {
      $years[$k] = $k;
    }

    // create form element with options retrieved from database
    $form['value']['year'] = array(
      '#type' => 'radios',
      '#options' => $years,
      '#default_value' => end($years),
    );
  }

  /**
   * Alters Views query when filter is used.
   */
  function query() {
    // make sure base table is included in the query
    $this->ensure_my_table();

    // retrieve real filter name from view options
    // this requires 'real field' filter option to be set (see code above)
    $real_field_name = $this->real_field;
    // get the value of the submitted filter
    $value = $this->view->exposed_data[$real_field_name];

    // finally, alter Views query
    if (is_numeric($value) && $value != 0) {
      /* 
        Having several custom exposed filters, make sure subsitution patterns
        (e.g. :filtername_value below) don't match across different filters.
        I spent some time figuring out why all my filters had the same value.
        It looks like the query skeleton is built first and then all replacements
        are made in bulk. Prefixing value with filter name looks good imo.
      */
      $this->query->add_where_expression($this->options['group'],
        "FROM_UNIXTIME(node.created, '%Y') = :filtername_value",
        array(':filtername_value' => $value));
    }
  }
}
10

There's a module that provides some examples here: https://www.drupal.org/project/views_plugin_examples. It's limited but has an exposed filter example.

General discussion here: https://api.drupal.org/api/views/views.api.php/group/views_plugins/7

In broad outline:

  1. Create a custom module, with a subdirectory called "views"

  2. In your .info file, add a line: files[] = views/MODULE_handler_filter_FILTERNAME.inc Your filter will never show up if you forget this!

  3. In the .module file, add an implementation of hook_views_api():

    function MODULE_views_api() {
      return array(
        'api' => 3,
        'path' => drupal_get_path('module', 'MODULE') . '/views',
      );
    }
    
  4. Within the views subdirectory, you will need to create two files, MODULE.views.inc and MODULE_handler_filter_FILTERNAME.inc

  5. In MODULE.views.inc, you will need to implement either:

hook_views_data_alter() - for a new filter on an existing entity type that already has views integration, which is more common OR

hook_views_data() - to provide views integration for your custom entity, including filters.

API documentation is available for both functions.

A quick example:

    function MODULE_views_data_alter(&$data) {
       if ( isset($data['users']) && !isset($data['users']['FILTERNAME']) ) {
        $data['users']['FILTERNAME'] = array(
          'real field' => 'uid', // name of entity field to which filter applies
          'title' => t('HUMAN READABLE NAME OF FILTER'),
          'help' => t('HELP TEXT'),
          'filter' => array(
            'handler' => 'MODULE_handler_filter_FILTERNAME',
          ),
        );
      }
    }
  1. All of the real work happens in MODULE_handler_filter_FILTERNAME.inc. This file defines a new class that implements your filter, class MODULE_handler_filter_FILTERNAME.

You will usually want to extend one of the existing views filter handler classes to get the benefit of existing code. Many of these can be found in subdirectories of the views module directory. The basic filters are in the "handlers" subdirectory, but many more complex filters may be found buried in subdirectories of the "modules" subdirectory. The views filter handlers you find there are the best documentation for how to implement a filter, as well as how to extend an existing filter, and you should read through them.

Normally your custom filter class will a) add fields to the extended filter's settings form, b) in the case of an exposed filter, modify the exposed widget, and then c) implement a query() method that does the real work, based on the saved settings and input from the exposed widget.

  • 1
    There are many explanations on the web about this process but I consistently come back to this one because it's the most clear and concise. – Blake Frederick Apr 24 '18 at 21:43
0

Ran into the same issue and figured it out, looks like the original op has the same problem.

Where you define your exposed filter form in value_form(), the key you use in the array should be the same key used in view_data (or views_data_alter).

e.g. in my case:

function value_form(&$form, &$form_state) {

  parent::value_form($form, $form_state);

  $form['users']['welcome_email_sent'] = array(
    '#type' => 'select',
    '#options' => array(
      0 => '- Any -',
      1 => 'Welcome Email',
      2 => 'Notification Email',

    ),
    '#title' => t('Email History'),
  );

  return $form;

}

And..

function ca_email_history_views_data_alter(&$data) {

  $data['users']['welcome_email_sent'] = array(
    'title' => t('Welcome email sent'),
    'help' => t('Filter based on e-mails the user has received'),
    'filter' => array(
      'handler' => 'ca_email_history_welcome_email_sent_handler',
    ),
  );

  return $data;

}

Notice how both have the key as 'welcome_email_sent', if I change them so they no longer match the issue where the query() function is never called occurs again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.