Feb 01, 2016

Form API in Drupal 7

Let's consider Forms step by step, on the example of our own module.

Create a module called azimut 7_forms, which will create a single page with the address /azimut7_forms available to all visitors.

azimut7_forms.info:

name = Azimut7 forms.
description = Drupal Form API examples.
core=7.x
version = "7.x-1.0"
project = "azimut7_forms"
datestamp = "1332419400"

azimut7_forms.module:

/**
 * Implements hook_menu().
 **/
function azimut7_forms_menu(){
  $items = array();

  $items['azimut7_forms'] = array(
    'title' => 'Page title',
    'page callback' => 'azimut7_forms_callback',
    'type' => MENU_NORMAL_ITEM,
    'access callback' => TRUE, 
  );

  return $items;
}

function azimut7_forms_callback() {
  return 'My forms';
}
 

Turn on the module and go to the /azimut7_forms. If you did everything right, you will see the page:

Now let's create a function that will store the description of the form:

function azimut7_first_form($form, &$form_state){
  $form = array();
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Your name'),
    '#default_value' => t('Tomas'),
  );
  $form['settings'] = array(
    '#type' => 'radios',
    '#title' => t('Your sex'),
    '#options' => array(0 => t('Man'), 1 => t('Woman')),
    '#description' => t('Select your sex.'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

In this function, I specified a form element of type textfield — this is a normal input field, an element of type radios — a group of switches, and the submit button (Submit). The key names of the $form array will be used as the names of the form elements.

So, we have a form with the identifier azimut7_first_form (form ID).

Now in our page rendering function azimut7_forms_callback you can display the form.

$form_1 = drupal_get_form('azimut7_first_form');
$form_1 = drupal_render($form_1);

The $form_1 variable can be returned in the azimut7_forms_callback function. The form will be displayed on the screen.

Our form is working, all the elements are working. Now we need a form handler. To do this, create a function named: <form ID>_submit

In our case it will be:

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

And add the form validator. It is created similarly to the handler, only the function name ends with _validate.

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

To check if the validator and form handler are working, let's install the Devel module and check the form data.

Installing the module via Drush:

drush dl devel && drush en -y devel

Let's display the output of form values:

function azimut7_first_form_validate($form, &$form_state){
  dpm($form_state['values']);
}

function azimut7_first_form_submit($form, &$form_state){
  dpm($form_state['values']);
}

Click Submit. The screenshot shows that the data got into both functions.

You can save data in the handler. Let's do this and immediately print a message:

function azimut7_first_form_submit($form, &$form_state){
  variable_set('my_name', $form_state['values']['name']);
  variable_set('my_sex', $form_state['values']['sex']);
  drupal_set_message(t('Your data saved'));
}

Validation

Next, you can make a check for the length of the name. Suppose we are suitable names consisting of at least three characters.

To do this, add a check in the validator. In form_set_error, the first argument is the form element to highlight and the second is the error text.

function azimut7_first_form_validate($form, &$form_state){
  if (drupal_strlen($form_state['values']['name']) < 3) {
    form_set_error('name', t('Your name is very short.'));
  }
}

Required elements

The field for the sex selection we have optional. Let's fix this situation. To do this, add the #required element to the corresponding form element.

$form['sex'] = array(
  '#type' => 'radios',
  '#title' => t('Your sex'),
  '#options' => array(0 => t('Man'), 1 => t('Woman')),
  '#description' => t('Select your sex.'),
  '#required' => TRUE,
);

Now Drupal will check whether the field is filled or not.

Own form handlers and validators

If you need another validator or handler, you can add them. To do this, add the names of these handlers to the form:

$form['#submit'] = array('azimut7_first_form_submit', 'my_custom_submitter');
$form['#validate'] = array('azimut7_first_form_validate', 'my_sex_validation');

And declare these functions:

function my_custom_submitter($form, &$form_state){

}

function my_sex_validation($form, &$form_state){

}

They will also be called when the form is submitted. In the validator, you can verify that the sex is selected as 1 or 0.

function my_sex_validation($form, &$form_state){
  if (!in_array($form_state['values']['sex'], array(0, 1))) {
    form_set_error('sex', t('Incorrect sex.'));
  }
}

And in the handler, let's just output a message:

function my_custom_submitter($form, &$form_state){
  drupal_set_message(t('It`s custom submitter'));
}

Form field values

Now let's make the previously saved data visible when you open this form. To do this, we'll work with the #default_value of the form elements.

$form['name'] = array(
  '#type' => 'textfield',
  '#title' => t('Your name'),
  '#default_value' => (variable_get('my_name', '') != '') ? variable_get('my_name', '') : t('Tomas')
);
$form['sex'] = array(
  '#type' => 'radios',
  '#title' => t('Your sex'),
  '#default_value' => (variable_get('my_sex', '') != '') ? variable_get('my_sex', '') : null,
  '#options' => array(0 => t('Man'), 1 => t('Woman')),
  '#description' => t('Select your sex.'),
  '#required' => TRUE,
);

When you open the page, our form is already filled out:

Header and Footer for the form

To do this, add the #prefix and #suffix elements directly to the form.

$form['#prefix'] = '<SOME_TAG>';
$form['#suffix'] = '<SOME_TAG>';

You can wrap all form elements. For example, let's make a line:

$form['sex'] = array(
  '#type' => 'radios',
  '#title' => t('Your sex'),
  '#default_value' => (variable_get('my_sex', '') != '') ? variable_get('my_sex', '') : null,
  '#options' => array(0 => t('Man'), 1 => t('Woman')),
  '#description' => t('Select your sex.'),
  '#required' => TRUE,
  '#suffix' => '<hr>'
);

Submitting a form using Ajax

 To send data via Ajax (without reloading the entire page) — it needs to specify special handlers. They are added to the Submit element:

$form['submit'] = array(
  '#type' => 'submit',
  '#value' => t('Submit'),
  '#ajax' => array(
    'callback' => 'azimut7_ajax_callback',
    'wrapper' => 'my-form',
    'method' => 'replace',
    'effect' => 'fade',
  ),
);

The azimut7_ajax_callback function is a form handler. It will be called after all standard handlers are called. And the value it returns is to replace the element with id= "my-form", that is, with the one we specified in the prefix of the entire form.

Create the function azimut7_ajax_callback:

function azimut7_ajax_callback($form, $form_state) {
  return t('Your data saved.');
}

We send the form and see that the content of the entire form is changing:

Theming form

Consider a situation where you already have some complex layout of the form and you need to apply it. For this you need a hook_theme:

function azimut7_forms_theme() {
  return array('azimut7_first_form' => array(
    'render element' => 'form',
    'path' => drupal_get_path('module', 'azimut7_forms') . '/templates',
    'template' => 'azimut7-first-form',
  ),
  );
}

Here I announced that I was going to theme (design) a form with id = azimut7_first_form. The template name will be azimut7-first-form.tpl.php and it is located in the module folder azimut7_forms, in the templates subfolder.

Create a templates directory in the module azimut7_forms and file azimut7-first-form.tpl.php

Let's add some code to it to make sure that the template works.

You can display all the form fields separately in the template. Suppose I have a table layout. In the template code it will look like this:

<?php
  $form["name"]["#title"] = "";
?>
<div class="parent-div">
  <table>
    <tr>
      <td>Your name, please</td>
      <td colspan="2"><?php print render($form["name"]); ?></td>
    </tr>
    <tr>
      <td colspan="3"><?php print render($form["sex"]); ?></td>
    </tr>
    <tr>
      <td></td>
      <td colspan="2"><?php print drupal_render_children($form); ?></td>
    </tr>
  </table>
</div>

I deleted the field header and displayed it elsewhere. And then scattered the form elements in the table cells.