The Form API
It is supplemented by an API for handling forms with user input. Together, they make up a powerful toolset to to capture, display and navigate the content managed by a Drupal website.
Table of contents
Drupal projects discussed in this chapter: Elements.
Those for D9 are marked "[D9]".
Introduction
In this chapter, you will learn about the Form API, which is a powerful tool to create forms with fields the user may manipulate.
Drupal.org: Introduction to Form API.
Form API
The Form API allows the developer to work with HTML forms as a nested associative array of properties and values. When the entire form has been assembled, Drupal's built-in form engine generates the HTML to render the page that contains the form, perform any form validation specified, and generates code to process the form.
This approach offers the following advantages:
- The developer do not have to write the HTML to render the form, or the PHP to validate and process the form, it is provided as part of the framework.
- The form is internally representated as structured data, not markup. This means that the developer can add, delete, reorder, and change forms before they are rendered. This is handy when the developer, for instance, want to modify a form created by a different module in a clean and unobtrusive way.
- Any form element can be mapped to any theme function.
- Custom form validation and processing can be added to any form.
The Drupal 7 form engine automatically protects forms against form injection attacks (these are attacks where the user submits form data that is malicious, or has been modified in some malicious way).
Form processing in Drupal has three phases: validation, submission, and redirection.
[More TBA]
A simple form
For our first form, we'll create a simple simple form with a single textfield to ask a user for a “magic word”. If the user types in the wrong word, the form validation will fail. If the word is right, the form will be submitted.
The form will be found on the path simpleform
below
the siteroot, and only have two elements: a textinput
field, and a submit
button.
/** * Implements hook_menu(). */ function mymodule_menu() { $items['simpleform'] = array( 'title' => 'A form built with the Form API', 'page callback' => 'drupal_get_form', 'page arguments' => array('mymodule_simpleform'), 'access arguments' => array('access content'), 'file' => 'mymodule.admin.inc', 'type' => MENU_NORMAL_ITEM, ); return $items; } /** * Implements the form callback. */ function mymodule_simpleform() { $form['magic_word'] = array( '#title' => t('Please enter the magic word.'), '#type' => 'textfield', '#description' => t('Hint: The magic word is "cookie"'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit') ); return $form; }
In
hook_menu()
,
do not run the title through t()
. This is to allow the
system to cache data, but also display these strings to users using
various languages on demand. For more about this, see
this page on Drupal.org.
The callback usually resides in a separate file, named as indicated above.
If there is a validation handler and a submit handler, these should be in the same file as the callback.
/** * Validatation handler. */ function mymodule_simpleform_validate( $form, &$form_state) { if ($form_state['values']['magic_word'] != 'cookie') { form_set_error('magic_word', t('Sorry. You did not type the magic word.')); } } /** * Submission handler. */ function mymodule_simpleform_submit($form, &$form_state) { $name = $form_state['values']['magic_word']; drupal_set_message(t('Thanks for giving me a @name!', array('@name' => $name))); }
Notice the names of the validation handler and submission handler functions. These names are simply the name of the form, followed by the strings “_validate” and “_submit” respectively. These names are the default names for these functions, and don't need to be declared.
The example above shows all the four basic functions you need to handle forms. They are:
- A menu hook defining the path to the form.
- A function to define the form's two elements as an associative array.
- A validation handler.
- A submission handler.
$form
.
In the above example, no callback function name for form access control (access callback
) is defined in hook_menu
.
This means that the default function (user_access
) is used. It requires one argument, the permission. This is passed as access arguments
.
In this example, the permission passed is 'access content'
.
Add a custom submission handler to a form
array_unshift($form['#submit'], '_rbg_booking_node_form_submit');
Form elements
Below are examples of some of the most common form elements.
Checkboxes, radio buttons and select list
Single checkbox:
$form['mycheckboxp'] = array( '#type' =>'checkbox', '#title'=>t("Check this box to get a cookie."), '#description'=>t("If not checked, no cookie will be awarded."), '#default_value' => TRUE, );
Multiple checkboxes:
$form['mymultiplecb1'] = array( '#title' => t('Multiple checkboxes'), '#type' => 'checkboxes', '#description' => t('Select all that apply'), '#default_value' => array(1,2), '#options' => array( 'zero', 'one', 'two', 'three', ), );
You might use an explicit key. It may be a string:
$form['mymultiplecb'] = array( '#title' => t('Multiple checkboxes'), '#type' => 'checkboxes', '#description' => t('Select all that apply'), '#default_value' => array('onek','twok'), '#options' => array( 'zerok' => 'zero', 'onek' => 'one', 'twok' => 'two', 'threek' => 'three', ), );
Using an explicit key also works for radio buttons and select lists.
Radio buttons:
$form['myradio'] = array( '#type' => 'radios', '#title' => t('Select one item'), '#description' => t('You should just pick one.'), '#default_value' => variable_get('myradio', 2), '#options' => array( 'zero', 'one', 'two', 'three', ), );
Select list:
$form['myselect'] = array( '#type' => 'select', '#title' => t('Select one item'), '#description' => t('You should just pick one.'), '#default_value' => variable_get('myselect', 2), '#options' => array( 'zero', 'one', 'two', 'three', ), );
File
The form element to upload a file may look like this:
$form['myfile'] = array( '#type' => 'file', '#title' => t('Image'), '#description' => t('Upload a file, allowed extensions: jpg, jpeg, png, gif'), );
Drupal splits files in two categories: managed or unmanaged. The difference between the two lies in the way the files are used.
Managed files work hand in hand with the Entity
system and are in fact tied to File entities. So whenever we
create a managed file, an entity gets created for it as well, which we
can use in all sorts of ways. A record of these file entities are
stored in a table called {file_managed}
. A key aspect of
managed files is the fact that their usage is tracked. This means that
if we attach a manged file to another entity, reference it, or even
manually indicate that we use it, this usage is tracked in a secondary
table called {file_usage}
. This way, we can see where
each file is used and how many times, and Drupal even provides a way
to delete “orphaned” files automatically.
Unmanaged files are not tracked and are not entities. They just live in the file system and can be linked if we know their path.
Processing an uploaded managed or unmanaged file requires custom validation and submit handlers.
To learn how to upload a managed file, see Form example tutorial 10 @ Drupal.org. For an example of how to process managed files without using the Form API, see this tutorial by Patrick J Waters.
The example below shows the scaffolding required to have a form element that let users upload an unmanged file.
First, define the form containing a form element of the type “file” and a submit button.:
/** * Implements hook_form */ function mymodule_form($form, &$form_state) { $form['img_file'] = array( '#type' => 'file', '#title' => t('Select the image to upload'), '#description' => t('Upload a file, allowed extensions: jpg jpeg png gif'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit') ); return $form; }
Provide a custom form submit function:
/** * Custom form submit function. */ function mymodule_form_submit($form, &$form_state) { // We are done with the file, remove it from storage. unset($form_state['storage']['file']); // Set a response to user to provide closure. drupal_set_message(t('The image has been saved.')); }
Most of the heavy lifting is done by the validate function by means of
file_save_upload()
.
Since the uploaded file must be an image, we use the validation function
file_validate_is_image()
to verify that the file is really an image.
We also use
file_validate_extensions()
to add .txt
to uploads with other extensions.
/** * Custom form validate function. */ function mymodule_form_validate($form, &$form_state) { $validators = array( 'file_validate_is_image' => array(), 'file_validate_extensions' => array('jpg jpeg png gif'), ); $file = file_save_upload('img_file', $validators, FALSE, FILE_EXISTS_REPLACE); // If the file passed validation: if ($file) { // Move the file into the Drupal public file system: $publicfile = file_unmanaged_move($file->uri, 'public://', FILE_EXISTS_REPLACE); if (!$publicfile) { form_set_error('file', t('Unable to move uploaded file to the destination.')); } } else { form_set_error('file', t('No file was uploaded.')); } }
Grouping [D9]
The Drupal 7 Form API provides two types for grouping
elements: container
and fieldset
. In Drupal
9, there is also detail
.
The container
type returns HTML to wrap child elements
in a container. It surrounds child elements with
a <div>
and adds attributes such as classes or an
HTML id.
$form['mycontainer] = array( '#type' => 'container', '#attributes' => array('class' => array('container-inline')), '#access' => TRUE, );
The fieldset type wraps the set in a frame and let you organize form elements which need to be grouped logically inside the frame.
$form['mycontainer'] = array( '#type' => 'fieldset', '#title' => t('Title'), '#attributes' => array('class' => array('container-inline')), '#access' => TRUE, );
By default, members of a fieldset is displayed on separate, which makes for a very long page. this answer on Drupal SE explains how to use CSS to display field inputs side-by-side.
See also: Drupal.org: Fieldsets.
Markup
$form['mymarkup'] = array( '#markup' => '<p>Some markup.</p>', );
Numbers
For input of integer and decimal numbers, use a single line textfield.
$form['myinteger'] = array( '#type' => 'textfield', '#title' => t('My integer'), '#default_value' => variable_get('myint', 0), '#size' => 3, '#maxlength' => 5, '#description' => t('You should type an integer.'), );
To be able to use the HTML5 type number
(rendered with stepper arrows by most browsers),
you need to enable the Elements module.
$form['myquantity'] = array( '#type' => 'numberfield', '#title' => t('Quantity'), '#default_value' => 1, '#step' => 2, '#max' => 9, '#min' => 0, );
Text
A single line textfield:
$form['myform'] = array( '#title' => t('Please enter the magic word.'), '#type' => 'textfield', '#description' => t('Hint: The magic word is "cookie"'), );
A multiline textarea:
$form['mytextarea'] = array( '#title' => t('A text area'), '#type' => 'textarea', '#description' => 'Please write your story in the form.', '#default_value' => 'This is my story ...', '#rows' => 5, '#resizable' => TRUE, );
Final word
[TBA]
Last update: 2019-06-21 [gh].