How to Pass Multiple Values through an Exposed Filter in Drupal Views

I regularly work with Views and recently I have had a few odd needs. One of which was when a user selects an item, that item then disappears from the view. The view has exposed filters with AJAX turned on. Since I don’t know how many items I’ll need to filter, I’ll need a way to pass multiple values through an exposed filter. Views apparently does not do this out of the box. If I was filtering on a content field setup for multiple values this may be easier. But no, I am filtering on nids. Since nids are a system field, Views sets the filter handler to 'views_handler_field_numeric'. Which gives me a textbox for input and numeric operators.

We can overcome this limitation. First we create a filter for ‘Content: Nid’ and set it to exposed. The comparision operator isn’t important here, but feel free to set it.

With the filter created we then can alter it:

function my_module_form_views_exposed_form_alter(&$form, &$form_state, $form_id) {
  if($form['#id'] == 'views-exposed-form-id'){
    $form['nid'] = array(
    '#type' => 'hidden',  //you can leave this for now and change it when everything works
     '#attributes' => array(
        'id'=>'filter-nid',  //set an id for jQuery
      ),
    );
  }
 
}

I don’t want to see this form field so I'll set it to ‘hidden’. I’m going to use jQuery to insert my values, so I want to make sure I have an ID.

Next we’re going to write some jQuery to insert our values into the field. You can use any event trigger you want (.click or .change for example). What needs to be accomplished is to insert a delimited string of values.


