Tutorial: Ten Step-by-Step Code Samples for Learning Form API

Last modified: June 20, 2009 - 21:44

This tutorial, for beginning and intermediate Drupal programmers, includes basic and some advanced features of the Form API (example: multistep forms). Intermediate programmers may want to skip down to code sample #8. The tutorial includes the ability to copy and paste the following code snippets into a module for viewing and experimenting.

First, learn how to get the code snippets working. For an existing Drupal 6 site:

  1. Create a new directory in sites/all/modules and name it 'my_module'
  2. Create a file named my_module.info in the my_module directory with the following contents:
  3. name = My module
    description = Module for form api tutorial
    core = 6.x

  4. Create a file and name it 'my_module.module'. Cut and paste the first code sample into the my_module.module file (note that it's preferable to omit the closing ?> tag)
  5. Enable the "My module" module on the admin/build/modules page.
  6. Type the following in the browser address bar: "http://yourhost/my_module/form" or "http://yourhost/?q=my_module/form"
  7. For each code sample in the tutorial, completely replace the code in my_module.module with the new code snippet and type the following in the browser address bar: "http://yourhost/my_module/form" or "http://yourhost/?q=my_module/form".

Note: Throughout the sample code are explanation comments.
This code uses drupal “hooks”, from the drupal API.

Code sample #1:

This is a very basic form which will be expanded upon in the upcoming code samples.

<?php

// This function defines the URL to the page created etc.
// See http://api.drupal.org/api/function/hook_menu/6
function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

// This function gets called in the browser address bar for:
//"http://yourhost/my_module/form" or
//"http://yourhost/?q=my_module/form". It will generate// a page with //this form on it.

function my_module_form() {

 
// This form calls the form builder function via the
  // drupal_get_form() function which takes the name of this form builder
  // function as an argument. It returns the results to display the form.
return drupal_get_form('my_module_my_form');

}

// This function is called the "form builder". It builds the form.
// Notice, it takes one argument, the $form_state
function my_module_my_form($form_state) {
   
   
// This is the first form element. It's a textfield with a label, "Name"
 
$form['name'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Name'),
  );
  return
$form;
}
?>

Code sample #2:

This form adds a "Submit" button--Notice what happens on submission.

<?php

function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

function
my_module_my_form($form_state) {
 
$form['name'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Name'),
  );
 
 
// Adds a simple submit button that refreshes the form and clears its contents -- this is the default behavior for forms.
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
  return
$form;
}
?>

Code sample #3:

Fieldsets are demonstrated here.

<?php

function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

function
my_module_my_form($form_state) {
   
 
// We establish a fieldset element and then place two text fields within
  // it, one for a first name and one for a last name. This helps us group
  //related content.
  //
  // Study the code below and you'll notice that we renamed the array of the first
  // and last name fields by placing them under the $form['name']
  // array. This tells
  // Form API these fields belong to the $form['name'] fieldset.
 
$form['name'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Name'),
  );
 
$form['name']['first'] = array(
   
'#type' => 'textfield',
   
'#title' => t('First name'),
  );
 
$form['name']['last'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Last name'),
  );
 
 
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
  return
$form;
}
?>

Code sample #4:

Demonstrates collapsible fieldsets and basic form validation for required fields. Try submitting the form without entering values.

<?php

function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

function
my_module_my_form($form_state) {
   
   
// To make the fieldset collapsible
 
$form['name'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Name'),
   
'#collapsible' => TRUE, // Added
   
'#collapsed' => FALSE// Added
 
);
 
 
// To make these fields required
 
$form['name']['first'] = array(
   
'#type' => 'textfield',
   
'#title' => t('First name'),
   
'#required' => TRUE, // Added
 
);
 
$form['name']['last'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Last name'),
   
'#required' => TRUE, // Added
 
);
 
 
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
  return
$form;
}
?>

Code sample #5:

Demonstrates how additional attributes can be added to form elements.

<?php

function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

function
my_module_my_form($form_state) {
 
$form['name'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Name'),
   
'#collapsible' => TRUE,
   
'#collapsed' => FALSE,
  );
 
 
// This demonstrates additional attributes of text form fields.
  //
  // For a very useful example of how other form fields work, see
  // http://api.drupal.org/api/file/developer/topics/forms_api.html
  //
  // For a complete listing of all form fields and their attributes, see
  // http://api.drupal.org/api/file/developer/topics/forms_api_reference.html
 
$form['name']['first'] = array(
   
'#type' => 'textfield',
   
'#title' => t('First name'),
   
'#required' => TRUE,
   
'#default_value' => "First name", // added
   
'#description' => "Please enter your first name.", // added
   
'#size' => 20, // added
   
'#maxlength' => 20, // added
 
);
 
$form['name']['last'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Last name'),
   
'#required' => TRUE,
  );
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
  return
$form;
}
?>

Code sample #6:

This adds a new form field and a way to validate it with a validate function, also referred to as a validate handler.

<?php
function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

function
my_module_my_form($form_state) {
 
$form['name'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Name'),
   
'#collapsible' => TRUE,
   
'#collapsed' => FALSE,
  );
 
$form['name']['first'] = array(
   
'#type' => 'textfield',
   
'#title' => t('First name'),
   
'#required' => TRUE,
   
'#default_value' => "First name",
   
'#description' => "Please enter your first name.",
   
'#size' => 20,
   
'#maxlength' => 20,
  );
 
$form['name']['last'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Last name'),
   
'#required' => TRUE,
  );
 
 
// New form field added to permit entry of year of birth.
  // Tthe data entered into this field will be validated with
  // the default validation function.
 
$form['year_of_birth'] = array(
   
'#type' => 'textfield',
   
'#title' => "Year of birth",
   
'#description' => 'Format is "YYYY"',
  );
 
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
  return
$form;
}

// This adds a handler/function to validate the data entered into the
// "year of birth" field to make sure it's between the values of 1900
// and 2000. If not, it displays an error. The value report is // $form_state['values'] (see http://drupal.org/node/144132#form-state).
//
// Notice the name of the function. It is simply the name of the form
// followed by '_validate'. This is the default validation function.
function my_module_my_form_validate($form, &$form_state) {
 
$year_of_birth = $form_state['values']['year_of_birth'];
  if (
$year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
   
form_set_error('year_of_birth', 'Enter a year between 1900 and 2000.');
  }
}
?>

Code sample #7:

This adds the submit function, also referred to as a submit handler.

<?php

function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

function
my_module_my_form($form_state) {
 
$form['name'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Name'),
   
'#collapsible' => TRUE,
   
'#collapsed' => FALSE,
  );
 
$form['name']['first'] = array(
   
'#type' => 'textfield',
   
'#title' => t('First name'),
   
'#required' => TRUE,
   
'#default_value' => "First name",
   
'#description' => "Please enter your first name.",
   
'#size' => 20,
   
'#maxlength' => 20,
  );
 
$form['name']['last'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Last name'),
   
'#required' => TRUE,
  );
 
$form['year_of_birth'] = array(
   
'#type' => 'textfield',
   
'#title' => "Year of birth",
   
'#description' => 'Format is "YYYY"',
  ); 
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
  return
$form;
}

function
my_module_my_form_validate($form, &$form_state) {
   
$year_of_birth = $form_state['values']['year_of_birth'];
    if (
$year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
       
form_set_error('year_of_birth', 'Enter a year between 1900 and 2000.');
    }
}

// Adds a submit handler/function to our form to send a successful
// completion message to the screen.


function my_module_my_form_submit($form, &$form_state) {
   
drupal_set_message(t('The form has been submitted.'));
}
?>

Code sample #8:

This creates a new button to reset the form and demonstrates how to give the button its own validation handler. Also, it places validation of the first and last name fields into the default validation form.

<?php

function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

function
my_module_my_form($form_state) {
 
$form['name'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Name'),
   
'#collapsible' => TRUE,
   
'#collapsed' => FALSE,
  );
 
 
// Removes the #required property and
  // uses the validation function instead.
 
$form['name']['first'] = array(
   
'#type' => 'textfield',
   
'#title' => t('First name'),
   
'#default_value' => "First name",
   
'#description' => "Please enter your first name.",
   
'#size' => 20,
   
'#maxlength' => 20,
  );
 
 
// Removes the #required property and
  // uses the validation function instead.
 
$form['name']['last'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Last name'),
  );
 
$form['year_of_birth'] = array(
   
'#type' => 'textfield',
   
'#title' => "Year of birth",
   
'#description' => 'Format is "YYYY"',
  ); 
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
 
// Adds a new button to clear the form. The #validate property
  // directs the form to use a new validation handler function in place
  // of the default.
 
$form['clear'] = array(
   
'#type' => 'submit',
   
'#value' => 'Reset form',
   
'#validate' => array('my_module_my_form_clear'),
  );
 
  return
$form;
}

// This is the new validation handler for our Reset button. Setting
// the $form_state['rebuild'] value to TRUE, clears the form and also
// skips the submit handler.
function my_module_my_form_clear($form, &$form_state) {
   
$form_state['rebuild'] = TRUE;
}

// Provides the first and last name fields to be required by using the
// validation function to make sure values have been entered. This
// causes the name fields to show up in red if left blank after clearing
// the form with the "Reset form" button.
function my_module_my_form_validate($form, &$form_state) {
   
$year_of_birth = $form_state['values']['year_of_birth'];
   
$first_name = $form_state['values']['first'];
   
$last_name = $form_state['values']['last'];
    if (!
$first_name) {
       
form_set_error('first', 'Please enter your first name.');
    }
    if (!
$last_name) {
       
form_set_error('last', 'Please enter your last name.');
    }
    if (
$year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
       
form_set_error('year_of_birth', 'Enter a year between 1900 and 2000.');
    }
}

function
my_module_my_form_submit($form, &$form_state) {
   
drupal_set_message(t('The form has been submitted.'));
}
?>

Code sample #9:

This sample adds a third button which will add another set of 'name' and 'year of birth' fields to our form. Validation is created for these new fields as well. Use $form_state['storage'] to keep track of the fact that this new button has been pressed.

<?php

function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

function
my_module_my_form($form_state) {
 
$form['name'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Name'),
   
'#collapsible' => TRUE,
   
'#collapsed' => FALSE,
  );
 
 
// Change/add the #default_value for the first name, last name, and
// birth year to show their old values so when the "Add another name"
//button is clicked, they will retain their values.
 
$form['name']['first'] = array(
   
'#type' => 'textfield',
   
'#title' => t('First name'),
   
'#default_value' => $form_state['values']['first'], // changed
   
'#description' => "Please enter your first name.",
   
'#size' => 20,
   
'#maxlength' => 20,
  );
 
$form['name']['last'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Last name'),
   
'#default_value' => $form_state['values']['last'], // added
 
);
 
$form['year_of_birth'] = array(
   
'#type' => 'textfield',
   
'#title' => "Year of birth",
   
'#description' => 'Format is "YYYY"',
   
'#default_value' => $form_state['values']['year_of_birth'], // added
 
);
 
 
// Add new elements to the form
 
if (isset($form_state['storage']['new_name'])) { // This value is set after
                                                   // "Add new name" button
                                                   // is clicked.
     
$form['name2'] = array(
       
'#type' => 'fieldset',
       
'#title' => t('Name #2'),
       
'#collapsible' => TRUE,
       
'#collapsed' => FALSE,
      );
     
$form['name2']['first2'] = array(
       
'#type' => 'textfield',
       
'#title' => t('First name'),
       
'#description' => "Please enter your first name.",
       
'#size' => 20,
       
'#maxlength' => 20,
       
'#default_value' => $form_state['values']['first2'],
      );
     
$form['name2']['last2'] = array(
       
'#type' => 'textfield',
       
'#title' => t('Last name'),
       
'#default_value' => $form_state['values']['last2'],
      );
     
$form['year_of_birth2'] = array(
       
'#type' => 'textfield',
       
'#title' => "Year of birth",
       
'#description' => 'Format is "YYYY"',
       
'#default_value' => $form_state['values']['year_of_birth2'],
      );
    }
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
 
$form['clear'] = array(
   
'#type' => 'submit',
   
'#value' => 'Reset form',
   
'#validate' => array('my_module_my_form_clear'),
  );
 
 
// Adds "Add another name" button, if it hasn't already been clicked.
 
if (empty($form_state['storage']['new_name'])) {  // This value is set
                                                    // after "Add new name"
                                                    // button is clicked.
     
$form['new_name'] = array(
       
'#type' => 'submit',
       
'#value' => 'Add another name',
       
'#validate' => array('my_module_my_form_new_name'),
      );
    }
 
  return
$form;
}

//Creating a new element, 'new_name' in the $form_state['storage'] array
// sets the value used to determine whether to show the new
// fields on the form and hide the "Add another name" button.
function my_module_my_form_new_name($form, &$form_state) {
   
$form_state['storage']['new_name'] = TRUE;
   
$form_state['rebuild'] = TRUE; // Calling this explicitly will cause the
                                   // default submit function to be skipped
                                   // and the form to be rebuilt.
}

function
my_module_my_form_clear($form, &$form_state) {
    unset (
$form_state['values']);  // ensures fields are blank after reset
                                    // button is clicked
   
unset ($form_state['storage']); // ensures the reset button removes the
                                    // new_name part

   
$form_state['rebuild'] = TRUE; // Calling this explicitly will cause the
                                   // default submit function to be skipped
                                   // and the form to be rebuilt.
}

// Adds logic to validate the form to check the validity of the new fields,
// if they exist.
function my_module_my_form_validate($form, &$form_state) {
   
$year_of_birth = $form_state['values']['year_of_birth'];
   
$first_name = $form_state['values']['first'];
   
$last_name = $form_state['values']['last'];
    if (!
$first_name) {
       
form_set_error('first', 'Please enter your first name.');
    }
    if (!
$last_name) {
       
form_set_error('last', 'Please enter your last name.');
    }
    if (
$year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
       
form_set_error('year_of_birth', 'Enter a year between 1900 and 2000.');
    }
    if (
$form_state['storage']['new_name']) {
       
$year_of_birth = $form_state['values']['year_of_birth2'];
       
$first_name = $form_state['values']['first2'];
       
$last_name = $form_state['values']['last2'];
        if (!
$first_name) {
           
form_set_error('first2', 'Please enter your first name.');
        }
        if (!
$last_name) {
           
form_set_error('last2', 'Please enter your last name.');
        }
        if (
$year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
           
form_set_error('year_of_birth2', 'Enter a year between 1900 and 2000.');
        }
    }
}

// Commenting out the line with the unset() function and
// then adding a new set of name fields and submitting the form,
// causes the form to no longer clear itself. The reason is that when
// the 'storage' value is set, the $form_state['rebuild'] value will get
// set to true  causing the form fields to get rebuilt with the
// values found in $form_state['values'].
function my_module_my_form_submit($form, &$form_state) {
    unset(
$form_state['storage']);
   
drupal_set_message(t('The form has been submitted.'));
}
?>

Code sample #10:

This sample creates a "multistep" form with two pages.

<?php
function my_module_menu() {
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_CALLBACK,
  );
  return
$items;
}

