Form API FAQ
Validation
How do you use the #validation arguments?
Example:
#valid => 'integer' , #validation arguments => array(1, 13) This would map to:
valid_integer($form['element'], 1, 13)where 1, 13 could be min/ max allowed values.
If you have multiple validation functions, use:
#valid => array('integer', 'uid'), #validation arguments = array(array(1, 13), array('anonymous'))which would map to: valid_integer($form['element'], 1, 13) and valid_uid($form['element'], 'anonymous') where the second parameter could be role, for instance, so you could see if it's a valid uid in a certain role or '#valid' => 'filename', '#validation_arguments' => 'rwx' where it becomes valid_filename($form, $permissions)
How do you validate a URL, e-mail address or integer value?
Until they are written for core, you would need to write a valid_url, valid_email and valid_integer function.
is the nodeapi 'validate' op to be used anymore given the validation features of the new api? if so, when?
nodeapi validate is for node objects. It gets called by the form api though (node_validate), but it can also be called from code. (programmatically creating nodes)
Form logic
How do you handle multiple submit buttons in a form? Where do you put the 'dispatch logic'?
Examine $_POST['op'] to find the pressed button. It's a good idea to have an execute function for a form, and the dispatch logic can go in there.
What does #after_build do?
#after_build is used to make alterations to the form, after the form has already been processed.
This particularly comes into play during Node Preview. We can't display unfiltered data to the user from the $_POST, so we have a post_process on the node form which adds the node_preview form element using the already filtered values. See Image.module for an example.
Miscellaneous
How do I add a taxonomy selection to my form?
As of Drupal 4.7, you no longer have to do this -- it is done automatically!
So, where you used to have something like:
if (function_exists('taxonomy_node_form')) {
$output = implode('', taxonomy_node_form($node->type, $node));
}You now have... nothing!
Why was the #attribute_name convention chosen?
Observe the following example:
$form['fieldset'] = array('#type' => 'fieldset', '#title' => 'something'); $form['fieldset']['title'] = array('#type' => 'textfield')If we didn't use the # in the beginning, the new title field would override the title property of the fieldset. we tried using defines, which looked nice, but were troublesome, and eventually we went witht the #, because if you look at your html page DOM with your dom inspector, you see all the text is named as '#TEXT', so it seemed like using that for standardization seemed like a good idea.

