Skip to main content

Drupal 7 Form API - Using #states on #markup field

An essential feature of a dynamic site would be to let its users add some contents to it. Often this is done using HTML forms. Drupal offers a rich and relatively simple way to use API for building forms for its Developers. In Drupal parlance it is referred as Form API (or FAPI).

The beauty of this is, developers can create the forms with PHP itself. Essentially using array keys and values (thanks to Drupal coding standards for offering a more readable structure). That is developers don't have to mess with both HTML (for creating / structuring) and PHP (for validation / response handling). With just PHP code we can get the job done.

Drupal 7 adds more spice to this Form API stuff. Yes I'm talking about the newly added #states property. At times it is needed to make a field change to its behavior based on the value its predecessor field takes, #states has been added in this connection. Without #states and earlier to Drupal 7, the common approach for this case would be add a JavaScript file on form load. The JS file will have jQuery snippet to do the needful alteration. But with #states the custom JS code is no longer needed. An additional array keys/values pair would do the needful.

In one of my recent works I had to show a help text (#markup text) as a part of sign up form based on value chosen for plan field. 

It was noticed that #states is having no effect on #markup field. The code I thought would work is below,

<?php
$form['plan'] = array(
  '#title' => t('Select a Plan'),
  '#type' => 'radios',
  '#options' => $plans,
  '#default_value' => $default_plan,
);
foreach ($plans as $nid => $plan) {    
  $form['plan_feature_' . $nid]['markup'] = array(
    '#markup' => kf_support_features($nid),
    '#states' => array(
      'visible' => array(
        ':input[name=plan]' => array('value' => $nid),
     ),
  );
}

Essentially I need to show a specific html text block based on value chosen for plan field. Note the #states in second field. It just didn't work the way it was expected. Looks like a limitation in Form API and core developers might have done this for some obvious reasons (could be because there is no HTML id attribute for markup field).

To workaround this case, a special wrapper field type is there in Form API. Referred by the name 'container'.

Actually this was explained in Form API page comment with a sample code. Thanks to becw for the same. Accidentally searching for '#markup' helped me locate this tip. Source https://api.drupal.org/comment/11049#comment-11049

After updating the code to use 'container' element, it worked as expected. The revised code is below,

<?php
$form['plan'] = array(
  '#title' => t('Select a Plan'),
  '#type' => 'radios',
  '#options' => $plans,
  '#default_value' => $default_plan,
);
foreach ($plans as $nid => $plan) {    
  $form['plan_feature_' . $nid] = array(
    '#type' => 'container',
    '#states' => array(
      'visible' => array(
        ':input[name=plan]' => array('value' => $nid),
      ),
    ),
  );
  $form['code_feature_matrix_' . $nid]['markup'] = array(
    '#markup' => kf_support_features($nid),
  );
}

I hope this would help others as well.

Comments

ata (not verified)

Sun, 02/16/2014 - 09:35

foreach ($plans as $nid => $plan) {
$form['plan_feature_' . $nid] = array(
'#type' => 'container',
'#states' => array(
'visible' => array(
':input[name=plan]' => array('value' => $nid),
),
),
);
$form['plan_feature_' . $nid]['markup'] = array( //<<<<<<--- must be child of container!!
'#markup' => kf_support_features($nid),
);
}

KingAndy (not verified)

Wed, 09/09/2015 - 10:06

Yup, just what I was looking for.

For reference, yes, it doesn't work because the markup #type doesn't have any of the traditional form API wrappers around it, so the form API has no way of reliably targeting it. The same applies to most "#theme" elements that aren't specifically rendering a form API field or widget.