function
my_module_form() {
  return
drupal_get_form('my_module_my_form');
}

// Adds logic to our form builder to give it two pages. It checks a
// value in $form_state['storage'] to determine if it should display page 2.
function my_module_my_form($form_state) {
 
// Display page 2 if $form_state['storage']['page_two'] is set
 
if (isset($form_state['storage']['page_two'])) {
    return
my_module_my_form_page_two();
  }
 
// Page 1 is displayed if $form_state['storage']['page_two'] is not set
 
$form['name'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Name'),
   
'#collapsible' => TRUE,
   
'#collapsed' => FALSE,
  );
 
$form['name']['first'] = array(
   
'#type' => 'textfield',
   
'#title' => t('First name'),
   
'#default_value' => $form_state['values']['first'], // changed
   
'#description' => "Please enter your first name.",
   
'#size' => 20,
   
'#maxlength' => 20,
  );
 
$form['name']['last'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Last name'),
   
'#default_value' => $form_state['values']['last'], // added
 
);
 
$form['year_of_birth'] = array(
   
'#type' => 'textfield',
   
'#title' => "Year of birth",
   
'#description' => 'Format is "YYYY"',
   
'#default_value' => $form_state['values']['year_of_birth'], // added
 
);
 
// Add new elements to the form
 
if (!empty($form_state['storage']['new_name'])) {
   
$form['name2'] = array(
     
'#type' => 'fieldset',
     
'#title' => t('Name #2'),
     
'#collapsible' => TRUE,
     
'#collapsed' => FALSE,
    );
   
$form['name2']['first2'] = array(
     
'#type' => 'textfield',
     
'#title' => t('First name'),
     
'#description' => "Please enter your first name.",
     
'#size' => 20,
     
'#maxlength' => 20,
     
'#default_value' => $form_state['values']['first2'],
    );
   
$form['name2']['last2'] = array(
     
'#type' => 'textfield',
     
'#title' => t('Last name'),
     
'#default_value' => $form_state['values']['last2'],
    );
   
$form['year_of_birth2'] = array(
     
'#type' => 'textfield',
     
'#title' => "Year of birth",
     
'#description' => 'Format is "YYYY"',
     
'#default_value' => $form_state['values']['year_of_birth2'],
    );
  }
 
$form['clear'] = array(
   
'#type' => 'submit',
   
'#value' => 'Reset form',
   
'#validate' => array('my_module_my_form_clear'),
  );
  if (empty(
$form_state['storage']['new_name'])) {
   
$form['new_name'] = array(
     
'#type' => 'submit',
     
'#value' => 'Add another name',
     
'#validate' => array('my_module_my_form_new_name'),
    );
  }
 
$form['next'] = array(
   
'#type' => 'submit',
   
'#value' => 'Next >>',
  );
  return
$form;
}

