18

I am trying to learn how to create a custom exposed filter for a contributed module (select or other). I found this tutorial for Drupal 6 but the code doesn't appear to work out-of-the-box on Drupal 7.

I also tried looking at the code in the hierarchical select module but it seems to be much more complex than what I am trying to do.

Does anyone have any suggestions for tutorials or modules that implement custom exposed filters in a relatively simple way (e.g., not a huge number of custom handlers like the location module) that I can learn from?

6

Short answer : nowhere.

But you can find bits of information here and there.

The first place to look is in Views sources, particularly the implementation of existing filters, starting with the simplier ones.

Personnaly, I participated in this thread which was very instructive but not completely satisfactory, as you'll see if you go read it. I think the info here is useful though, and I can at least say it was acurate at the time it was written.

  • 2
    Here is a conference from the last Drupalcon which I just discovered and includes incredibly useful information, including mention of Views part of api.drupal.org documentation I didn't know about. This is the best starting place for Views development I know about so far. – Countzero Mar 27 '12 at 9:00
10

I was lurking around the internet trying to find answer for the same question and this is what I've got in result:

  1. Implement several hooks in your custom module. Replace modulename and filtername with your actual names.

    /**
     * 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;
    }
    
  2. Create folder named inc inside your module folder and create file named modulename_handler_filter_filtername.inc there (see code above for implicit reference to this file). Don't forget to use actual module and filter names.

  3. Paste following code into that modulename_handler_filter_filtername.inc file. The code I used for this example creates a set of radio buttons that present years. So you can filter nodes by year created, using only years in which nodes were created.

    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));
        }
      }
    }
    

That's all you need to get simplest custom exposed filter working!

Note that using FROM_UNIXTIME in condition of SELECT query may slow your database.

  • first: thanks! the best guide ever!, second: for those who are looking for a more advanced usage of query(), see views_handler_filter_numeric.inc – hkoosha Nov 27 '14 at 21:42
  • Also, there is a more elegant usage where you do not have to write the query and substitutions manually, like: $this->query->add_where($this->options['group'], $real_field_name, $this->value['value'], $this->operator);. It can be found in the above link. – hkoosha Nov 27 '14 at 21:46
  • 2
    It works for me on Drupal 7. However, to make this work I had to 1) Delete implementation of hook_views_handler function and 2) Add this to .info file: files[] = inc/modulename_handler_filter_filtername.inc 3) I based this two changes on this post 4) Many thanks! – Roger Jun 23 '15 at 12:20
5
  1. Drupal 7 Tutorial: Creating Custom Filters in Views in English
  2. Tutorial in Russian

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.