
Using Form Alter hooks with Field API Forms
Update: As noted in the comments, there is now a hook in core to alter the widget form so you no longer need to do this two step process. This hook was introduced in Drupal core 7.8 and is called hook_field_widget_form_alter
. Here is the example below, redone using this hook:
<?php
function custom_field_widget_form_alter(&$element, &$form_state, $context) {
// first check the type of the field to ensure only change the required widget
if ($context['field']['field_name'] == 'field_twitter') {
$element['value']['#field_prefix'] = '@';
}
}
?>
Original article:
Customising Drupal forms is easy thanks to hook_form_alter()
, unless you're working with form elements generated by Drupal 7's Field API. It's common to do customisations, add jQuery UI behaviours, or even advanced form item manipulation using a form alter hook, and Drupal makes it easy by giving your custom code access to the full form array and form state via the form alter hooks, but when you're dealing with Field API generated form fields, you will find it's not quite enough. The problem is that at the time the form alter hook is executed, the field API items in the form haven't been fully built yet. The solution to this is straight forward, and involves the use of a callback function that is called once the form build process is complete.
In the code examples below replace the word custom
with your module name. The first step is to define your form alter hook, as you would with any normal form alter operation. You can do this using hook_form_alter() or hook_form_FORMID_alter(). The first hook is called for every form that is built on your site, the second allows you to target individual forms.
This example below performs the simple task of adding a field prefix to a textfield. A text field has been created with the purpose of collecting Twitter IDs. It is required to add an '@' symbol before the text field entry box to indicate that the user should just enter their ID without the '@' symbol. Before altering the form item looks like this:
After the form alter operation, the field will look like this:
The more generic form alter could be useful if you wanted to adapt items across several different forms. The code could look something like this:
<?php
function custom_form_alter(&$form, &$form_state, $form_id) {
if (isset($form['type']) && $form['type']['#value'] . '_node_form'== $form_id) {
$form['field_twitter'][LANGUAGE_NONE][0]['#after_build'][] = '_custom_add_twitter_field_prefix';
}
}
?>
Alternatively, to target a specific form you specify the form ID in the function name, for example to target the user profile form (who's form ID is user_profile_form
) it would look like this:
<?php
function custom_form_user_profile_form_alter(&$form, &$form_state, $form_id) {
$form['field_twitter'][LANGUAGE_NONE][0]['#after_build'][] = '_custom_add_twitter_field_prefix';
}
?>
The '#after_build'
key added to the form array defines a callback function. When the Drupal Form API builds this element of the form array it calls the callback function, passing the element in as an argument. The callback function has to return a new element, or modified version of the argument. Don't expect this callback to work the same as the main form alter hook. In the form alter hook the form is passed in by reference and you modify the $form variable. In this after build callback you have to return a new version of the element.
This example callback adds the field prefix to the twitter field:
<?php
function _custom_add_twitter_field_prefix($element, &$form_state) {
$element['value']['#field_prefix'] = '@';
return $element;
}
?>
Debugging help: If you're having issues with your modifications inside the callback function not getting applied to the form, check the structure of the original form by adding a debug line to the form alter. Something like: debug($form, $form_id, TRUE);
. This will output the full form array, you can use this just to find the keys of the form array: debug(array_keys($form), $form_id, TRUE);
. In the case of these Field API form items notice how the code above makes use of the LANGUAGE_NONE ('und') constant, and then a [0]. This is to take account of the facility in the form array structure for multilingual and multivalue fields.