// New function created to help make the code more manageable
function my_module_my_form_page_two() {
 
$form['color'] = array(
   
'#type' => 'textfield',
   
'#title' => 'Favorite color',
  );
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
  return
$form;
}

function
my_module_my_form_new_name($form, &$form_state) {
 
$form_state['storage']['new_name'] = TRUE;
 
$form_state['rebuild'] = TRUE; // Will cause the default submit function
                                 // to be skipped.
}

function
my_module_my_form_clear($form, &$form_state) {
  unset (
$form_state['values']);  // ensures fields are blank after reset
                                  // button is clicked
 
unset ($form_state['storage']); // ensures the reset button removes the
                                  // new_name part

 
$form_state['rebuild'] = TRUE;
}

// Modifies function so it can validate page 2
function my_module_my_form_validate($form, &$form_state) {
 
// Validate page 2 here
 
if (isset($form_state['storage']['page_two'])) {
   
$color = $form_state['values']['color'];
    if (!
$color) {
     
form_set_error('color', 'Please enter a color.');
    }
    return;
  }
 
 
$year_of_birth = $form_state['values']['year_of_birth'];
 
$first_name = $form_state['values']['first'];
 
$last_name = $form_state['values']['last'];
  if (!
$first_name) {
   
form_set_error('first', 'Please enter your first name.');
  }
  if (!
$last_name) {
   
form_set_error('last', 'Please enter your last name.');
  }
  if (
$year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
   
form_set_error('year_of_birth', 'Enter a year between 1900 and 2000.');
  }
  if (
$form_state['storage']['new_name']) {
   
$year_of_birth = $form_state['values']['year_of_birth2'];
   
$first_name = $form_state['values']['first2'];
   
$last_name = $form_state['values']['last2'];
    if (!
$first_name) {
     
form_set_error('first2', 'Please enter your first name.');
    }
    if (!
$last_name) {
     
form_set_error('last2', 'Please enter your last name.');
    }
    if (
$year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
     
form_set_error('year_of_birth2', 'Enter a year between 1900 and 2000.');
    }
  }
}

