A new form element in Drupal core
Heine Wed, 2009/01/28 - 09:48
This post was written for a development version of Drupal between 6 and 7. The queries and pager need conversion to DBTNG.
With Commit #167487, a new form element has been added to Drupal core (7.x) to provide an alternative means of selecting items. Now, next to 'select' (combobox, list), checkboxes and radios, core carries the 'tableselect' element. This element allows developers to easily create tables with selectable rows. Ideal for those situations where you have to provide a lot of data on the items to the user.
In line with the 'select' type, it has support for the #multiple
property, allowing multiple or single item selection. When set to allow multiple item selection, the element automatically enables "Select all" and SHIFT-select functionality. This can optionally be disabled by setting the #js_select
property to FALSE.
Sortable tables are easy as well; the element works seamlessly with tablesort_* functions.
Properties
- #header
- The table header, an array of field_key => title pairs or the format described for theme_table.
- #options
- The data displayed in the table. Nested array of id => array pairs where the array is an array of field_key => value pairs.
- #multiple
- Determines whether multiple values can be selected. Displays checkboxes when TRUE, radios when FALSE.
Default: TRUE - #js_select
- Whether to provide advanced selection behaviour (SELECT ALL checkbox, SHIFT-select).
Default: TRUE - when #multiple is TRUE.
When #multiple is FALSE, always FALSE. - #empty
- Text to display over the full width of the table should #options be an empty array.
- #default_value
-
#multiple TRUE - as checkboxes - provide an array of id => x pairs for the ids that should be selected by default when #multiple is TRUE.
#multiple FALSE - as radios - provide the id as a scalar for the id that should be selected by default when #multiple is FALSE.
Usage examples
Note that the code examples below contain a few 6.x constructs (db_query, db_rewrite_sql). The point is how the form element behaves.
The most important properties are #header
and #options
, on which the whole element functionality hinges. Both #header
and #options
accept keyed arrays. An item in the #header
array has a significant key which is used by the element to match header and item columns later. #options
accepts an array of arrays that is keyed by unique ids of the selectable items. The array value contains the values of the columns and has keys matching the #header
array keys. An example should make this clearer:
'column_1' => 'Title of column 1',
'column_2' => 'Title of column 2',
);
$options['unique_id_item_1'] = array(
'column_1' => 'Value of column 1, item 1',
'column_2' => 'Value of column 2, item 1',
);
$options['unique_id_item_2'] = array(
'column_1' => 'Value of column 1, item 2',
'column_2' => 'Value of column 2, item 2',
);
Important: Column values are HTML; any plaintext must be escaped with check_plain
.
A simple example
The following example creates the simple table you saw above.
$form = array();
$header = array(
'title' => t('Title'),
'author' => t('Author'),
);
$query = "SELECT n.nid, n.title, u.name FROM {node} n INNER JOIN {users} u ON n.uid = u.uid";
$result = pager_query(db_rewrite_sql($query));
$options = array();
while($partial_node = db_fetch_object($result)) {
$options[$partial_node->nid] = array(
'title' => check_plain($partial_node->title),
'author' => check_plain($partial_node->name),
);
}
$form['nodes'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
// Should $options be array(), this will get displayed:
'#empty' => t('No items available'),
);
$form['pager'] = array('#value' => theme('pager'));
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
#default_value and #multiple
The value of $form_state['values']['nodes']
follows the format of the checkboxes type. By setting #multiple to FALSE, the checkboxes will be replaced by radio buttons. In that case, $form_state['values]['nodes']
follows the format of the radios type. Not just the behaviour of form values follows these elements, #default_value
does so as well:
When #multiple
is TRUE and row one is selected, $form_state['values']['nodes']
will be
'one' => 'one',
'two' => 0
'three' => 0,
)
When #multiple
is FALSE and row one is selected, $form_state['values']['nodes']
will be
A sortable table
Instead of a simple title, #header
values can also take a more complicated array in the format described for theme_table to support sortable tables.
$form = array();
$header = array(
'title' => array('field' => 'n.title', 'data' => t('Title')),
'author' => array('field' => 'u.name', 'data' => t('Author')),
);
// Note the tablesort_sql($header).
$query = "SELECT n.nid, n.title, u.name FROM {node} n INNER JOIN {users} u ON n.uid = u.uid". tablesort_sql($header);
$result = pager_query(db_rewrite_sql($query));
$options = array();
while($partial_node = db_fetch_object($result)) {
$options[$partial_node->nid] = array(
'title' => check_plain($partial_node->title),
'author' => check_plain($partial_node->name),
);
}
$form['nodes'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#empty' => t('No items available'),
);
$form['pager'] = array('#value' => theme('pager'));
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
Thanks to moshe, chx and wimleers for input, noahb for lots of HEAD whitespace chasing rerolls and webchick for a review I could do something with.
Comments
cool
Submitted by rapsli (not verified) on Wed, 2009/01/28 - 11:45awesome... is this already in a stable drupal version?
It is in the 7.x development
Submitted by Heine on Wed, 2009/01/28 - 12:38It is in the 7.x development version. If you want to use the element in other versions of Drupal, use the Elements module.
Woot
Submitted by eigentor (not verified) on Wed, 2009/01/28 - 13:15Rocks like Hell. :)))
So it will be also available for Permissions Page? Or rather not due to inherent danger of unwillingly enable too many rights?
Does the Elements module in Drupal 6.0 work?
Submitted by Marximus (not verified) on Tue, 2009/03/24 - 22:31I can't see to get it to put the checkbox(es) or radios in the first column. I've traced this to a line of code in theme_tableselect...
... which (it appears) is supposed to cause _elements_expand_tableselect to be invoked... but it doesn't. I don't see how it can... as $element[$key] is undefined... assuming you follow the example of how to assemble the form.
Could be there is something simple missing from the read-me?
The tableselect element from
Submitted by Heine on Wed, 2009/03/25 - 18:16The tableselect element from Elements for Drupal 6 certainly works. _elements_expand_table select is called during form processing, so I'm pretty sure you are not correctly building the form (via drupal_get_form('form_builder').
You may want to post code. See also http://heine.familiedeelstra.com/coding-help-tips.
Yup, you are correct... I was using it incorrectly...
Submitted by Marximus (not verified) on Wed, 2009/03/25 - 21:03Sorry about the fuss. Never meant to cast Elements in a bad light... just wanted confirmation that it worked... assuming I used it correctly.
A long story short... I build a table (of book titles) using the table and pager themes... wanted to give the user the option to select table rows ("hide this from me", "I own this one"). So the paradigm was purely "output"... and my hook_menu had no need to call "drupal_get_form" as the page wasn't a form. I thought I could fake my way through this with "drupal_render"... but apparently not. So I've modified the menu hook and now the form displays correctly... with checkboxes displayed. (Now to get it to work with the pager theme and JS... ahhh the challenges).
As the Elements is seeking a maintainer, I felt it prudent to simply ask "does it work?" The code checked in for Drupal v7.0 is somewhat changed... so who knows? Sorry to have wasted your time.
Thanks,
Mark
PS. My posts don't really contribute to the meat of your thread here... I've no qualms if you delete them.
Nice work Heine! This patch
Submitted by Anonymous (not verified) on Wed, 2009/01/28 - 20:37Nice work Heine! This patch is in due to your skill and guidance...
Awesome news, Heine. Look
Submitted by Ryan (not verified) on Wed, 2009/01/28 - 20:43Awesome news, Heine. Look forward to putting this to good use!
Great
Submitted by Sven Decabooter (not verified) on Wed, 2009/01/28 - 22:11Great feature!
This will make a lot of administration pages so much easier.
Great job!
Submitted by Wim Leers (not verified) on Wed, 2009/01/28 - 22:35Thanks for your efforts Heine! I know it took quite some time & effort :)
Awesome work!
Submitted by Dave Reid (not verified) on Wed, 2009/01/28 - 22:45You did a great job on this issue Heine! Congrats!
Good stuff! What about extra columns in the <tbody> section?
Submitted by Kent R (not verified) on Thu, 2009/03/26 - 05:16I want to emulate a form like on admin/build/contact, where there are two columns under the 'Operations' header.
I found I can do this by embedding closing and opening tags for
<td>
into the value for the 'operations' key like below, but I'm wondering if there's a cleaner way (something more along the lines of how rows are built for input totheme_table() )
:'title' =>
l( $item->title, 'admin/my_module/edit/' . $item->id, array('query' => $destination) ) ,
'id' =>
$item->id,
'operations' =>
l( t('edit'), 'admin/my_module/edit/' . $item->id, array('query' => $destination) ) .
'</td><td>' .
l( t('delete'), 'admin/my_module/delete' . $item->id, array('query' => $destination) ),
);
Very nice tutorial, But how
Submitted by Lasters s (not verified) on Fri, 2009/10/30 - 12:26Very nice tutorial, But how do I know which values are selected?
Depending on #multiple, just like checkboxes / radios
Submitted by Heine on Fri, 2009/10/30 - 13:31The table select element works like checkboxes or radios (depending on #multiple).
Suppose we have the following form:
'column_1' => 'Title of column 1',
'column_2' => 'Title of column 2',
);
$options['unique_id_item_1'] = array(
'column_1' => 'Value of column 1, item 1',
'column_2' => 'Value of column 2, item 1',
);
$options['unique_id_item_2'] = array(
'column_1' => 'Value of column 1, item 2',
'column_2' => 'Value of column 2, item 2',
);
$form['example'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#multiple' => // TRUE or FALSE
// Should $options be array(), this will get displayed:
'#empty' => t('No items available'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
When
#multiple
isFALSE
and you selectunique_id_item_1
,$form_state['values']['example']
will be'unique_id_item_1'.
When
#multiple
isTRUE
,$form_state['values']['example']
will be :(
[unique_id_item_1] => unique_id_item_1
[unique_id_item_2] => 0
)
You can filter this array with array_filter to get just the selected items.
Drupal Module SELECT Control
Submitted by Anirudh (not verified) on Tue, 2009/12/01 - 11:20How do i get value selected in select control after submiting form in module...
$gender = $form_state['values']['gender'];
this is giving me whole list, i want only selected value....!!!!
Thanx in adv.
array_filter
Submitted by Heine on Thu, 2009/12/03 - 08:41If you are using the tableselect control with
#multiple
set toTRUE
you get as value an array withselected items (value == key) and nonselected items (value 0):
'one' => 'one',
'two' => 0
'three' => 0,
)
You can remove the nonselected items with $filtered = array_filter($form_state['values']['foo']).
Calling array_filter on the previous example array would result in the following array:
'one' => 'one',
)
make tableselect with expanded row
Submitted by chenop (not verified) on Sat, 2010/01/30 - 23:41Hi,
I'm using the elements module in my site.
I want to make the rows of the table expanded, which mean that when a user will click the row an extra info will be shown regarding the node that was clicked.
A second click will collapse the row.
I guess this feature should be done somehow with Javascript.
Anyone have an idea how should i do it?