Heine

  • Home
  • Drupal
  • About
Home

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:

$header = array(
  '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.

function example_elements_form() {
  $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
$options = array(
  'one' => array(//...),
  'two' => array(//...),
  'three' => array(//...),
);

When #multiple is TRUE and row one is selected, $form_state['values']['nodes'] will be

array(
 'one' => 'one',
 'two' => 0
 'three' => 0,
)

When #multiple is FALSE and row one is selected, $form_state['values']['nodes'] will be

'one'

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.

function example_elements_form() {
  $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.

  • Drupal
  • Planet Drupal
  • FAPI

Comments

cool

Submitted by rapsli (not verified) on Wed, 2009/01/28 - 11:45

awesome... is this already in a stable drupal version?

It is in the 7.x development

Submitted by Heine on Wed, 2009/01/28 - 12:38

It 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:15

Rocks 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:31

I 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...

$row[] = drupal_render($element[$key]);

... 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:16

The 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:03

Sorry 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:37

Nice 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:43

Awesome news, Heine. Look forward to putting this to good use!

Great

Submitted by Sven Decabooter (not verified) on Wed, 2009/01/28 - 22:11

Great 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:35

Thanks 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:45

You 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:16

I 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 to theme_table() ):

$options[$item->id] = array(
        '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:26

Very 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:31

The table select element works like checkboxes or radios (depending on #multiple).

Suppose we have the following form:

  $header = array(
    '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 is FALSE and you select unique_id_item_1, $form_state['values']['example'] will be 'unique_id_item_1'.

When #multiple is TRUE, $form_state['values']['example'] will be :

array
(
    [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:20

How 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:41

If you are using the tableselect control with #multiple set to TRUE you get as value an array with
selected items (value == key) and nonselected items (value 0):

array(
 '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:

array(
  'one' => 'one',
)

make tableselect with expanded row

Submitted by chenop (not verified) on Sat, 2010/01/30 - 23:41

Hi,

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?

Recent posts

  • Teampassword manager's password generator is biased
  • Other vectors for SA-CORE-2014-005?
  • Lazy loading: hook_hook_info is for hook owners only.
  • "Always offline" problem in EA's Origin due to antivirus
  • From bug to exploit - Bakery SSO
more

Security reviews

I provide security reviews of custom code, contributed modules, themes and entire sites via LimoenGroen.

Contact us for a quote.

Follow @ustima

Copyright © 2021 by Heine Deelstra. All rights reserved.

  • Home
  • Drupal
  • About