You are here

Drupal 7 FAPI's #states: A Great New UI Improvement For Forms

The little-known #states feature has gone into Drupal 7, and it rocks.

Before you read on, try this dynamic form live at d7.drupalexamples.info. It's developed without using a line of javascript, just plain Form API.

Essentially, you can provide dynamic behavior in a form based on changes to other elements in the form. An easy example: Often you only need to collect information if a particular element is selected. If they select type=student, you don't have to require them to fill in a further "Employer" field.

The idea of #states is that you add a #states property to a form element that is supposed to change when some other form element changes. So if a form element is supposed to be shown or hidden, the #states property will be added on that element, not on the element that caused the change.

The #states property is a structured array of action => condition arrays. The action is 'visible' or 'checked' or 'required' or several other options. The condition is an associative array of 'jquery_selector' => array(value_statement). But mostly you can do it by copying and pasting examples. Much of the time the jquery selector can be ":input[name=field_name]" and the rest of the array can be cookbook from example code.

In the example, the 'tests_taken' field is only to be visible if the form-filler is a high school student:

33 comments

What about people who have JavaScript disabled?
Will the above example never be shown, or will the whole #states thing simply be ignored?
If the former, I guess it would be better to use eg 'invisible' than 'visible'

I found the answer through experimentation in case this helps anyone else:

If you are using a fieldset in front of your elements, then you must account for this in your jQuery selector. For example, if you have a fieldset such as 'ask' with a number textfield you want to expose when some other textfield is filled.

Having three fields on a form: year-from, year-to and a title, all textfields. The title field should be enabled if and only if either of the year fields is filled, and having considered that Form API's #disabled attribute cannot be used for this. So I've came up with this:

This ensures that the form comes up with all the three fields being empty, and the title field being disabled (by jQuery, only on client-side). This ensures that when either of the year fields has anything in it, the title field becomes enabled. Speaking CS: both the enabler and disabler conditions are OR'ed together, but the disabler ones should be AND'ed.

But this solution also has a problem: if either of the year fields gets empty, the title field gets disabled, though it should be only getting disabled if both the year fields become empty. How should I address this?

In other words: the above example is quite good for an OR relation, but how to implement an AND relation between the (disabler) conditions?

I since the dependency is just a jquery target, you could... EXCEPT: The states that you can depend on are all form element states. I wouldn't be surprised if you could depend on the existence of some element though. Give it a try.

I create forms using the webform user interface rather than code. Is there any way to add #states in the GUI? Any examples out there?
Alternatively, is there any way to take my GUI form and extract the Drupal code from it?

I'm not familiar with any way to use #states in webform module, but I don't use it often. I'm quite sure that webform will not generate a module for you. But these would be good support issues in the Webform issue queue.

Great write up on how to make the visibility of fields conditional! Many thnx for the clarity.

I am searching for a solution on how to make particular checkbox options conditional so that:

If user selects checkbox A in Field (i), a certain array of checkbox selections in Field (v) are visible. Additionally, if user also selects checkbox B in Field (i), a different array of checkbox selections is visible as well in Field (v).

I have a few taxonomy vocabularies (iii), one of which (x) has a few term reference fields that are populated with values derived from the additional vocabularies (iii).

In my "add node" form, I am trying to present the (iii) taxonomy fields at the top, then have the (x) taxonomy field below. I'd the checkbox selections in field (x) to be dependent upon the choices made previously in the (iii) taxonomy fields above.

Now I am working on making the entire visibility of the topblock fieldset (and the entire topic field) contigent on whether checks have been made in the previous fields. I will keep you updated on my progress.

Thanks! Hey - for more permanence (somebody might come by and want to know what you did in a year or so) you might want to post your code on http://gist.github.com or as a sandbox on drupal.org, or something like that. Keeping your code in a repository is good for you too, as it helps you find things that you know you did sometime.

Thanks. The only problem with that, of course, is that the ID must be unique on the page. And you can't be using AJAX in any of those elements (which would be strange anyway... one should generally use either #states or AJAX) because AJAX messes with the ID on every load.

In D7, the HTML ID changes on every request, due to drupal_html_id(). In practice, it's always the same on the initial page load, but after that, any part replaced by an AJAX action will result in an unpredictable ID (which will be different).

Hi, I wrote this code, it seems right but my dynamic form doesn't work. When page loads (it loads inside a popup) all fields are shown. My hook_menu calls ripa_slideshow_edit_page() to show only the form without entire page.
I tried several variants but nothing seems work

There is a major example of #states in the Examples project. I recommend starting there and then working with that to understand how to build what you want. There's also a #states exercise that shows nearly everything #states can do.

States property is great but I've found a little problem. Let's say we have a radio buttons like 'Cars' and 'Air Planes'. If 'Cars' button is checked another two checkboxes shows like 'Ferrari' and 'Mercedes'. If 'Air Planes' is checked also two different checkboxes are shown, it's 'Concorde' and 'Boeing'. Simple right? Now, lets say that user decided to choose Cars and then checks 'Ferrari'. After that our user changes his mind and checks 'Air Planes' without unchecking previous 'Ferrari'. And here is the problem, now user can check 'Boeing' and click 'Submit', however our form remember a 'Ferrari' value and 'Boeing' value which is incorrect. The purpose of choosing radio buttons is to give user one option to choose (OR) not many (AND). Does anyone knows how to uncheck 'Ferrari' checkbox automatically/programmatically at the momment when user chooses 'Air Planes'?

Ferrari, Mercedes AND Concorde, Boeing should belong to the same radio group, all the four. This way the user may choose only one, whilst the #states only _helps_ her choosing the right one. Remember: #states is client side stuff, so cannot be relied upon. In other words, #states is for display/UX purposes, not logic (at least as I see it).

I tried the states form entries in a signup form, unfortunately it did not work. Do you have any idea on how to embed conditional fields in the signup module? I see the they us a standard $form entry for settings like "Phonenumber" etc so it shouldn't be to difficult.

Hi Randy. I'm trying to use this to achieve the functionality of having a checkbox that, when selected, causes a range of checkboxes to all be selected. I made the selectall box a separate form field, and it looks like this, and works not a lick:
$form['report_group']['selectall'] = array(
'#type' => 'checkbox',
'#default_value' => 0,
);
$form['report_group']['boxes'] = array(
'#type' => 'checkboxes',
'#options' => $boxes,
'#states' => array(
'checked' => array(
':input[name="report_group[selectall]"]' => array('checked' => TRUE),
),
),