Customising the Views Exposed Filter (allowed values, view arguments, field type and javascript)

muggin - July 4, 2009 - 14:40

This example illustrates several points when customizing the Views 2 exposed filter form:

In order to get at our filter structure we hook into hook_form_FORM_ID_alter.

The filter operates on two CCK fields of a CCK node:

make - indicates part manufacturer - a CCK select text box
model - indicates available models from that manufacturer - a simple CCK text box

The make is restricted to certain allowed values. Model is free form.

The exposed filter block required behaviour is:

  1. The make filter must be overridden to only show allowed values that actually occur in the database.

    ie: we don't want to allow filtering on values that we know will return an empty result.

  2. When we pass in a make value as an argument to the view we want the corresponding make selected in our filter.

    Views won't do it for us. ie: if we pass in IBM as a view argument we want IBM selected in our filter.

  3. Our model field is a CCK text box. In our filter however we want to display a select box of available models.

    So we want to ditch the text box and override it with a select box.
    ref: http://drupal.org/node/463990#comment-1612804

    So now we have two dynamic filter select boxes.

  4. The last requirement is that when we select our make out model gets updated on the client.
    The amount of model data here is small so rather than utilising AJAX we just embed our models in the form.
    For this we use the jquery.selectboxes plugin (which also supports AJAX).
    http://www.texotela.co.uk/code/jquery/select/

Improvements and suggestions welcome.

<?php
/**
* hook_form_FORM_ID_alter
*
*/
function HOOK_form_views_exposed_form_alter(&$form, $form_state)
{
 
// function seems to get called twice so need to cache
 
static $make;
  static
$called;

 
$make_field = 'field_make_value_many_to_one'// view make filter id
 
$model_field = 'field_model_value'; // view model filter id
 
  // take a peek
  // note that dpm() is preferable to dvm();
  // displays better and handles recursion
  //dpm($form);
 
  // ===================================================
  // requirement:
  // want our make field to be dynamic
  //
  // we want to override the make allowed values to only
  // include those which occur in the DB
  //
  // ===================================================
 
$content_field = content_fields('field_make'); // CCK make field name
 
$allowed_values = content_allowed_values($content_field);
 
 
// key distinct used keys
 
$db_info = content_database_info($content_field);
 
$result = db_query("SELECT DISTINCT " . $db_info['columns']['value']['column'] . " FROM {" . $db_info['table'] . "}");
 
 
// build and sort options array
 
$options["All"] = "<Any>";
  while(
$row = db_fetch_array($result)) {
   
$key = $row[$db_info['columns']['value']['column']];
   
$options[$key] = $allowed_values[$key];
  }
 
asort($options);

 
// update the make field
 
$field = &$form[$make_field];
 
$field['#options'] = $options;
 
$field['#attributes'] = array(
   
'onclick' => 'updateModels()'    // define our javascript on click handler
 
);
 
 
// =====================================================================================
  // requirement:
  // if we pass in a make argument we want to select  the corresponding value in the
  // make filter select box
  //
  // when filter view using argument the exposed filter block selection does not
  // automatically match the argument
  //
  // $form_state['input'] maintains the input state of our form fields.
  // setting it to our make will cause it to become reselected
  // =====================================================================================

  // get the view argument if defined
 
$make_arg = $form_state['view']->args[0];
  if (isset(
$make_arg) && isset($allowed_values[$make_arg])) {
   
$make = $make_arg;
  }

 
// change our input as required
 
if (isset($make)) {
   
$form_state['input'][$make_field] = $make;
  }

 
// ==============================================================
  // requirement:
  // want to replace our Model text field with a select box
  //
  // in order for the <Any> filter to work ensure that
  // field_model is marked as optional
  // ==============================================================
  //
  // get models associated with each of our makes
  //
 
$models = array();
  foreach (
array_keys($options) as $key) {
    
$result = myDatabase::fetchDistinctModelsByMake($key);  // ROLL YOUR OWN HERE: returns sorted result of makes
    
while($row = db_fetch_array($result)) {
      
$models[$key][] = $row["field_model_value"]; // NOTE: use your own field name here
    
}
  }

 
$model_options['All'] = "<Any>";
 
$input_make = $form_state['input'][$make_field];
 
 
// get models for make
  // note that we cannot merely perform this via js at the client
  // as we need to have valid options in order to accomodate the value of $form_state['input'][$model_field]
 
if (isset($input_make)) {
   
$make_models = $models[$input_make];
   
   
// create model options for this make
   
if (isset($make_models)) {
      foreach (
$make_models as $model) {
       
$model_options[$model] = $model;
      }
    }
  }
 
 
// update the models field
 
$field = &$form[$model_field];
  unset(
$field['#size']);
 
$field['#type'] = "select";
 
$field['#options'] = $model_options;
 
$field['#default_value'] = 'All'
 
 
// validate our input model
 
$input_model = $form_state['input'][$model_field];
  if (!isset(
$input_model) || !isset($model_options[$input_model])) {
   
$form_state['input'][$model_field] = 'All'
  }

 
// =====================================================================================
  // requirement:
  // create our inline javascript to dynamically populate our Model select according to
  // Manufacturer select state
  //
  // in order to update the select box we use the jquery.selectboxes plug in
  // jqueryjs.googlecode.com/svn/trunk/plugins/selectboxes/
  // =====================================================================================
 
if (!isset($called)) {
   
   
$js = 'function updateModels(){';
   
$js .= 'var o = {};'// create our object
   
$model_list = "";
    foreach (
array_keys($models) as $key) {
     
// use JS object literal syntax here so we can key our data by make
      // literal rep is just a short hand for var o = new object;
     
$js .= "o.$key = {";
     
$model_list = "";
      foreach (
$models[$key] as $model) {
        if (
$model_list != "") $model_list .= ', '// extraneous separator can cause JS error in IE 6
       
$model_list .= '"'.$model.'" : "'.$model.'"';
      }
     
$js .= $model_list;
     
$js .= "};";
    }
   
$js .= 'mylib.selection.updateModels(o);}';
   
   
// insert js inline
   
drupal_add_js($js, 'inline');
   
   
// add the select boxes jquery plugin and site js
    // javascript is in the theme for this example but probably best in a module
   
drupal_add_js(path_to_theme()."/js/jquery.selectboxes.min.js", 'module');
   
drupal_add_js(path_to_theme()."/js/mylib.js", 'module');
  }
 
 
// change the button text
 
$form['submit']['#value'] = t('Search');
   
 
$called = true;
}
?>

