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:
- 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.
- 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.
- 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-1612804So now we have two dynamic filter select boxes.
- 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.
/**
* 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); } } } }
Comments
Thanks!
Thank you so much for this. It is exactly what I need!
grt
Ericka
Exactly what I was look for.
Exactly what I was look for. Saved me hours -- thanks!
this is great. thanks Chris
this is great. thanks
Chris
http://SocialNicheGuru.com
Delivering inSITE(TM), we empower you to deliver the right product and the right message to the right NICHE at the right time across all product, marketing, and sales channels.
how to make it work with a nodereference field?
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
Similar, but with Taxonomy Term
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
Informative but not Step-By-Step
I was looking for a step-by-step instruction on how to link an onchange action of a Faculty select box to their respective departments, then the department select box brings out their corresponding courses. This page is informative but does not focus on the real issue on the ground which should be step-by-step. I mean do this, do that and everything works perfectly.