// Modifies this function so that it will respond appropriately based on
// which page was submitted. If the first page is being submitted,
// values in the 'storage' array are saved and the form gets
// automatically reloaded.
// If page 2 was submitted, we display a message and redirect the
// user to another page.
function my_module_my_form_submit($form, &$form_state) {
 
// Handle page 1 submissions
 
if ($form_state['clicked_button']['#id'] == 'edit-next') {
   
$form_state['storage']['page_two'] = TRUE; // We set this to determine
                                               // which elements to display
                                               // when the page reloads.
                                              
    // Values below in the $form_state['storage'] array are saved
    // to carry forward to subsequent pages in the form.
   
$form_state['storage']['page_one_values'] = $form_state['values'];
  }
 
// Handle page 2 submissions
 
else {
   
/*
     Normally, some code would go here to alter the database with the data
     collected from the form. Sets a message with drupal_set_message()
     to validate working code.
     */
   
drupal_set_message('Your form has been submitted');
    unset (
$form_state['storage']); // This value must be unset for
                                    // redirection! This is because
                                    // $form_state['rebuild'] gets set to TRUE
                                    // when 'storage' is set. See code sample
                                    // #9 for more on this.
                                   
   
$form_state['redirect'] = 'node'; // Redirects the user.
 
}
}
?>