$(".selectedElement").click(function(){
currentValue = $(this).val();
}

$('#filter-nid').val( $('#filter-nid').val() + currentValue + ",");

There may be a more elegant way of writing this but essentially I’m grabbing the current value from the exposed form field and appending the new value. I’m using commas to delimit the list but you can use any character.

Now we have an exposed filter and jQuery inserting multiple values into the filter as a string, we can make our magic happen by calling hook_views_query_alter. If you do not have Devel installed, do it now. We are going to take a look at the $query object.

function my_module_views_query_alter(&$view, &$query) {
  dpm($query,'query');

This is the query object your view is constructing. And you will notice a few familiar terms (tables, relationships, where, fields, groupby). Our filter that we are creating is going to live in the ‘where’ array. If you set multiple filter groups in your view, you will have a couple of arrays directly inside 'where'. What we really care about is knowing which of these arrays our filter is living. If you just have one array then you are set.

If you are unfamiliar to the $query object, my advice is to go back into the view, change the exposed filter we working on and set it to ‘not exposed’ and add a value like ‘FIND ME’ or ’1234’. Save the view and head back into the dpm. Dig into the ‘conditions’ until you see the ‘value’ you set for the filter. That is the filter we are going to change. With our filter set back to exposed, we’ll move forward.

To make everything work, we are going to add to the hook_views_query_alter:

function my_module_views_query_alter(&$view, &$query) {
  // remove the dpm
  // we only want to alter this view, so we check against the machine name
  // and we make sure our exposed filter is set
  if ($view->current_display == 'my_current_display' && $view->exposed_raw_input['nid']) {
    // first we grab the value from our filter and convert it into an array
    // make sure you use the same character you used to delimit the string
    $nids = explode(',',$view->exposed_raw_input['nid']);
 
    // now we loop over the array of values we created from our exposed filter field
    foreach ($nids as $nid){
      // Be conscience of the ‘where[1]’
      // In the $query object you noticed the conditions array had multiple arrays.
      // Each filter in your view will be placed in a conditions array.
      // It is important to include the empty brackets [] after ['conditions']. This will append the new conditions instead of overwriting them.
      $query->where[1]['conditions'][] = array(
        'field' => 'node.nid', // this is ‘table.field’ from your database. If you used another filter than ‘Content: nid’ this will change. i.e ‘users.uid’
        'value' => $nid,  // looped value from our values array
        'operator' => '!=',  // condition operator. I want to filter out nids, so I am using ‘not equal’
      );
    }
  }
}

There you have it. One way to pass multiple values into an exposed filter. I’m sure there are others. Post your ideas below.

Filed under:

Comments

Hi,

Bit concern with this lines,

currentValue = $('#filter-nid').val();
$('#filter-nid').val(currentValue + team + ",");

can u please explain "team" is it a variable? is there any value?
it seems to me that first u take the value from hidden field & then again store with in same field with , deliminator - please explain ... sorry couldn't get ur point...

also why I should use for hidden field? If I try to enter multiple values in expose filter suppose I enter 5,6 in the GUI Content:Nid then by using query alter i can filter those values from original node table as you have shown in the example - what do think?

Regards,
Nilanjan

ah, try this instead. It's a little more clear:

$(".selectedElement").click(function(){
currentValue = $(this).val();
}

$('#filter-nid').val( $('#filter-nid').val() + currentValue + ",");

All I am doing is grabbing the old value in the form field and placing it back in with the newly selected value and the delimiter. I'll also update the text above.

With the hidden field there is nothing stopping you from using a regular text (visible) field. The reason I am using it is for aesthetics (I don't want to see it). The user experience for my site benefits from the user checking a checkbox and my form does all the dirty work. I also don't expect my users to know nid numbers. So the numbers have no meaning to them and would cause confusion. My last thought on preferring a hidden field is how I set up the value string to be parsed. I would not trust a user to delimit the string properly. If you want to have users enter the values, then you will need more robust Regex to handle other characters and spaces.

Hi,
I was just wondering why you choose this route instead of making your own custom views filter handler. A custom views filter handler would accomplish 2 things:
1) you make the exposed form for that field naturally take 2+ values. If you want them to enter NIDs still (which I get the feeling you don't) you would need to make a custom form element that consists of a list of previously entered values and a textbox for a new value.
2) If you don't actually ever want your user to enter NIDs you can make the exposed form element show titles (for example) of nodes in a radio/checkbox list and when selected the NID is passed to the filter. This improves on user experience without extra non-views related coding.

It would be a relatively simple handler only needing you to override views_handler_filter_numeric::value_form to change the exposed form and views_handler_filter_numeric::query to change how the views query is made.

You can even make your new handler have administrative options allowing it to work this way on some views and as a normal textbox on others. You need to override views_handler_filter::options_form if you want to add administrative options (those that get changed by the administrater of the view when the filter is added).

This approach will work when javascript is not enabled.

I think you have a great solution outlined. Part of the spec given to me was this form was to use AJAX, so javascript not enabled would render the form useless.

Ultimately this was apart of a larger solution and I felt it was an interesting approach to post. Jonathan has a bolg on Custom Filters so I was steering in a different direction for topics.

I was looking for not equal(!=) solution in views UI but was not getting succeeded until I saw this solution

$query->where[1]['conditions'][] = array(
        'field' => 'node.nid', // this is ‘table.field’ from your database. If you used another filter than ‘Content: nid’ this will change. i.e ‘users.uid’
        'value' => $nid,  // looped value from our values array
        'operator' => '!=',  // condition operator. I want to filter out nids, so I am using ‘not equal’
      );

Thanks Bro for the soln and keep up the good work!

Hello, thank you for this post. If I want the user to input multiple values directly, say through cut and paste or with a barcode scanner, can I just leave the out the code:

     '#attributes' => array(
        'id'=>'filter-nid',  //set an id for jQuery
      ),

Also, just to clarify, all code goes into one custom module, not separate modules for each function, is this correct?

Hi Peter, i'm looking for a similar solution; how can i programmatically pass a value. For example, how can i avoid to display node-referenced title wich contains the word "done" so as not to show those that contain the word "todo"?
Thanks if you can give me an help
Eddie

hello I see that here there are expert in exposed filters:
I need for 3 views to use the same exposed filter;

the solutions I think are two:
1)
for every views create an exposed filter;
when user choice for example 1st view and select a value in the
1st exposed filter when choice the 2nd views, the own exposed filter
is set to 1st exposed filter value so the 2nd views is filtered by sam e value;

example of use
exposed filter: Cities
views : Restaurant - Hotel - Cafe

think is need pass exposed session value between the exposed

2)
create a custom filter similar at the exposed filter; one select filled with the cities terms

and when click on a views is actived a function type

hook_views_query_alter

with similar code
$view->query->add_where( here code that read the value selected in the custom filter );

but in this case is necessary a particular code
if normal query (no filtered by city)
WHERE ((node.status = 1) AND (node.type in ('service')) )

query filtered by city with tid 18 must be so
WHERE ((node.status = 1) AND (node.type in ('service'))
AND (node.vid IN (SELECT tn.vid FROM term_node tn WHERE tn.tid = 18 )) )

if someone can solve I pay for the help

Sorry, noob here. Where do you put this jQuery block? Which file and which function?

$(".selectedElement").click(function(){
  currentValue = $(this).val();
}
 
$('#filter-nid').val( $('#filter-nid').val() + currentValue +  ",");

Thanks

Hi Peter,
Thanks for this post. It got me started to solve my own issue with multiple values. However, doing this in hook_views_query_alter is saw that the initial field input string - the comma separated string - was already in the conditions array so I deleted it and added the individual strings back in.

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?