Forms In Drupal Overview

In this post, we cover Form API of Drupal including form creation, validation and submission.


Creating Form


There is essentially two ways with slight variation on how the form is declared and initiated.
a)Simple Form
Have a module with the moduleName.module containing the following:

/**
* Implements hook_menu().
*/
function MODULENAME_menu() {
$items['fruit/simple'] = array(
'title' => 'Form API examples',
'description' => 'Example of using the Form API.',
'page callback' => 'drupal_get_form',
'page arguments' => array('fruit_simple_form'),
'access callback' => TRUE,
);
return $items;
}

/**
* A simple form.
*/
function fruit_simple_form($form, &$form_submit) {
$form['fruit'] = array(
'#title' => t('Favorite fruit'),
'#type' => 'textfield',
'#required' => TRUE,
'#description' => t('What is your favorite fruit?'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}

The input_menu() function defines the page the end point of ‘fruit/simple’ where the form is going to be rendered (see more on Menu Sys in Drupal at post Add And Manipulate Pages).
The ‘drupal_get_form’ is an internal drupal function that returns a render array containing the form. We later define a function ‘fruit_simple_form’ that builds the render array. This function is passed as an argument to the ‘drupal_get_form’ function in line 9.

The function argument – ‘$&form_state’ (i.e. fruit_simple_form) passed to ‘drupal_get_form’ function will contain values submitted by form.

The ‘#’ symbol at beginning for each form elements are the attributes for the different input elements

If you submit this is not going to be processed till the submission function is declared

b)Creating Embedded Form
This way of creating form is good in situations when you want to add addition code or information around the form. In our example, if wanted to have a text ‘Please, choose your favorite fruit’ in front of form then:

/**
* Implements hook_menu().
*/
function MODULENAME_menu() {
$items['fruit/embedded/simple'] = array(
'title' => 'Simple',
'description' => 'Simple example using a page callback.',
'page callback' => 'fruit_simple_page',
'access callback' => TRUE,
);
return $items;
}


/**
* A simple form.
*/
function fruit_simple_form($form, &$form_submit) {
$form['fruit'] = array(
'#title' => t('Favorite fruit'),
'#type' => 'textfield',
'#required' => TRUE,
'#description' => t('What is your favorite fruit'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}


/**
* Page demonstrating embedding a form on a page.
*/
function fruit_simple_page() {
$build = array(
'header_text' => array(
'#type' => 'markup',
'#markup' => '<p>' . t('Please, choose your favorite fruit') . '</p>',
),
'example_form' => drupal_get_form('fruit_simple_form'),
);
return $build;
}

Here, the ‘fruit_simple_page’ returns the render array to our menu item with items that are going to be rendered to html page. After specifying the text around the form, we add another element on page – ‘example-form’ that is going to construct the form by calling drupal internal function drupal_get_form.

Note on drupal_render()

The drupal_render() is the function that is eventually called to render page. This function can be called on specific parts of our build array(in our case, ‘example_form’ or ‘header_text’). So if we wanted to render the form before passing it to the page callback we could do:

function fruit_simple_page() {
$build = array(
..
'example_form' => drupal_render(drupal_get_form('fruit_simple_form')),
);
return $build;

This would render the from and return to our page call back before all page is rendered. However, we want to retain the array structure of our page callback as long as possible, so the other modules can manipulate as long as they need to.

Drupal Form Validation

Drupal knows to call a validation function in two ways:

1. The name of the render array function or form id plus ‘_validate’ at the end

All functions with ‘_validate’ are searched automatically. We don’t have to register.

/**
* Validation for fruit_simple_form().
*/
function fruit_simple_form_validate($form, &$form_state) {
// Check for the fruit 'orange'.
if ($form_state['values']['fruit'] == 'orange') {
form_set_error('fruit', 'Sorry, your favorite fruit is actually apple.');
}
}
2. Register validation function by adding a function name to a from element ‘validate’

For validating some other form, we have to register our custom validation function or submission as well for that matter. There are multiple hooks to use for altering form with our custom validation function. One most general is using Hook_form_alter() as following:

function MODULE-NAME_form_alter(&$form, &$form_state, $form_id)
{
    switch ($form_id) {
        case 'OUR-FORM-ID':
            //assign validation
            $form['#validate'][] = 'our_custom_validate';
            //assign submition
            $form['#submit'][] = 'our_custom_submit';
            break;
    }
}

Since this is a general hook, we filter the form of our interest via switch statement. Afterwards, we set functions to be called for form validation and submission. In those functions(i.e. our_custom_validate, our_custom_submit) you would put logic for validating the form and form processing

Note On Validation Errors

To set validation error:

function some_validate($form, &$form_state) {
..
if (empty($form_state[values]['text'])) {
                            form_set_error('text', t('Please select an text for layer #'.$layer));
                        }
..
}

Here, we check weather the field ‘text’ is empty. If it is empty then display error message. In addition, it will highlight the form element you specified in our example ‘text’ in function form_set_error(). To find out exact element name(i.e. ‘text’) to pass in form_set_error(), look at the attribute ‘name’ value for the element

Drupal Form Submission

Drupal knows to call a submission function in two ways:
1. The name of form id plus ‘_submit’ this function will be searched automatically. We don’t have to register

function fruit_simple_form_submit($form, &$form_state) {
// Display a message upon successful submission.
drupal_set_message(t("I like @fruit, too!", array('@fruit' =>$form_state['values']['fruit'])));
}

2. Register submission function by adding to the form validate array
In the form build function add the name to handle submission

$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
'#submit' => 'name_of_function_handling_submission',
'#validate' => 'name_of_function_handling_validation',
);

function name_of_function_handling_submission($form, &$form_state){
...
}

function name_of_function_handling_validation($form, &$form_state){
...
}

Here, we first register functions to the submit element(please, see highlighted lines) to handle the submission and validation. Afterwards, those functions are implemented.

Set Variable or Save Node on Form Submission

Most of the time on submission you will save a variable or create new node with some kind of content type. To save variables:

function form_handling_submit($form, &$form_state){
variable_set('var_name',$form['field_some']['#value']);
}

Here, on form submission a value is saved for variable name ‘var_name’

To create new node instance and save it on form submission:

function collect_publication_info_form_submit($form, &$form_state) {
// Create a node object, and add node properties.
$newNode = new stdClass;//or (object) NULL
$newNode->type = 'publication';
$newNode->title = $form['first_name']['#value'].' Publication';
$newNode->language = 'und';//or LANGUAGE_NONE or language code if Locale module is enabled
$newNode->uid = 0;//or any id you wish
node_object_prepare($newNode);

//custom fields of node
$newNode->field_publisher_first_name[LANGUAGE_NONE][0]['value'] = $form['publisher_first_name']['#value'];

$newNode = node_submit($newNode);//prepare and return id
node_save($newNode);

Here a new node instance is created with content type ‘publication’ and saved in database

Explore More Form Elements and Attributes

a) Radios

$form['car'] = array(
'#title' => t('Do you own a car?'),
'#type' => 'radios',
'#options' => array('yes' => t('Yes'), 'no' => t('No')),
'#default_value' => 'yes',
'#required' => TRUE,
);

the key of the ‘#options’ is the value in the form

b) Fieldset with Textfield and Select Example
To encapsulate several other inputs.

// Demonstrating a fieldset.
$form['car_brands'] = array(
'#title' => 'Car brands',
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['car_brands']['ford'] = array(
'#title' => t('Ford'),
'#type' => 'textfield',
'#field_prefix' => t('Capital Letters'),
'#field_suffix' => t('(usa manufactured)'),
'#maxlength' => 3,
'#size' => 3,
);
$form['car_brands']['fiat'] = array(
'#title' => t('Fiat'),
'#type' => 'select',
'#options' => array(
'model1' => t('model 1.'),
'model2' => t('model 2'),
'model3' => t('model 3'),
),
);

By default, all the elements with the fieldset is passed at the same level in that from field array(in our case $form[‘car_brands’]). There is an option for ‘tree’ that will embed each fieldset element as another array within the fieldset array.

c) Help Markup Field
For situation when we want to add some info like a help info to the form without any input

$form['help'] = array(
'#type' => 'markup',
'#markup' => '<p>' . t('For more information about elements and attributes, see the <a href="!url">Form API reference page</a>', array('!url' => url('http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7'))) . '.</p>',
);

The highlighted line contains attribute ‘#type’ that specifies ‘markup’ for any time we want to add html directly to the form. The ‘#markup’ includes the html itself

States Attribute

The state attribute tells the form what different actions is associated with different state

$form['car'] = array(
'#title' => t('Do you own a car?'),
'#type' => 'radios',
'#options' => array('yes' => t('Yes'), 'no' => t('No')),
'#required' => TRUE,
);

// Demonstrating a fieldset.
$form['car_brands'] = array(
'#title' => 'Car Brands',
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#states' => array(
'visible' => array(
':input[name="car"]' => array('value' => 'yes'),
),
),
);

Here, the highlighted lines makes the fieldset visible only if car input has selected value ‘yes’. There is also ‘invisible’ state and you can add more than one condition with multiple value in the array.

In case, you need a condition for text field being filled or not, then:

...
'#states' => array(
'visible' => array(
:input[name="child_first_name"]' => array('filled' => TRUE),
),
),
...

Here the input field ‘child_first_name’ can be of type ‘textfield’, ‘textarea’.

Finding Form ID

Here are two ways to Find Form Id:

1. Put the break points in ‘hook_form_alert(&$form, &$form_state, $form_id)’ and inspect $form_id variable

Drupal during the building process of a form before its rendered goes and looks in any modules for alter functions that manipulates the form at hand. That is one way to find

2. In Firebug, inspect the form element and look for ‘id’ attribute.

The form id is set to it:

<form id="commerce-cart-add-to-cart-form-2-8" class="commerce-add-to-cart commerce-.."

Here the form id is after replacing dashes with underscores commerce_cart_add_to_cart_form_2_8

Enforcing Constraints

Drupal has really good module for enforcing constraints – fapi validation on the user input. To install:

sudo drush dl fapi_validation
drush en fapi_validation

The FAPI Validation module comes with predefined constraints(i.e. ’email’, ‘length’,etc) that you can apply to user input as following:

...
$form['some_field'] = array(
'#type' => 'textfield',
'#title' => 'Some Field',
'#required' => TRUE,
'#rules' => array(
'email',
'length[10, 50]',
),
);
...

Here, we apply email constraint with the length limit as well. Drupal also enables to apply custom constrain defined and declared by you. For more info, please, see FAPI Validation

Redirecting After Submission

Often times you will need to redirect after successful form submission. Here is one of the may ways to redirect:

function some_form_submit($form, &$form_state){
...
$newNode = node_submit($newNode);//prepare and return id
node_save($newNode);
// Display a message upon successful submission.
drupal_set_message(t("Thank You @first_name @last_name for registering and becoming a member!", array('@first_name' =>$form_state['values']['member_first_name'],                                                                              '@last_name' =>$form_state['values']['member_last_name'])));
drupal_goto($destination);

//retrieving destination path to just created node
$destination = drupal_get_path_alias("node/".$newNode->nid);
drupal_goto($destination);
}

Here, we first create an new node – member from the information provided by user and then we redirect to the page of this particular node.

Adding Configuration Fields – hook_block_configure and hook_block_save

By default, every block comes with two fields – title and body, however. It may not be enough for some blocks that you are creating. In that case, there are two hooks – hook_block_configure and hook_block_save both to add additional fields to the block.

The hook_block_configure is used to add additional fields to the block configuration form as following:

function MODULE.NAME_block_configure($delta='') {
$form = array();
switch($delta) {
case 'twitter_feed' :
// Text field form element
$form['builder_twitter_invite'] = array(
'#type' => 'textarea',
'#rows' => '3',
'#title' => t('Enter twitter invite description displayed next to tweets'),
'#default_value' => variable_get('builder_twitter_invite', ''),
);
break;
}
return $form;
}

Here we are adding additional field of type ‘textarea’ with default value retrieved from variable ‘builder_twitter_invite’. This will enable anyone to input the value via the Admin->Blocks and configure UI form

Next, the new field needs to be saved and for that the hook_block_save is used as following:

function MODULE.NAME_block_save($delta = '', $edit = array()) {
switch($delta) {
case 'twitter_feed' :
variable_set('builder_twitter_invite', $edit['builder_twitter_invite']);
break;
}
}

Here, the field is taken from the form(i.e. $edit[‘builder_twitter_invite’]) and saved in the database via the function variable_set()

File Upload With and Without Managed_File

There are two ways to upload file via Drupal Form API. One is with the managed_file functionality and another without it.

1. Without Managed_file

Without Managed_file functionality, the form element is as follows:

function some_form_gen($element, $form_state, $complete_form){
   $element['image'] = array(
        '#type' => 'file',
        '#title' => t('Image'),
        '#description' => t('Upload a file, allowed extensions: jpg, jpeg, png, gif'),
        '#default_value' => variable_get('some_variable_'.$element['#delta'],''),
   );
return $element;
}

The element for file upload is of type – file. Also, the ‘default_value’ is retrieved from Drupal variable that contains the file FID set at the validation or submission as demonstrated next (perhaps, a better practice is to store File FID in the $form_state variable) Next, the file is saved on submission as follows:

function some_form_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors){
if (!empty($item['image'])) {
                    $file = file_save_upload('image', array(
                        // Validates file is really an image.
                        'file_validate_is_image' => array(),
                        // Validate extensions.
                        'file_validate_extensions' => array('png gif jpg jpeg'),
                    ));
                    // If the file passed validation:
                    if ($file) {
                        // Move the file into the Drupal file system.
                        if ($file = file_move($file, 'public://')) {
                            // Save the file for use in the submit handler.
                            $form_state['storage']['image'] = $file;
                        }
                        else {
                            form_set_error('image', t("Failed to write the uploaded file to the site's file folder."));
                        }
                    }
                }
...
}