6.5 example of setting values for forms on second view?

peterx - October 21, 2008 - 07:48

#default_value sets a one off value for a form when a form is created but does nothing for a form after the submit button is hit. In 6.5, following the documentation for Drupal 6 Forms API, I get empty fields in forms after I hit submit and return to the same form. What I would like is a explained example of taking the values returned from the submission of the form to make them appear back in the same form, not on a second page of a multipage form.

The fields are not going into a node because the node is not created until the form is complete. I can display the returned values in messages but they never get back to the form. I went through the 5 - 6 differences and could not find an appropriate change. '#default_value' => $form_state['values']['first'] does nothing for returning to the same form. I suspect the Drupal 6 Forms process is following a new route and some vital bit is not yet in the documentation. A working example with an explanation of the data movement would be a great help.

I agree that this is very

moshe weitzman - November 24, 2008 - 02:33

I agree that this is very awkward. i did see that '#default_value' => $form_state['values']['first'] works fine.

How to "Point your browser to my_module/form" ?

wanneng - November 25, 2008 - 14:20

Sorry ,I dont know How to "Point your browser to my_module/form to run the sample code"?
I can not find it.
wanneng

How to "Point your browser to my_module/form" ?

loets - November 28, 2008 - 10:40

I also struggled with this. The solution for me was to put the module folder in the folder where all the system modules are (i.e. [root]/modules instead of the path suggested in this guide).

How to "Point your browser to my_module/form" ?

mscado - December 1, 2008 - 07:30

I also put the module in the same directory as all the systems module. and I also don't know what you mean by pointing my browser to my_module/form.

In my case, they directory I put it in is:
C:\www\vhosts\localhost\_\drupal\modules\my_module
I went into the administration and was able to enable the my_module module.

But when I went to
http://localhost/drupal/admin/build/modules/my_module/form

no sample forms show up.

I am brand new to Drupal. Thanks!

msCado

How to "Point your browser to my_module/form" ?

mscado - December 1, 2008 - 07:34

Oops...I figured it out.

It's suppose to be - http://localhost/drupal/my_module/form

Thanks for the tutorial. :)

msCado

Browse Where?

jgoodwill01 - January 22, 2009 - 17:59

Currently we don't have Clean URLs on our site.

I'm running 6.x and I can't seem to access the tutorial page.