The mylib javascript file is here:
Note that you will of course have to update the select element ids.

/*
* Javascript Library
* Object literal notation
*/
mylib={
// selection class
selection: {
// update models select box
updateModels:function(options) {
    var make = $("#edit-field-make-value-many-to-one").selectedValues(); 
    var field = "#edit-field-model-value";
    $(field).removeOption(/./);                     
    $(field).addOption({"All" : "<Any>"}, true);   
    if (make != "All") {
    $(field).addOption(options[make], false);
    } 
}
}
}

Thanks!

ericka - August 8, 2009 - 19:08

Thank you so much for this. It is exactly what I need!

grt
Ericka

Exactly what I was look for.

JaceRider - August 14, 2009 - 17:15

Exactly what I was look for. Saved me hours -- thanks!

this is great. thanks Chris

SocialNicheGuru - August 20, 2009 - 20:21

this is great. thanks

Chris

--
Bringing value to the social web by connecting people with events, products, and services that match their interests and values

how to make it work with a nodereference field?

le_petit_basil - September 23, 2009 - 14:53

I'm very excited to have found this posting here.
I'm making an ecommerce (übercart) site for a company that has a few hundred products that are made by a few dozen manufacturers.
But what is the best way to associate products with the product manufacturers?
I'm using a cck manufacturer content type, and then a node reference field to the manufacturers in the products. This makes it relatively easy for the store owners to edit the manufacturers, if need be, as well as the products in their catalog.

The catalog is organized using a taxonomy vocabulary.
I use the taxonomy display instead of ubercart's catalog to view products, an override the taxonomy display with a taxonomy_term_page.tpl.php in my theme.
And this displays a list-products (with term) views block, passing tids as views arguments.

I'd like for users to be able to filter the products shown in the view by the manufacturers, and I'd like to only show manufacturers that make sense - where there actually are products made, that are listed, made by the manufacturer (otherwise it should not be shown).

In the list-products view, I've exposed a filter on the manufacturer node_reference field, but that displays all of the manufacturers, regardless of whether or not any of the products actually listed are made by those manufacturers.

And so, it was with much hope that I attempted to implement the code in this thread. But alas, I haven't been able to figure out how to wrangle this to work with a nodereference field for the product manufacturers, instead of a text field.

Any advice?

Cheers,

A

Madness takes its toll ...

... please have exact change.

Similar, but with Taxonomy Term

mr.andrey - October 1, 2009 - 23:44

And if you want to limit the taxonomy term exposed filter selection to only relevant (non-0-result) terms, here's a post about that:

http://drupal.org/node/463678#comment-2105496

 
 

Drupal is a registered trademark of Dries Buytaert.