How to make a range selector filter in Views

Messing with your exposed filters for fun and profit.

Ah, to get the request from a client that just cannot be done via stock Views; they are rare but so choice indeed. This episode of Hacking Views will feature our latest request, making a select box with a range of values where all you have is a CCK number/float.

Impossible you say? Unlikely to happen you quip? Nay! It happens all the time. In fact, considering the options given to us when dealing with number CCK fields it's any wonder that Views doesn't already have an option for this case. Aside from a fancy javascript double-ended slider what is a person to do? Min/max are just ugly! Gaze farther for my solution.

The field in question: field_price
The options requested: 'Less than $1,000', '$1,000–$1,999', '$2,000–$3,999', '$4,000–$6,999', '$7,000 plus'.

Starting from the front I felt like this would be in the domain of a custom module. Since I basically do a custom module for Views on every project, I already had a views_tweaks module but lets quickly go through the process just in case you don't habitually create mini-modules.

First, we create a folder at drupal/sites/all/modules/custom/views_tweaks. I like to put it in a custom folder to make it blindingly obvious that it's my problem when it breaks. In addition, Drush is smart enough to not mess with this folder when you do a drush upc.

Inside the views_tweaks folder we need two files: the info file to tell Drupal what's up, and the actual module file to do the heavy lifting. The info file should be called views_tweaks.info and should look something like this:


name = Views Tweaks
description = Misc tweaks to Views
core = 6.x

dependencies[] = views

After that we need to put together the module file, this will contain all the actual logic. The next three large bits of code will all go in views_tweaks.module one after another.



/**
* Implementation of hook_form_alter().
*/
function views_tweaks_form_alter(&$form, $form_state, $form_id) {
// Load up a $view object like we would expect to work with
if (isset($form['#parameters'][1]['view'])) {
$view = $form['#parameters'][1]['view'];
}
switch ($form_id) {
case 'views_exposed_form':
if ($view->name == 'products_wall') {
views_tweaks_range_to_select('field_price_value', array(
'0,999' => 'Less than $1,000',
'1000,1999' => '$1,000–$1,999',
'2000,3999' => '$2,000–$3,999',
'4000,6999' => '$4,000–$6,999',
'7000,999999' => '$7,000 plus',
), $form, $form_state);
}
break;
}
}

This is the meat of the whole thing, generally this is all you're going to have to modify unless you're trying to build your own type of filter and using this as a sample.

All you need to do here is specify the machine name of the field you're acting on, and an array of options. To find your field name you can do so by doing `dpm($form)` on the first line inside the function and searching for things starting with field_.

When you expose a CCK Number field filter in Views you get a min/max textfield. Which is logical of course, but looks terrible. Basically all we're going to do is specify what will go in those two fields, and then some text to accompany it. So if you want one option, who's min is 0 and max is 10k, your options would look like this:


array('0,10000' => 'Maybe a few, maybe LOTS of widgets!',)

That's pretty much it if all you're looking for is implementation. Paste the other two functions below into your views_tweaks.module and enable the Views Tweaks module in the module admin.

If you want to learn more, continue reading.


/**
* Turn a range field into a select dropdown. This assumes that the $options array
* is going to be something like: array('5,9' => '5 to 9 lbs') where the index is
* a comma delimited string of the min/max values.
* Pass in $optional = TRUE if you want there to be an value at the top of
* the select dropdown. Defaults to TRUE.
*/
function views_tweaks_range_to_select($field, $options, &$form, &$form_state, $optional = TRUE) {
$form[$field]['#type'] = 'select';
if ($optional) {
$options = array_merge(array('All' => ''), $options);
}
$form[$field]['#options'] = $options;
unset($form[$field]['min']);
unset($form[$field]['max']);
$f = $form_state['input'][$field];
$f ? $form[$field]['#value'] = $f : true;
$form[$field]['#element_validate'] = array('views_tweaks_range_validate');
}

The comment says a lot here but I'll try to summarize the functionality as well. If you're reading this I'm going to assume you've done hook_form_alter() before, this is largely the same syntax and uses FAPI like normal. What's we're doing here is destroying the two min/max fields and adding in the options the user specified above.

The real magic comes from #element_validate which will run a function before submission, we'll use this to change the form structure back to what Views expects before it gets to Views.


/**
* Turn values created by range_to_select back into ranges so that Views can process
* the request. This assumes that if the value passed in is 'All' the min/max array
* should be set to array('min' => '', 'max' => '')
*/
function views_tweaks_range_validate($element, &$form_state) {
if (($v = $element['#post'][$element['#name']])) {
if ($v == 'All') {
$min = $max = '';
}
else {
list($min, $max) = explode(',', $v);
}
$form_state['input'][$element['#name']] = array(
'min' => $min,
'max' => $max,
);
$form_state['values'][$element['#name']] = array(
'min' => $min,
'max' => $max,
);
}
}

This last function turns the dropdown back into two range fields in the form_state variable. For some reason Views uses $form_state['values'] instead of $form_state['input'] as I would normally expect. In addition, it has to respect the 'All' value in case selecting a value is optional.

So that's pretty much it. I hope that you learned something, I enjoyed writing it up.

Filed under:

Comments

Hi,

Thank you for the great post. The Select box appears but whenever I filter by it, the URL doesn't have any min or max query string, thus not filtering against the values I set. Thank you

Hi, nice work! I'm trying to implement this but for some reason whenever I invoke

unset($form[$field]['min']);
unset($form[$field]['max']);

I'll get a blank screen! Any idea why?

Thanks for the amazing writeup. It solved most of my problems. Well I had one quick question as to where do I add the label for the range?( 'label' => 'Price)
Thanks

Hello,
Thanks a lot for the awesome writeup, I followed your instructions it works fine with my range filter however when I activate this hack the other fields of my exposed form filter stops showing, could you please give me a lead or hint on how to fix that ??
Thanks again

This function should be great.
I fallowed the instruction but it is not working though..
The dropdown selection appears right after 'apply' button but it doesn't filter anything.

Thanks for the code. I was able to make it work but I'm having some strange watchdog errors reported.

Warning: explode() expects parameter 2 to be string, array given in mymodule_range_validate() (line 62 of /var/www/mywebsite.com/htdocs/sites/all/modules/mymodule/mymodule.module).

Which is this line of code in question:
list($min, $max) = explode(',', $v);

It reports this error anytime my exposed filter is displayed on the site but without a parameter in the url. For example, I'm on my listings page which the url would be /listings and the error hits. But after using the exposed filter, the new url would be /listings?&field_size_value=All which does not generate an error. Any ideas on how to fix this?

Thanks.

It look like there is some serious bug in the D7 version, as far as i can see, if I use a solution like that i always lose the first char from the [#value] item of the array passed to the function for the validation, so for example "All" become "ll", "100,200" become "00,200"....anyone have this strange behavior?? :(

In Drupal 7, you can simply add an exposed filter to the number field, then:

Filter type to expose = Grouped filters
Widget type = Select

Then add your ranges and labels at the bottom.

That is true, but if you have indexed node view, then it wont help, since there isnt any between option :)

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Ready for transformation?