Skip to main content

Using CTools for form item dependency / visibility

An article from ComputerMinds - Building with Drupal in the UK since 2005
10th Jan 2011

James Silver

Developer
James Silver
Hey, you seem to look at this article a lot! Why not Bookmark this article so you can find it easily in the future?

What are CTools Dependencies?

One of several helpers included in the ctools module, ctools dependency is described on the module page itself as "a simple form widget to make form items appear and disappear based upon the selections in another item". It's designed to make it easy and quick to hide/show form elements based on the value of other form elements in the browser using javascript.

This article was written in reference to Drupal 6. See below for notes about Drupal 7.

How to add visual dependencies to your form elements

An example. This code can live in the form builder callback, or a hook_form_alter callback:


// 1. Include CTools Dependent helper
ctools_include('dependent');

$form['restrict_by'] = array ( '#type' => 'select', '#title' => t('Restrict by'), '#options' => array ( 'trip' => t('Trip'), 'month' => t('Month'), ), '#default_value' => $restrict_by_default_value, );

$form['trips'] = array ( '#type' => 'select', '#title' => t('Trips'), '#options' => array ( 'trip1' => t('Mediterranean Cruise'), 'trip2' => t('Trek to the North Pole'), ), '#default_value' => $trips_default_value,

// 2. This ensures that the element will be processed by ctools dependent '#process' => array('ctools_dependent_process'),

// 3. This instructs ctools_dependent which other elements this // should depend on. It has the following format: // #dependency => array('id-of-element-to-depend-on' => array('any_value', 'will', 'trigger', 'me')) '#dependency' => array('edit-restrict-by' => array('trip')), );

$form['months'] = array ( '#type' => 'select', '#title' => t('Month'), '#options' => array ( '1' => t('Jan'), '2' => t('Feb'), //...and so on ), '#default_value' => $months_default_value, // And likewise '#process' => array('ctools_dependent_process'), '#dependency' => array('edit-restrict-by' => array('month')), );

Depending on radios

From ctools/includes/dependent.inc:

For radios, because they are selected a little bit differently, instead of using the CSS id, use: radio:NAME where NAME is the #name of the property. This can be quickly found by looking at the HTML of the generated form, but it is usually derived from the array which contains the item. For example, $form['menu']['type'] would have a name of menu[type]. This name is the same field that is used to determine where in $form_state['values'] you will find the value of the form.

So, in this case our example becomes:


$form['restrict_by'] = array (
  '#type' => 'radios',
  ...
);
$form['trips'] = array (
  ...
  '#dependency' => array('radio:restrict_by' => array('trip')),
);

Things to watch out for / How to make this darn thing hide my checkboxes, radios, fieldsets & CCK widgets!

  1. Add the other #process callbacks In the current version of Form API, if you add a new item to a form and provide a #process for it, then it's default #process callback(s) will not be added. This can break checkboxes, radios and all CCK-provided widget elements. When you add #process to an element, first check what #process values it would get by default, and add those in addition to ctools_dependent_process. Some common examples:
    1. checkboxes: '#process' => array('ctools_dependent_process', 'expand_checkboxes')
    2. radios: '#process' => array('ctools_dependent_process', 'expand_radios')
  2. Are you trying to hide a fieldset? By default fieldsets do not get their #process callbacks run, so ctools_dependent_process does not get called. Set #input = TRUE on the fieldset item to force #process callbacks to run and fix this issue.
  3. Does the output have a -wrapper? CTools dependent assumes that the rendered output of the form item it is trying to hide is wrapped by a certain div:

    ...Rendered form item


    Where ID is the #id of the dependent element. In simple cases you don't need to care about this, but if the form element does not conform, then dependent.js won't be able to find it and nothing will work. This affects checkboxes and radios among other form element types. The solution is to add the wrapper ourselves:

    
    $form['trips'] = array (
      '#type' => 'checkboxes',
      ...
      '#prefix' => '
    ', '#suffix' => '
    ', '#process' => array('ctools_dependent_process', 'expand_checkboxes'), '#dependency' => array('radio:restrict_by' => array('trip')), );
  4. Beware of #process or theme callbacks that disregard or change the #id of a form item Just before process callbacks are executed by FormAPI, the unique ID of the element is generated and set to $element['#id']. Theme callbacks use this value later on to render id attributes. When ctools_dependent_process is called it grabs this #id and passes it to javascript in good faith.

    If a process / theme callback does not honour this #id, then ctools could be trying to use an id that is not actually rendered. To fix this issue it might be sufficient to inspect the HTML, determine the #id and set it manually on the element (FormAPI will use yours rather than generating one). Alternatively you may find that moving 'ctools_dependent_process' to the last item in the #process array fixes the problem.

    If it still isn't working, the most comprehensive way around this problem is to specifiy your own id for the element, then wrap the element accordingly and make sure that ctools_dependent_process is the first listed #process callback:

    
    // Foolproof dependency
    $form['trips'] = array (
      ...
      '#dependency' => array('radio:restrict_by' => array('trip')),
      // 1. Specify an html id of our own devising. This overrides
      //    anything FormAPI might generate
      '#id' => 'edit-trips',
      // 2. Manually wrap the whole thing with 
    '#prefix' => '
    ', '#suffix' => '
    ', // 3. Make sure ctools_dependent_process is listed first '#process' => array('ctools_dependent_process', 'expand_checkboxes'), );

    If you ever try to hide a hierarchical select element using ctools, you'll probably encounter this problem.

Dependencies In Drupal 7

Form element dependencies are much easier to create now, with support 'out of the box', working in a slightly similar way to these CTools dependencies, but without needing CTools! The #states form element property is the answer. Our article on dynamic forms in Drupal 7 goes into more detail. You can see this in action at d7.drupalexamples.info.

If you want to use AJAX, dynamically altering forms has been made easier than ever too. Given the advantages to handling form item dependencies on the server, rather than through javascript (e.g. consistent experience for js and non-js users, ability to customize the dependence beyond just visibility), consider familiarizing yourself with how its done and choosing to handle visibility dependencies via ajax.

You can of course use CTools dependency in Drupal 7 - it's still here, and offers more flexibility with evaluation conditions - but the #states solution in Drupal core will be the answer for most people.

Related pages

Hi, thanks for reading

ComputerMinds are the UK’s Drupal specialists with offices in Bristol and Coventry. We offer a range of Drupal services including Consultancy, Development, Training and Support. Whatever your Drupal problem, we can help.