"Advanced Search" fieldset inside exposed filter

Events happening in the community are now at Drupal community events on www.drupal.org.
shushu's picture

I was looking for a simple way to filter a view, and on the other hand allow more advanced filtering.
I didn't want fancy javascript games, nor to split between the views (simple vs. advanced).

My solution combined naming the exposed filters with a given prefix, splitting the widgets into two arrays in the preprocess function, and changing the theme of the exposed view by putting the advanced widgets in a fieldset.

So:

  • Adding all of the "simple search" exposed filters as-is.
  • Every "advanced" filter will get the prefix "advanced_" as the filter identifier.
  • The preprocess function will split the widgets variable into two lists according to the prefix.
  • function theme_preprocess_views_exposed_form(&$vars) {
      // Split the widgets into standard and advanced widgets
      foreach ($vars['widgets'] as $id=>$widget) {
        if (preg_match('/edit-advanced/', $widget->id)) {
          $vars['advanced_widgets'][$id] = $widget;
          unset($vars['widgets'][$id]);

          // Adding fieldset in the template required us to add the javascript specifically
          drupal_add_js('misc/collapse.js');
        }
      }
    }

  • Creating new template file named views-exposed-form--VIEW_NAME.tpl.php, and adding the advanced widgets inside a fieldset.
  •    Standard template, and then...
      <div class="views-exposed-widgets clear-block">
        <fieldset class=" collapsed collapsible">
            <legend>Advanced Search</legend>
            <?php foreach($advanced_widgets as $id => $widget): ?>
              <div class="views-exposed-widget">
                <?php if (!empty($widget->label)): ?>
                  <label for="<?php print $widget->id; ?>">
                    <?php print $widget->label; ?>
                  </label>
                <?php endif; ?>
                <?php if (!empty($widget->operator)): ?>
                  <div class="views-operator">
                    <?php print $widget->operator; ?>
                  </div>
                <?php endif; ?>
                <div class="views-widget">
                  <?php print $widget->widget; ?>
                </div>
              </div>
            <?php endforeach; ?>
           
        </fieldset>
      </div>

    This is more or less. Hope it was clear enough.

    I was trying to make the same task by changing the $form itself, but it only made a mess.

    Is there any better way to accomplish this ?

    Regards,
    Shushu

    Comments

    Date filters throws a wrench

    jason.bell's picture

    Thanks! This got me a lot further than mucking around with hook_form_alter and almost accomplished what I needed. This view has 2 exposed date filters that give to/from options and they are not caught by the prefix because of nesting.

    I'm looking at http://www.bjrtechnologies.com/completely-customize-views-exposed-filters as a possible way to rewrite the entire output. What I like about your solution is that when I add a new filter, just prepending the filter id will allow it to be picked up instead of having to edit a custom module when somebody changes their mind.

    http://img.skitch.com/20100511-ckaib4w8jtfgyqtitdax9m9gqq.jpg

    Any idea how to work around this?

    I tried targeting by $widget->operator which collected these filters into the Advanced Search area, but started given 3 date filters on each set for some reason.

    I am not sure I understand the problem

    shushu's picture

    What is the problem in the screenshot you sent ?
    Do you want the dates to be in the advanced as well ?
    If so, try to printout the whole $widget and I will take a look, maybe I could think on something to make it simple.

    What exactly is the "nesting" problem ?

    I solved this for my use

    jason.bell's picture

    The problem in that shot was that I needed to get the date filters into the advanced fieldset as well, and even though they had the prefix on the filter identifier, due to their composition they were being missed.

    I solved this by updating the if statement if (preg_match('/edit-adv/', $widget->id) || preg_match('/date/', $widget->widget)) { so that it would collect any widget with date in its name and include it in the advanced_ group.

    Thanks for the repsonse.

    Making a module out of it

    shushu's picture

    Hi,
    Do you think there is a place to make a module out of it ?
    If others like you need it, I rather make it simpler and clearer to use.
    What do you think ?

    Re: expose filter layout module

    jason.bell's picture

    I agree that it would be nice to have an easier and cleaner way to customize the layout of exposed filters. Theme information is provided for all fields displayed which makes it easy enough to customize in the theme layer, but the filters template doesn't offer as much flexibility.

    Just floating ideas on how to make this easier for users to implement as a module…

    • provide a checkbox in the filter configuration "Include in advanced filter group" to eliminate the need for prefixing filter id manually
    • An easy way via the interface to provide a custom title for the grouping. "Advanced Search" won't fit everybody's needs
    • Multiple groupings? Would there be a way to provide options to group some filters in one set and others in a different set.

    Since "Advanced Search" is so specific I wonder if as a module it's value might be increased by providing the base view as a drop in replacement for the default search pages (content, user, help) plus the advanced grouping that we have discussed here. The module would create the views needed that could then be customized including the functionality addressed above.

    It could certainly help a number of my projects and I'd be happy to collaborate on it as needed.

    Confused on one piece...

    marty.true's picture

    @shushu:

    I like wherre you're going with this and everything makes sense to me except for one tiny piece, if you could clarify...

    In your code:
    if (preg_match('/edit-advanced/', $widget->id)) {
    Where is the '/edit-advanced/' coming from? What is that exactly? Is that the name of your View? That is the one part that is tripping me up.
    I figured it out... working great, thanks for the idea!!

    Thanks in advance!

    For others…

    jason.bell's picture

    Glad you figured it out. For the benefit of others that may come across this…

    …that line is doing a regular expression match for exposed filters' ID. In the original above, based on the filter prefix suggested by shushu, it is matching "edit-advanced", before adding it to the advanced_widgets array . In my usage I used a prefix of "adv" and so needed to edit that line of the code listed above to match "edit-adv" in the ID strings of my exposed filters.

    I kno my problem isnt just

    draganFSD's picture

    I kno my problem isnt just about this case but something similiar and i hope Im knocking on right door 4 answer.
    I need something like i found on this site http://www.cars.com where u can choose a type of car and after you choose 4 example BMW on below dropdown menu u can choose only type of cars from BMW and so on. I kno that can be done in Views by exposed filter but i have no clue how to make trigger for sub dropdown in some kind of relation...

    I hope u understand what I need.

    Thanks in advance...

    Hierarchical Select

    jason.bell's picture

    You may be looking for something like Hierarchical Select (http://drupal.org/project/hierarchical_select) which uses the car make model example.

    Hello Shushu,

    aac's picture

    Hello Shushu,
    Is it possible to use your code to solve this issue

    http://drupal.org/node/893390

    Thanks for any help!!

    ---~~~***~~~---
    aac

    I am not sure, at least not

    shushu's picture

    I am not sure, at least not as-is.

    I am not sure that I understand 100% the situation you described. If you can draw those cases, it might help.

    I also asked this issue

    aac's picture

    Thanks for your prompt response!!
    I also asked this issue here

    http://stackoverflow.com/questions/3602063/decouple-all-the-exposed-filt...

    Got one answer there. I shall try to implement that by using hook_form_alter(). I have not developed any module yet, so it will be quite challenging for me.
    If you have any other solution, then please suggest here.
    Thanks!!

    ---~~~***~~~---
    aac

    Trying to use this solution

    andosteinmetz's picture

    Hi,

    I'm looking to do just this, and added the preprocess function into my template.php file, and then added the additional code into my views-exposed-form.tpl.php, but I've been getting an error message from the views-exposed-form.tpl.php.

    The error reads:
    Invalid argument supplied for foreach() ...

    which I'm taking to mean that $advanced_widgets is not being recognized. Any clues?

    Many thanks in advance!

    update

    andosteinmetz's picture

    Moved this into /site/all/modules/views/theme/theme.inc and got it to work, but that seems like a bad place for it...

    I had put it in my template.php file under the name:
    function solutions_center_preprocess_views_exposed_form(&$vars)
    (solutions_center is the name of my template)

    Sorry if this question's a bit daft, but I'm still a newcomer to Drupal...

    ok got it

    andosteinmetz's picture

    Sorry for the flood of comments. Turned out I just had to clear my theme cache under Administer -> Performance

    Thanks.. this was very

    capellic's picture

    Thanks.. this was very helpful.

    "There's got to be a better way!"

    saltednut's picture

    "I was trying to make the same task by changing the $form itself, but it only made a mess."

    Yes, it was a mess. But I somehow managed. Requires the "advanced_" or "basic_" hook on your Filter identifier. You can add more custom fieldsets at this point by adding more to the $hashes array.

    <?php
    function views_exposed_filters_fieldsets_form_alter(&$form, $form_state, $form_id){

    //define hash array as "title", collapsed
    $hashes = array(array("basic",FALSE),array("advanced",TRUE));

    $haystack = $form['#id'];
    $needle = "views-exposed-form";    
      if(
    strlen(strstr($haystack,$needle))>0):                         
        foreach(
    $hashes as $hash):
         
    $hashExists = FALSE;
         
    $hashFieldset = $hash[0].'-fields';
         
    $form[$hashFieldset] = array(
           
    '#type' => 'fieldset',
           
    '#title' => $hash[0],
           
    '#collapsible' => TRUE,
           
    '#collapsed' => $hash[1],
           
    '#tree' => FALSE,
          );
         
    $hashCheck = $hash[0].'_';
          foreach(
    $form['#info'] as $refkey => &$ref){     
            foreach(
    $ref as $title){          
              if(
    strlen(strstr($title,$hashCheck))>0):                     
               
    $hashExists = TRUE;       
                foreach(
    $form[$title] as $key => &$value):
                 
    $form[$hashFieldset][$title][$key] = $value;
                  if(
    $key == 'min'){ $form[$hashFieldset][$title][$key]['#title'] = $form['#info'][$refkey]["label"]; }
                endforeach;
               
    $form[$hashFieldset][$title]['#title'] = $form['#info'][$refkey]["label"]; 
                unset(
    $form[$title]);
                unset(
    $form['#info'][$refkey]);
              endif;  
            }
          }
          if(
    $hashExists == FALSE){ unset($form[$hashFieldset]); }
        endforeach;
      endif; 

      if(
    $hashExists == TRUE):
       
    $submit = $form['submit'];
        unset(
    $form['submit']);
       
    $form['submit'] = $submit;
      endif;
    }
    ?>

    Form alter at module

    pepe84's picture

    Nice piece of code brantwynn. Here it is an improved version to mantain user input.

    <?php
    function mymodule_form_alter(&$form, &$form_state, $form_id) {
        static
    $hashes;
       
        if (empty(
    $hashes)) {
           
    // Define hash as [key, "title", collapsed]
           
    $hashes = array(
                array(
    'basic', t('Basic search'), FALSE),
                array(
    'advanced', t('Advanced search'), TRUE),
                array(
    'more', t('More filters'), TRUE)
            );       
        }
       
       
    // Views exposed filters form case?
       
    $hashExists = FALSE;
       
        if(
    strlen(strstr($form['#id'], 'views-exposed-form')) > 0) {
           
    // Prepare references hash
           
    $namespaces = array();
            foreach(
    $form['#info'] as $refkey => &$ref) {
               
    // Retrieve namespaces
               
    $name = $ref['value'];
               
    $namespace = explode('<em>', $name);
                if (
    count($namespace) > 1) {
                   
    // Removing filter name
                   
    array_pop($namespace);
                   
    $namespaces[implode('</em>', $namespace)][$name] = $refkey;
                }
            }
           
    // Check fields
           
    foreach($hashes as $hash) {
               
    $namespace = $hash[0];
                if (isset(
    $namespaces[$namespace])) {
                   
    $hashExists = TRUE;
                   
    $fieldsetKey = $hash[0] . '-fieldset';
                   
    $filters = array_keys($namespaces[$namespace]);
                   
    $form[$fieldsetKey] = array(
                       
    '#type' => 'fieldset',
                       
    '#title' => $hash[1],
                       
    '#collapsible' => TRUE,
                       
    '#collapsed' => $hash[2] &&
                           
    // Expand fieldset to remember users' input?
                           
    !mymodule_filter_has_value($filters),
                       
    '#tree' => FALSE,
                       
    '#attributes'   => array(
                           
    'class' => 'filters-fieldset'
                       
    )
                    );
                    foreach(
    $namespaces[$namespace] as $refkey) {
                       
    $name = $form['#info'][$refkey]['value'];
                       
    $label = $form['#info'][$refkey]['label'];
                       
    // Assign original field values
                       
    $form[$fieldsetKey][$name] = $form[$name];
                       
    $current = &$form[$fieldsetKey][$name];
                       
    $current['#title'] = $label;
                       
    // Specific case
                       
    if (array_key_exists('min', $current)) {
                           
    $current['min']['#title'] = $label;
                        }
                       
    // Better exposed filters module issue
                       
    if (isset($current["#theme"]) &&
                           
    $current["#theme"] === 'select_as_checkboxes') {
                           
    // Add artificial label
                           
    $current['#prefix'] = $current['#prefix'] .
                               
    '<label for="edit-'.$name.'">'.$label.'</label>';
                        }
                       
    // Remove field and field info from root
                       
    unset($form[$name]);
                        unset(
    $form['#info'][$refkey]);
                    }
                }
            }
        }
       
        if(
    $hashExists) {
           
    // Prepend before submit button
           
    $submit = $form['submit'];
            unset(
    $form['submit']);
           
    $form['submit'] = $submit;
        }
    }


    function
    mymodule_filter_has_value($filter) {
        if (!
    is_array($filter)) {
           
    $filter = array($filter);
        }
        foreach (
    $filter as $curr) {
           
    $curr = isset($_GET[$curr]) ? $_GET[$curr] : null;
            if (!empty(
    $curr) && (is_array($curr) || strtolower($curr) !== "all")) {
                return
    TRUE;
            }
        }
       
        return
    FALSE;
    }
    ?>

    Views Developers

    Group organizers

    Group notifications

    This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: