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 new #states example in the Examples module's Form Example shows how a dynamic form can work. You can try it out live as well at d7.drupalexamples.info.
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: $form['tests_taken'] = array(
'#type' => 'checkboxes',
'#options' => drupal_map_assoc(array(t('SAT'), t('ACT'))),
'#title' => t('What standardized tests did you take?'),
'#states' => array(
'visible' => array( // action to take.
':input[name=student_type]' => array('value' => t('High School')),
),
),
);
So we set the action to 'visible' when the condition (our select called student_type is set to 'High School') is true.
That's basically all there is to it.
So when would you use #states as opposed to AJAX forms or a sprinkling of jquery?
- If your form actually changes under the hood based on user input, then you need to use AJAX forms. #states doesn't fundamentally change any of the elements, it just changes their presentation.
- If you don't change the form, but need a transformation that simple conditional logic can't do, you'll have to roll some jQuery.
For more detailed comments and possibilities, read the #states example.
21 comments
What about people who have
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'
All is shown if javascript is disabled
If javascript is disabled, the whole form is shown. It's as if the #states properties didn't exist (because they essentially don't).
So the form works, but you might need extra text and might want to use fieldsets to organize the data, for example.
Drupal 6: conditional fields
Or try this Drupal 6 module conditional fields which allows you to create these forms without a single line of javascript or php code! In a couple of clicks you set up the perfect cck form.
Try Ctools dependencies instead
Looks like Conditional Fields can only be used with CCK. States can be used with any Form API elements.
If you want #states type things in Drupal 6, I'd much rather recommend Ctool's 'Dependent' system which is what was shaped in the states system in D7.
http://drupal.org/project/ctools
I am no drupal expert. And
I am no drupal expert. And before finding this page, i did try conditional field on D7. But I don't think conditional field can check whether a field's value equal to "High school" or not. Am I right?
How does #states handle the #tree attribute
I'm trying to use the #state attribute in my form with a tree=TRUE fieldset. How do I set ':input[name=]' when there is a treed fieldset?
Thanks
Answer in case anyone else needs help.....
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.
$form['ask']['number'] = array(
'#type' => 'textfield',
'#options' => ...
'#states' => array(
'visible' => array( // action to take.
':input[name=ask[so_no]]' => array ('filled' => TRUE),
),
In the input selector you must account for the fieldset by prefacing the name with the fieldset as shown above. Perhaps Randy can add this tidbit to the documentation at http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.....
HTH
Double Quotes Needed
The input selector needs double quotes around the name value to work:
':input[name="ask[so_no]"]' => array ('filled' => TRUE),
For a single checkbox, it would be
':input[name="ask[so_no]"]' => array ('checked' => TRUE),
The examples URL has changed
The link to the examples needs to be updated to: http://d7.drupalexamples.info/examples/form_example/states
Thanks - updated
Thanks - I just updated and think I got all instances.
How to depend on multiple fields properly?
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:
<?phpfunction mymodule_myform() {
$form = array();
$form['yearfrom'] = array(
'#type' => 'textfield',
'#title' => t('Year from'),
);
$form['yearto'] = array(
'#type' => 'textfield',
'#title' => t('Year to'),
);
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#states' => array(
'enabled' => array(
'#edit-yearfrom' => array('filled' => TRUE),
'#edit-yearto' => array('filled' => TRUE),
),
'disabled' => array(
'#edit-yearfrom' => array('empty' => TRUE),
'#edit-yearto' => array('empty' => TRUE),
),
),
);
}
?>
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?
Answered over on http:/
Answered over on http://drupal.org/node/1106388#comment-4269336.
I don't think I can help you do OR. If you have other questions, please provide a sample module (perhaps in a sandbox?)
Preprocess form question
I'm trying to add state in the preprocess form function. Just for testing.
function karapuziki_preprocess_recipe_node_form(&$variables) {
$variables['form']['make_link'] = array(
'#type' => 'checkbox',
'#title' => 'Make link',
);
$variables['form']['in_new_window'] = array(
'#type' => 'checkbox',
'#title' => 'in new window',
'#states' => array('visible' => array(
'input[name="make_link"]' => array('checked' => TRUE),
)),
);
}
This new two fields appear in the form, but both of them are visible and #states conditions doesn't work. I tried to add states conditions to field that already exist in the form
$variables['form']['recipe_source']['#states'] = array(
'visible' => array( // action to take.
':input[name=recipe_yeld]' => array('filled' => TRUE),
),
);
but #states doesn't work too. May be it can't work if I use preprocess hook? Please give me advice :)
Depending on non-form items
Is it possible to have a form element's state depend on value of a non-form element with #states or is this a custom jQuery job?
Probably not
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.
What about GUI forms?
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
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.
Conditional #options ?
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.
Thnx in advance for your time and consideration.
Don't forget about #ajax and multistep forms
Although #states is great for lots of things, it's not as expressive as PHP, so you might have to use #ajax or mutistep forms to get what you want.
I don't think you'll be able to say "If A then one-set-of-things, if B then another-set-of-things, if A AND B, a third-set.
But you can easily make two sub-fieldsets, and everything is easy.
Great Suggestion!
Hi Randy - I greatly appreciate you pointing me in the right direction!
After much tinkering, I was able to get the #ajax to perform the first iteration of what I'm looking for.
I've posted it here for anyone to use or improve:
http://pastebin.com/2YPCyPYK
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.
Many thnx for your help and guidance.
-Ben
Thanks
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.