by John Durance
Drupal 7 provides a form API for constructing both simple and dynamic forms. Dynamic forms display their fields based on user interactions with the form – such as selecting an option from a select list. There are really two ways to make a dynamic form:
- by using the #ajax property to rebuild the form, with new or modified fields, as a result of user interaction; or
- by using the #states property to hide or unhide fields as a result of user interaction.
Although the #ajax approach offers more flexibility, I prefer to use the #states property, where possible. The page refresh is faster and it, generally, requires less code. In the simple example, below, the ‘Language’ select list only appears when the ‘Canada’ option is selected from the ‘Country’ select list.
However, there is a problem. If I modify the ‘Language’ field in my example, making it a required field (line 4), the form’s logic will fail.
Why? Because all form elements are validated when a form is submitted – even the invisible ones. Invisible fields will always be empty because the user cannot see them, and by default, if an empty required field is submitted it will fail validation. This puts the user in a situation where they get an error message, every time they try to submit the form, about a field that doesn’t appear on the form.
There are several proposed solutions to solving this problem, but, as far as I know, only one that works fully. Let’s start by looking at the others first.
Limit the validation errors
Before I outline this approach, I must concede that it doesn’t appear to work; however, some propose this solution as the logical way to solve the problem. Two properties are key to this approach: #limit_validation_errors and #element_validate.
The #limit_validation_errors property takes, as its single argument, an array of form elements that should be validated; #element_validate takes an array of validation functions to be called to validate a particular form element (and/or its children).
The logic of this approach follows that, on submission of the form, only the specified elements will be validated by the validation functions that they, themselves, specify. Although, in my testing of this approach, I found that the presence of’#required’ => TRUE, will call the default validation regardless.
Unset the error messages
This approach seeks to directly remove particular error messages before they get set. The function to achieve this comes from this comment. On testing, I found that it worked on the first failed validation, but not on the second (I received empty error messages).
Use custom validation
The general consenus on how to solve this problem seems to be: do not require any fields and create your own custom validation function(s). These are alternate functions to the default form validation function. This is the only approach I have found to work without any problems.
My approach is to remove ‘#required’ => TRUE, from,the form constructor altogether. If you want to theme your fields to look like a required field, you can call theme_form_required_marker and append the returned markup to your field titles. For select lists you may also add ” => t(‘- Select -‘), to your options, then your field will look as if you had required it in the usual way. The final step is to add the #validate property to the appropriate button/submit element. This property takes an array of validation function names. Each function is passed $form and $form_state as
arguments. Such functions should employ form_set_error(), if the form values do not pass validation.
Then use some kind of logic to ensure your validation functions are called only when you know their corresponding elements are visible to the user.
Or…try to use #ajax
If, however, you decide, after reading this, you don’t want to use the #states property
and, instead, decide to go for the #ajax approach, you can try toggling the #required property between TRUE and FALSE depending on the contents of $form_state[input].
In my experience, this approach can be problematic.