.../index.php?q=my_module/form
.../?q=my_module/form
/my_module/form

all do not work, I keep getting a page cannot be found. Is there something else that anyone suggests I try? I'm really trying to learn the forms API but it's taking more than I thought it would to actually sink in.

Thanks!

File Name

jgoodwill01 - January 23, 2009 - 14:13

After using the #drupal_support IRC I learned that it is important that the filename of both the modaule and the info file be named exactly like the functions used in _hook. I had named my files "mymodule.module" as opposed to "my_module.module". After correcting the file names I had no trouble getting the pages to load. If you are receiving a "Page Not Found" no matter what you try check the file names.

reagrding the redirecting form

shirishab - December 16, 2008 - 10:09

hi,

i am using the same code in example 10. in my form i am having four steps ans it s working fine.now the problem is after submitting form i want to redirect the form to 3rd step of with the same session id. how can we do that do you have any idea regarding this can you please help me reagrding this issue

Thanks
shirisha

got stuck in the initial example

ivom - January 22, 2009 - 20:18

I got stuck in the initial lines of this tutorial like some others here.

Try this http://yourhost/drupal-6.9/?q=my_module/form.

Don't know if this was supposed to be the solution, but this runs the form.

I have battled with this for

martinquested - December 16, 2008 - 17:16

I have battled with this for hours. In my case, I was finally able to get it to work by using something like '#default_value' => $form_state['values']['first'] in the form, and $form_state['rebuild'] = TRUE; in the submit handler. This is a shockingly badly documented part of Drupal! I still don't feel I understand it well enough to write any documentation myself, otherwise I would.

regarding form redirect

shirishab - December 17, 2008 - 17:27

Hi,

Thanks its working . i had one more problem we have two users one is mysql and one is mssql.
mysql users will login on ip based and mssql users also logging but as admin but they should not login as admins . we are inserting the users with webservices in to mssql DO you have any idea regarding this.
can you please help me regarding this issue

Thanks & Regards
shirisha

how to previous button in multistep form

shirishab - March 23, 2009 - 03:43

Hi,

I am having four steps in form.if i want to give backforth option for user.would you please tell me how to give back button in form.can you give one example for that.

Thanks
shirisha

regarding back buton in registration form

shirishab - March 24, 2009 - 11:07

hi,

i am using the example 10 model for multistep form.

i have four steps in my form can any one tell how to give back buttton so that form will go back and forth .

Thanks & Regards
shirisha

various comments on this good tutorial

cquezel - January 21, 2009 - 14:20

Comments on the tutorial

If you are a newbie to Drupal, (like me) you probably want to know the following things about this tutorial:
There is a discussion on why the closing “?>” may be omitted here: http://ca.php.net/basic-syntax.instruction-separation
In the first section,
“ Enable the "My module" module on the admin/build/modules page.”
Should read:
“ Enable the "My module" module on the administer/site building/modules page.”

When reading: Point your browser to my_module/form to run the sample code, you should probably know that this is done by:
http://yoursite_site_url/?q=my_module/form or http://yoursite_site_url/my_module/form depending on your configuration.

On example 1

The 'name' key has no special meaning and any value may be used. This key value is used to generate ids and names in the generated html.
'textfield' is just the Drupal way of saying a html text input.
Drupal caches some information on modules menus. For example, if you want to change my_module_menu to give a new title to the form, you will have to disable and enable your module before refreshing to see this change.
Wrapping Titles and descriptions in t() is not required as of v6: http://drupal.org/node/140311
More info on menu here: http://drupal.org/node/102338
More info on form API here: http://api.drupal.org/api/file/developer/topics/forms_api.html/6
Where is the 'description' value used?

On example 6

Note that the validation, as expressed, allows an empty value for the 'year_of_birth' field.
The example introduces the 'value' key for $form_state other keys are documented here: http://drupal.org/node/144132
Drupal has functions to help form designers and date_validate is one out of the bunch: http://api.drupal.org/api/group/form_api/6
It would probably be a good thing to express the error message in terms of the field title (i.e. $form['year_of_birth']['#title']).
The '#validate' property may be used to explicitly register form validation handlers to forms and buttons: http://api.drupal.org/api/file/developer/topics/forms_api_reference.html...
The '#element_validate' may be used to explicitly register element validation handlers to any element: http://api.drupal.org/api/file/developer/topics/forms_api_reference.html...
The difference between '#validate' and '#element_validate' is described here: http://drupal.org/node/169815

On example 7

The '#submit' property may be used to explicitly register form submit handler to forms and buttons: http://api.drupal.org/api/file/developer/topics/forms_api_reference.html...

On example 9

The example introduces the 'storage' key for $form_state see: http://drupal.org/node/144132
I’m not so sure it is a good design decision to move the field validations in the form validation in this case.

Does validation work on multistep form?