Weight
Note that form fields don't necessarily appear in the order in which you list them in hook_form. Some common fields have default #weight attributes set to them elsewhere, as follows:
* "Title" set to -5.
* book.module "Parent" set to -4.
* "Body" set to 0.
* book and page.module's "Log message" set to 5.
* book.module "Weight" set to 5.
* "Authoring information" set to 20.
* "Publishing options" set to 25.
* NodeAPI addons default to 30 (see below).
* "Preview" set to 40.
* "Submit" set to 45.
* "Delete" set to 50.
* Renames "User Comments" to "User comments".
* Fixes some leftover $nodeapi (to $extra) in taxonomy.module.
* Weight of the vocabulary is now actually considered.
* Removes "Log message" from story; revised it for page.module.
* Default filter_form weight is 0, not -16.
* menu.module form_alter doesn't collapse if an item exists.
* path.module form_alter doesn't collapse if an item exists.
See http://drupal.org/node/34920#comment-60158 for more on this.
How to actually use form element validation
Okay... the above notes on how to use form validation made no sense to me, and I couldn't get them to work. Same for the notes in the API reference. I posted in the forums about this, and it was suggested there that I post a revised note for the docs here.
Any corrections or clarifications are obviously welcome - I won't be offended if someone points out an error on my part. : )
Anyway, digging through the form validation code, I came up with the following on how to actually use validation on form elements:
You can add a "#validate" tag to various form elements.
It should be added, apparently, in this format:
'#validate' => array('The_function_name' => array('$parameter1, $parameter2[, etc...])),So, basically, #validate should be set to an array, with the keys of the array being function names, and the contents of those array items being arrays of parameters to send (in addition to the form element which is automatically passed as the first parameter.
The function that gets called should be able to accept the form element as the first argument, as well as any further parameters passed. Apparently, it does not need to return anything.
When I did this, I was able to get an arbitrary function to accept parameters without a problem. It did not appear to be forced to follow any naming convention or anything.
Whatever function you choose will have the form element array passed to it as the first parameter, followed by any other parameters specified in the #validate parameter of the form element.
Apparently, the form ignores the output (if any) of the validation function, relying on form_set_error (of course) to determine whether to accept the data or not.
Even seeing how it is done, I can make no sense of the note on the API reference page. Likewise, the note on the Forms API FAQ.
I'm hoping that, if I'm not the only one who had this problem, that this will help.
I'm guessing that, from the lack of comments or discussion on this, either nobody's using this feature, or everyone else understood how to do it. : )
In my example, we create a form element containing a #validate attribute...
(...)$form['myformExample']['myFormElementName'] = array(
'#type' => 'textfield',
'#title' => t('My Integer'),
'#default_value' => variable_get('myFormElementName', 0),
'#size' => 4,
'#maxlength' => 4,
'#description' => t('Enter an Integer in this field.'),
'#required' => FALSE,
'#validate' => array('is_integer_between' => array('myFormElementName', 0)),
);
(...)
The #validate attribute contains an array of items, where the key is the name of a user-defined function to be called, and the value is an array of parameters to send to the function. Keep in mind, the first parameter sent to the function will always be an array containing the form element itself, followed by any of the user-defined parameters.
The example validation function (checks to see if an integer is entered, and optionally if it is above or below a certain value) I created takes the following parameters:
-- $formelement - automatically the first parameter.
-- $fieldname - the name of the field, so that if there's a problem, form_set_error has a field name to work with.
-- $min - the minimum value of the integer
-- $max the maximum value of the integer
function is_integer_between ($formelement, $fieldname, $min=NULL, $max=NULL) {$thevalue = $formelement['#value'];
if (is_numeric($thevalue)) {
$thevalue = $thevalue + 0;
} else {
form_set_error($fieldname, t('Item entered must be an integer.'));
}
if (!is_int($thevalue)) {
form_set_error($fieldname, t('Item entered must be an integer.'));
} else {
if (isset($min) && ($thevalue < $min)) {
form_set_error($fieldname, t("Item entered must be at least $min."));
}
if (isset($max) && ($thevalue > $max)) {
form_set_error($fieldname, t("Item entered must be no higher than $max."));
}
}
}
Additions, clarifications, corrections, etc. would be most welcome.
Get the fieldname without passing a parameter
Rather than passing an arguement '$fieldname' to the validation function you could use the '#name' element already available in the '$formelement' parameter. For example:
<?phpform_set_error($formelement['#name'], t('Item entered must be an integer.'));
?>
However, this doesn't seem to work as expected. If you look at the $formelement['#name'] value it'll be something like edit[myFormElementName]. The form_set_error function doesn't like the whole name (though I think it should), but only the part contained in the brackets. So you can take the name out of the brackets and use that instead like this:
<?php// $formelement['#name'] is equal to edit[whatever][whatever][exampleField]
$fieldname = substr($formelement['#name'],(strrpos($formelement['#name'],'[')+1),-1);
// $fieldname is now equal to exampleField
form_set_error($fieldname, t('Item entered must be an integer.'));
?>
Many thanks to Coyote for the real way to use the #validate parameter.
...
You're correct that it's not needed to pass the element name.
But, fortunately, once you have the element you can simply do:
form_error($formelement, t('Item entered must be an integer.'));form_error() calls form_set_error() for you.
form vs element #validate
When using #validate on a form element, the above text from Coyote is beautiful and correct. The callback function receives $element as the only automatically generated parameter.
If you need to have the contents of the entire form at your disposal while validating, then you need to use #validate in a FORM context rather than in an ELEMENT array. then the parameters which are sent to the validation function include form_id and $edit, just like in the #submit functions.
Good luck,
Campbell
Form email validation
Thanks Coyote.
I managed to get email validation working using your help. Not sure if there's an easier way, but this is how I did it:
In hook_form function, added the form element for email
$form['email'] = array('#type' => 'textfield',
'#title' => t('Email'),
'#default_value' => $object['email'],
'#size' => 60,
'#maxlength' => 128,
'#description' => t('Your email address'),
'#validate' => array('is_valid_email' => array('email'))
);
Then created the validation function using Drupal's built in email validation function (valid_email_address):
function is_valid_email ($formElement, $fieldName) {$emailAddress = $formElement['#value'];
if (!valid_email_address($emailAddress)) {
form_set_error($fieldName, t('Please enter a valid email address.'));
}
}
Charles
www.parkroad.co.za
Small suggestion
This works, except if no email address is submitted. To test a form field which contains an optional email address, use this:
<?phpif ($emailAddress && !valid_email_address($emailAddress)) {
form_set_error($fieldName, t('Please enter a valid email address.'));
}
?>
Since Drupal already ensures that required fields are in fact submitted, this validation function should accept either possibility.
form_set_value
It's probably worth noting here that your validate functions can change the value of the form elements via the form_set_value function.
form_set_value($formelement,$new_value);Form_set_value's first parameter is the form element array exactly as it is passed to your validation function.