I'm using Views' "expose filters as a block" functionality in a custom search page. One of the filters is for content type, and users need to be able to select multiple options. Views can only generate multi-select lists as dropdowns, however, and the designs require checkboxes. Using a hook_form_alter to flip the #type from 'select' to 'checkboxes' fails because the form API structures those so differently. The views_filter_pack module claims to be able to convert from one to the other, but is extremely over-engineered for this purpose. Some searching around showed that other people had the same problem but no solutions seemed to work.
Then it dawned on me that I didn't need Drupal or Views to use checkboxes, I just needed the browser output to be rendered as checkboxes.
So I did a test in Firebug, rewriting the <select> as checkboxes, with the same name and value's, and it worked!
With that revelation, I made a hybrid of theme_select() and theme_checkboxes() called theme_select_as_checkboxes(), which takes a select element but renders checkboxes. Here's the code:
/**
* hack to make exposed dropdowns appear as checkboxes
* (can't do it w/ form_alter b/c whole structure is different)
* just render the options directly as checkboxes, pass the values the same as the SELECT,
* tricks the form api and views to think it's a dropdown
* [registered w/ hook_theme, toggled w/ #theme in form_alter]
*/
function theme_select_as_checkboxes
($element) {
$output = '';
$selected_options = (array) $element['#post'][$element['#name']]; // the selected keys from #options
foreach($element['#options'] as $key=>$value) {
$id = $element['#id'] . '-' . $key; // custom
// is this option selected?
$selected = (array_search($key, $selected_options) !== false); // (returns key or false)
$checkbox = '<input type="checkbox" '
. 'name="'. $element['#name'] . '[]' .'" ' // brackets are key -- just like select
. 'id="'. $id .'" '
. 'value="'. $key .'" '
. ($selected ?
' checked="checked" ' : ' ')
. drupal_attributes
($element['#attributes']) .' />';
$output .= '<label class="option" for="'. $id .'">' . $checkbox .' '. $value .'</label>' . "\n";
}
return theme_form_element
($element, $output); // wraps it neatly
}
To enable it, register the function in hook_theme() with 'select_as_checkboxes' = array( 'function' => 'theme_select_as_checkboxes'), and set the field you want, in my case 'type', to use the custom theme function: $form['type']['#theme'] = 'select_as_checkboxes';
And it works!
I find this a lot more sensible than converting the whole tree structure in a form_alter. The rendering itself is basically Drupal-standard, it just happens to mix up the types a little.
Comments
Hi Ben,
Looks like a great and customisable workaround - seems like you could also rewrite it for radio buttons etc...
But I'm having some trouble following where things go too - I am assuming you register the function in form.inc (yes?), but I am confused about where to find hook_theme and where to set the custom theme to the field.
I have another question, which is - will this work for CCK fields?
Thanks for doing the work, by the way - the views filter pack module does some weird weird things to the SQL queries and doesn't seem to function at all...
Ben
Erik,
I just implemented this with good success. Was more customizable than trying to modify the select items in a custom module.
The "$form['type']['#theme'] = 'select_as_checkboxes';" goes in a custom module that you'll have to create in a form_alter hook.
I spent 1.5 days trying to change the select to checkboxes in my custom module.
I spent 1.5 hours to finish this with Ben's method.
What about "Complete Idiot's Guide"?
Can you please write more specific what and where I should write?
Pleeese...
I need a little help too!
Can you provide a step-by-step idiot-proof guide?
Thanks so much
This seems like exactly what I'm looking for, although I want to rewrite it for links, instead. But where does this code go?? I see I'm not the only one with this quesetion.
I would appreciate some further guidance on this too. So farI've registered the function in hook_theme().
I've also included "$form['type']['#theme'] = 'select_as_checkboxes';" into a module, inside a MYMODULE_form_alter() function.
You say above to: "set the field you want, in my case 'type', to use the custom theme function".
How do I find out what the field is in _my_ case - presumably it won't be 'type'? How do I find out what it is?
As Ryumkin, Paolo and Tevi above say, if anyone can produce a comprehensive step-by-step guide for this, I would very much appreciate it.
This was very helpful, thanks. For those people who aren't sure of how to implement it, this might help:
Create a module, and then in the .module file register the theme_select_as_checkboxes function as a theme like so:
/**
* Implementation of hook_theme().
*/
function yourmodule_theme() {
return array(
'select_as_checkboxes' => array(
'function' => 'theme_select_as_checkboxes'
),
);
}
Somewhere in the .module file stick in theme_select_as_checkboxes too. Then you need to alter the correct form so that it uses the correct theme for the correct form element. It's an exposed view filter so the following function with a few modifications should work:
/**
* Implementation of hook_form_FORM_ID_alter(&$form, &$form_state).
*/
function yourmodule_form_views_exposed_form_alter(&$form, &$form_state) {
// Note: if you have problems working out what the right form #id
// is you can do print_r($form); exit(); and it will print out the
// form array for you, which will contain the #id somewhere
// Make sure that you only edit the exposed views form you want:
if($form['#id'] != 'views-exposed-form-product-grid-page-2')
return;
// Apply the theme to the form element you want!
// If you can't work out the id of the form element do the print_r
// form thing again
$form['type_tid']['#theme'] = 'select_as_checkboxes';
}
Now, just make sure your module is active and installed and away you go!
This was very helpful, thanks. For those people who aren't sure of how to implement it, this might help:
Create a module, and then in the .module file register the theme_select_as_checkboxes function as a theme like so:
* Implementation of hook_theme().
*/
function yourmodule_theme() {
return array(
'select_as_checkboxes' => array(
'function' => 'theme_select_as_checkboxes'
),
);
}
Somewhere in the .module file stick in theme_select_as_checkboxes too. Then you need to alter the correct form so that it uses the correct theme for the correct form element. It's an exposed view filter so the following function with a few modifications should work:
* Implementation of hook_form_FORM_ID_alter(&$form, &$form_state).
*/
function yourmodule_form_views_exposed_form_alter(&$form, &$form_state) {
// Note: if you have problems working out what the right form #id
// is you can do print_r($form); exit(); and it will print out the
// form array for you, which will contain the #id somewhere
// Make sure that you only edit the exposed views form you want:
if($form['#id'] != 'views-exposed-form-product-grid-page-2')
return;
// Apply the theme to the form element you want!
// If you can't work out the id of the form element do the print_r
// form thing again
$form['type_tid']['#theme'] = 'select_as_checkboxes';
}
Now, just make sure your module is active and installed and away you go!
I've built a basic module around this technique and submitted it for inclusion on Drupal.org. If/When the powers-that-be approve it, I'll post a link here -- hopefully it'll be named "Better Exposed Filters". Basically, it will offer a dropdown menu in the Expose filter area that lets you specify the default display (select box) or checkbox/radio buttons (depending on the "force single" setting).
In answer to #2, you can change a single-select (ie: dropdown menu) to radio buttons by simply changing the #type in hook_form_alter(). Radios and single-select dropdowns work the same in the API. (You should also run htmlentities() on the "" option if the field is not required.) The new module will take care of that as well.
Ben, I've included a link to your site in the project credits and can include a link on the project page (assuming it's approved) if you like. Also, if you had plans to package this solution as a contributed module, please contact me at let me know as I don't want to step on any toes or steal anyone's thunder.
Thanks for posting such a simple and elegant solution!
- Mike
Edit to my post @17:
The parenthetical should read: You should also run htmlentities() on the <Any> option if the field is not required.
Forgot to escape my brackets...
This is a slick solution -- I'd been banging my head against the wall on this for a couple of days before I saw this. One change I made was to make an allowance for default values without a form submission. That allows me to check off some default options in my form alter hook.
// Get the selected keys from #options if the exposed form has been submitted.
if ($element['#post'][$element['#name']]) {
$selected_options = (array) $element['#post'][$element['#name']];
}
// Otherwise, load in the default value.
else {
$selected_options = $element['#default_value'];
}
...
Thanks for this post! I've been trying to transform select fields in Views exposed filters into checkboxes in
hook_form_alter(), which seems to work fine until I start doing OTHER things to the form. I ended up coming back to this simple theme function :)I use Drupal's
form_clean_id()function to generate a unique id. I also determine selected values differently--because the form element is fully processed when we get it in the theme function, the selected values are already present in$element['#value'].* Transform multiple-select fields into checkboxes in the theme layer.
*/
function theme_net2voting_views_select($element) {
$output = '';
// add standard Drupal checkbox class
$attributes = $element['#attributes'];
$attributes['class'] .= ' form-checkbox';
foreach($element['#options'] as $key => $value) {
// generate an id
$id = form_clean_id($element['#id']);
// note #name brackets, like in <select><options>
$checkbox = '<input type="checkbox" '
. 'name="' . $element['#name'] . '[]' . '" '
. 'id="' . $id . '" '
. 'value="' . $key . '" '
. (in_array($key, $element['#value']) ? ' checked="checked" ' : ' ')
. drupal_attributes($attributes) . ' />';
$output .= $prefix . '<label class="option" for="' . $id . '">' . $checkbox . ' ' . check_plain($value) . '</label>' . $suffix . "\n";
}
// use Drupal to finish it off
return theme_form_element($element, $output);
}