newbuntu - January 10, 2009 - 17:39

I think the validation function counts on it being called more than once, so it can validate the 2nd page.

But I believe drupal form has a bug that it skips the 2nd (or more) validations. http://drupal.org/node/260934

Has anyone tried the multistep (example 10) form? does the validation work? What's the best work around?

menu item to form page

VinceW - January 30, 2009 - 10:31

A few readers had some problems finding the correct url to the new form.

if you enter this snippet of code on top of the module, you will get a new menu entry, from where you can reach the form. (I adapted the code on: http://drupal.org/node/206761 a bit to fit this tutorial. Search on that page for: Add the page to hook_menu).

<?php
function my_module_menu() {
 
$items = array();
 
$items['my_module/form'] = array(
   
'title' => t('My form'),
   
'page callback' => 'my_module_form',
   
'access arguments' => array('access content'),
   
'description' => t('My form'),
   
'type' => MENU_NORMAL_ITEM,
  );
  return
$items;
}
?>

After reloading/refreshing the admin -> modules the menu-item will show up.

Hope it's useful for someone
VinceW

-=[ Your Information Matters ]=-

Great Tutorial

eglon - February 19, 2009 - 17:45

Thank you, this is a really good tutorial. Keep up the good work:)

Christian

Set the Menu

CJOKRAYNER - February 24, 2009 - 17:30

I found this more by mistake.

Create a menu item in admin/build/menu with the path set to "my_module/form" (Leaving out the quotation marks)

Job Done.

Pip Pip

tutorial #9 issue

dazmcg - March 7, 2009 - 17:48

hi! great tutorial, but I'm having an problem which I think is related to the fact that I'm calling the form using the node/add method? If this is so, can it be explained?

I get this error when I use the drupal_get_form() method:
warning: array_merge_recursive() [function.array-merge-recursive]: Argument #2 is not an array in /home/web/active/drupal-6.10/modules/node/node.pages.inc on line 134.

My module can be viewed here:
http://home.imprison.me.uk/t/test1.module.txt

Any tips?

Cheers!

Using this tutorial to create a form for a CCK node type?

dazmcg - March 8, 2009 - 15:57

If I want to take this tutorial and add on additional functionality so that the form and its elements maps to a CCK type with fields like 'first name', 'last name' and 'year of birth', which when submitted, will create a new node of the cck type, injecting the values from the form into the new node?

Building on this, if I have CCK field types which have particular widgets attached (eg: tags, which have the autocomplete functionality), how would I expose this in my form?

Thanks!

structure of CCK data types

Neil_in_Chicago - March 23, 2009 - 05:08

There's a lot more to CCK than meets the eye.
I've been working with some CCK fields, and a couple
var_dump($form);
var_dump($form_state);
show more complexity than I was expecting!

Here's the best documentation I've been able to find (note how little of it is drupal.org!):
CCK 2 Documentation | groups.drupal.org
What is the Content Construction Kit? A View from the Database. | Lullabot
CCK Documentation Resources | CCK API
API reference | CCK API
Database abstraction layer | CCK API
Unraveling the mysteries of the cck (and the cck docs) | The Empowerment
Updating CCK Modules from 5.x to 6.x | drupal.org

Multistep forms refuse to set default values of checkboxes

seanr - May 4, 2009 - 20:47

Here's a workaround in a multistep node form:

<?php
/**
* Implementation of hook_form_alter().
*
* When first adding the node, put the 'Title' field on its own page in a
* multistep node form.  We need to do this in hook_form_alter() itself
* instead of a form_id-specific form alter so that we're likely to come after
* everyone else's alterings, e.g. taxonomy, menu, etc.
*/
function example_form_alter(&$form, $form_state, $form_id) {
 
$type = node_get_types('type', $node);
  if (
$form_id == 'example_node_form') {
    if (
arg(1) != 'add') {
     
$node = $form['#node'];
    }
   
    if (isset(
$node->section)) {
     
$section = $node->section;
    }
    elseif (isset(
$form_state['section'])) {
     
$section = $form_state['section'];
    }
    elseif (isset(
$form_state['values']['section'])) {
     
$section = $form_state['values']['section'];
    }
   
    if (empty(
$section)) {
      [
step 1 fields here]
    }
    else {
      [
step 2 fields here]
     
     
// Add a submit handler to copy the version values from
      // $form_state['storage'] to $form_state['values']
     
$form['#submit'][] = 'crmapi_node_form_submit';
     
// For the actual submit button, add a handler to clear out
      // $form_state['storage'] entirely so that we actually submit the form.
     
$form['buttons']['submit']['#submit'][] = 'crmapi_node_form_final_submit';
     
      if (
arg(1) == 'add') {
       
// Fix FUBAR Drupal handling of chewckboxes in multistep forms
       
_example_form_checkboxes($form);
      }
    }
  }
}


