#tree and #parents

Last modified: June 18, 2008 - 17:37

Summary

The Form API compresses two structures into one, one is the rendering structure defined by the keys of the form array. The other is the data structure which is defined by #parents from which #name (HTML element name) and #id are computed. There is an automated feature which creates #parents from the array keys - it is controlled by #tree and described below.

How it works

All forms are a tree, for example:

           (root)
             |
       Foo  / \
           /   \
          |     |
   Bar   /     /\
        /     /  \
       |
Baz  /
    /

In code, this is written:

<?php
$form
['foo']['bar']['baz']
?>

As long as the #tree attribute is TRUE at any point in the tree, the form element is aware that it is in a tree, and traverses the tree towards the root (from baz, to bar, to foo). Along the way, the names of the modules passed are stored in #parents. #parents is used to create the name/ID of the form element itself.

So, if:

<?php
$form
['foo']['#tree'] == TRUE
?>

and
<?php
$form
['foo']['bar']['#tree'] == TRUE
?>

and
<?php
$form
['foo']['bar']['baz']['#tree'] == TRUE
?>

Then #parents for baz will be array('foo', 'bar', 'baz') and the name of the element in the HTML will be edit['foo']['bar']['baz'].

If, on the other hand,

<?php
$form
['foo']['bar']['baz']['#tree'] == FALSE
?>

Then #parents will only be array('baz') and the name of the element in the HTML edit['baz'].

Cascading of #tree

There are shortcuts to traversing the full tree each time. If you set #tree = TRUE at a closer point to the root of the tree, as in:

<?php
$form
['foo']['#tree'] = TRUE
?>

and you have not specifically set #tree anywhere else, then it will cascade and make all of the sub-elements' #tree = TRUE. This is very useful because otherwise you would need to write #tree = TRUE for each element in the tree.

The FAPI code that deals with #tree and #parents

The following is the section of code from which deals with #tree and #parents, taken from _form_builder():

<?php
 
foreach (element_children($form) as $key) {
   
// Don't squash an existing tree value.
   
if (!isset($form[$key]['#tree'])) {
     
$form[$key]['#tree'] = $form['#tree'];
    }

   
// Don't squash existing parents value.
   
if (!isset($form[$key]['#parents'])) {
     
// Check to see if a tree of child elements is present. If so,
      // continue down the tree if required.
     
$form[$key]['#parents'] = $form[$key]['#tree'] && $form['#tree'] ?
       
array_merge($form['#parents'], array($key)) : array($key);
    }
?>

Note that the code is not the same to the explanation above for performance reasons. Instead, each element walking towards to the root as long as #tree is TRUE, we pass #parents down as long as #tree is TRUE.

Common use of #tree

A common use of #tree is fieldsets. Another example is the checkboxes element type where #tree is set to TRUE internally before expanding to multiple checkbox elements.

Common use of #parents

You can set #parents manually, but the need for this is rare. More common is to read #parents to determine where in the form tree the current element is. Setting #parents does not affect the rendering of the form, that's decided by the indexes. However, setting #parents does affect placement of $form_values, as can be seen from filter_form().

An example with insert

When we set fieldset value to TRUE we create the form:

<?php
$form
['colors'] = array(
'#type' => 'fieldset',
'#title' => t('Choose a color'),
'#collapsible' => FALSE,
'#tree' => TRUE,
);
$form['colors']['green'] = array(
'#type' => 'checkbox',
'#title' => t('Green'),
'#default_value' => $node->green,
'#required' => FALSE,
);
$form['colors']['blue'] = array(
'#type' => 'checkbox',
'#title' => t('Blue'),
'#default_value' => $node->blue,
'#required' => FALSE,
);
?>

and this is how they are inserted or updated in a db_query:

<?php
function example_insert($node){
 
db_query("INSERT INTO {example} (nid, question, green, blue) VALUES (%d,'%s', %d, %d)", $node->nid, $node->title, $node->colors['green'], $node->colors['blue']);
}
?>

Make sure $form['foo']['#type'] is null

dwees - September 12, 2008 - 05:23

After some experimentation, it looks like if $form['foo']['#type'] is set to anything, this whole '#tree' thing seems to auto fail. This structure is extremely useful for making your $form_values in the _submit stage much easier to work with though.

The solution of course is not to nest form elements (except for fieldsets, which can be nested with their children only it appears).

I'm not sure this is the intended behaviour or not, but I think this page needs to be updated to reflect the reality.

 
 

Drupal is a registered trademark of Dries Buytaert.