Here, the file is saved or copied on form validation but it may be a better practice to do it so in save function

Upload File With Managed_File

It differs that with ‘managed_file’ functionality, the file is copied in the destination and the file FID is provided in $element[‘#value’][‘image’] variable.
To create form element:

define('REV_SLIDER_DEST', 'public://slider/revolution');
define('MAX_SIZE_LIMIT_DS', (int)(ini_get('upload_max_filesize')));

function some_form_process($element, $form_state, $complete_form){
...
$element['image'] = array(
        #title' => t('Image'),
        '#type' => 'managed_file',
        '#description' => t('Upload a file, allowed extensions: jpg, jpeg, png, gif'),
        '#default_value' => isset($element['#value']['image']) ? $element['#value']['image'] : '',
        '#upload_location' => REV_SLIDER_DEST.'/img',
        '#upload_validators' => array(
            'file_validate_extensions' => array('jpg jpeg png gif'),
            // Pass the maximum file size in bytes
            'file_validate_size' => array(MAX_SIZE_LIMIT_DS*1024*1024),
        ),
    );

Here the form element is of type ‘managed_file’. The default value is retrieved from the variable $element[‘#value’][‘image’]. Next, all is left to implement validation as following:

function some_form_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors){
...
 if($item['field_remove_item'] == 0){//check if it is no ajax remove taking place when using module field_remove_item 
                    if (!isset($item['image']) || !is_numeric($item['image']) || $item['image'] == 0) {
                        form_set_error('image', t('Please select an image to upload.'));
                    }else{
                        //make file permanent 
                        $file = file_load($item['image']);
                        // Change status to permanent.
                        $file->status = FILE_STATUS_PERMANENT;
                        //all permanent files need an entry in the 'file_usage' table
                        file_usage_add($file, 'MODULE-NAME', $entity_type, $entity->nid);
                        // Save.
                        file_save($file);
                    }
}

Here, the module field_remove_item is used to have ‘Remove’ button for field instances that are more than one. This is implemented as ajax call, so to avoid the general validation, there is check using $item[‘field_remove_item’]. Afterwards, the file FID is checked to see if file is present. At last, the ‘managed_file’ functionality does copy the file into destination location and save the file info in the table ‘file_managed’, however. The saved file entry in the table is saved with status – 0 which is temporary and is removed in some certain time. So to make it permanent, the file is loaded and status set to ‘FILE_STATUS_PERMANENT’. This will ensure the file is permanently saved

Other

1. How to add ‘class’, ‘title’, ‘name’ or any attribute value to a form element?
To add an extra value for ‘class’ attribute, add the following:

$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
'#attributes' => array(
    'class' => array('text'), // change to just 'text' for Drupal 6
    'title'=>'title',
  ),
);

Here the highlighted line would add ‘text’ to the ‘class’ attribute for the submit button

2. How to add placeholder value to the input field?

function ace_form_alter(&$form, &$form_state, $form_id){
if($form_id == 'user_login'){
...
$form['name']['#description'] = '';
$form['name']['#attributes']['placeholder'] = t('Username');
}

Here, for the login form the username input take placeholder value and the description field is removed

Add Color Wheel

You may have input for choosing color. While there are many different ways to add color wheel, here we demonstrate one powered by Spectrum.js
See details post Adding Color Wheel To Form Element In Drupal

Troubleshooting

The file used in the Image field may not be referenced.

This error comes up when the file usage information is not saved in the ‘file_usage’ table at the time the file is made permanent. By adding the following fixes:

file_usage_add($file, 'MODULE-USING-FILE', 'TYPE', 'INSTANCE_ID');

Here, the module using the file is specified with the type(i.e. node, user’, etc.) of instance along the id referencing the file
This also happened when we exported artifacts and saved their FIDs in file_manage table but forgot to update the file_usage table at the same time. As result, when editing the node, we weren’t able to save any more even tho the file was present and functioning as expected

Notice: Undefined index: #field_name in file_managed_file_save_upload()

Ensure the write permissions is set for the destination specified by ‘#upload_location’ attribute

Form Snippets

1. Comment form

$comment = new stdClass;
$comment->nid = $row->nid;
$form = drupal_get_form('comment_form', $comment);

Useful Links

  • For a full list of Form API elements and attributes
  • https://api.drupal.org/api/drupal/modules%21node%21node.module/function/node_save/7
  • http://timonweb.com/how-programmatically-create-nodes-comments-and-taxonomies-drupal-7
  • https://drupal.org/project/fapi_validation
  • http://fourkitchens.com/blog/2012/07/18/building-custom-blocks-drupal-7

Leave a Reply

Your email address will not be published. Required fields are marked *