/*
* Helper function to step through form elements and fix default
* values for checkboxes.
*/
function _example_form_checkboxes(&$form) {
  foreach (
element_children($form) as $child) {
    if (
$form[$child]['#type'] == 'checkbox') {
      if (
$form[$child]['#default_value'] == TRUE) {
       
$form[$child]['#attributes'] = array('checked' => 'checked');
      }
    }
    elseif (
$form[$child]['#type'] == 'fieldset') {
      foreach (
element_children($form[$child]) as $grandchild) {
        if (
$form[$child][$grandchild]['#type'] == 'checkbox') {
          if (
$form[$child][$grandchild]['#default_value'] == TRUE) {
           
$form[$child][$grandchild]['#attributes'] = array('checked' => 'checked');
          }
        }
      }
    }
  }
}
?>

It's really not recursive, but it works well enough for the standard node forms (I don;t think I've seen too many node forms with nested fieldsets)

Do not translate in hook_menu

advseb - May 16, 2009 - 09:46

The documentation of hook_menu (http://api.drupal.org/api/function/hook_menu/6) says that the title and description property must be untranslated. So you should remove the t() function in your example.

great tutorial!

nikitas - May 31, 2009 - 19:36

i followed your code and i must say that you made it look very easy . . . but now i'm stuck with other issues and i didnt found any answers at the comments of your post.So what i want to ask you is this. . .
how does the form api connects with the taxonomy module and takes data and submit them through the database shema api?It would be enough only to submit the code and nothing more with a title.I believe that this will do it!thanx again!

Code example 10

owenmc - June 1, 2009 - 05:39

Is the expression $form_state['clicked_button'][#id] == 'edit-next' correct? Should it actually be: $form_state['clicked_button'][#id] == 'next' or $form_state['clicked_button'][#value] == 'Next >>' ? I'm confused.

Unable to insert data from first page into a database

jkiwanuka - June 10, 2009 - 14:28

I have a form based on the above tutorial. I've managed to get it working with all the code. But when I try to submit the data to a database, it only inserts the data from the second page.

the code snippet I have is:

<?php
// Handle page 1 submissions
 
if ($form_state['clicked_button']['#id'] == 'edit-next') {
   
$form_state['storage']['page_two'] = TRUE; // We set this to determine
                                               // which elements to display
                                               // when the page reloads.
                                             
    // We save our values below in the $form_state['storage'] array so they
    // will carry forward to subsequent pages in the form.
   
$form_state['storage']['page_one_values'] = $form_state['values'];
  }
 
// Handle page 2 submissions
 
else {
   
/*
     Normally, some code would go here to alter the database with the data
     collected from the form. Here we just set a message with drupal_set_message()
     so you know the code is working.
     */
    
    
    //drupal_set_message('Your form has been submitted');
   
db_query("INSERT INTO {test} (first, last, dob, name, mail, password) VALUES ('%s','%s',%d,'%s', '%s', '%s' )",
$form_state['values']['first'], //page 1
$form_state['values']['last'], //page 1
$form_state['values']['year_of_birth'], // page 1
$form_state['values']['name'],  //page 2
$form_state['values']['mail'],  //page 2
$form_state['values']['pass']); // page 2
drupal_set_message(t('Your form has been saved.'));

   
   
    unset (
$form_state['storage']); // This value must be unset or we will
                                    // not be redirected! This is because
                                    // $form_state['rebuild'] gets set to TRUE
                                    // when 'storage' is set. See code sample
                                    // #9 for more on this.
                                  
   
$form_state['redirect'] = 'node'; // Here's how we redirect the user.
 
}
}
?>

What do i do to make sure the first page field values are submitted and passed to the database as well.

Unable to insert data from first page into a database

jkiwanuka - June 10, 2009 - 14:44

As soon as i asked the question I solved it! I had to state the page one values using

<?php
$form_state
['storage']['page_one_values'] = $form_state['values'];
?>

so the database query becomes

<?php
db_query
("INSERT INTO {test} (first, last, dob, name, mail, password) VALUES ('%s','%s',%d,'%s', '%s', '%s' )",
$form_state['storage']['page_one_values']['first'], //page 1
$form_state['storage']['page_one_values']['last'], //page 1
$form_state['storage']['page_one_values']['year_of_birth'], // page 1
$form_state['values']['name'],  //page 2
$form_state['values']['mail'],  //page 2
$form_state['values']['pass']); // page 2
drupal_set_message(t('Your form has been saved.'));
?>

And it worked. Seems a long way around though.

Thanks for a great tutorial!!

aac - June 15, 2009 - 09:17

Thanks for a great tutorial on Form API in Drupal6. It is very much helpful for newcommers of Drupal.

 
 

Drupal is a registered trademark of Dries Buytaert.