Advanced AJAX enabled multi-step forms: creating and altering Drupal entities in custom forms (aka: Wizard forms)

Overview

Sometimes we need to create custom Drupal forms to create or update Drupal Entities, in a different manner than the default forms that we are provided with. In this tutorial, we will look at creating a form 'wizard', that allows a user to create or edit two nodes in a single form.

Example overview

Throughout the world, many companies have multiple branches of their company. The form we will create in this tutorial will create a company, and then a branch that is assigned to that company. This will be done using a multistep form with the following steps:

  1. Create company
  2. Create branch
  3. Confirm values before saving

Technical overview

In this tutorial, we will create a module with a machine name of 'company_wizard'. This module will create a single page, on which our multistep form will be displayed.

This form will handle two different content (node) types. The first is 'company', which will simply contain the default node fields - title and body.

The next node type is 'branch', which will contain the default node fields of title and body, and will have an additional field of type entity reference (from the Entity Reference module), that will refer to the company node. The entity reference field will have the machine name field_branch_company.

In step one of our form, which will have the key 'create_company', we will set the title and body (description) of the node/company.

In step two of our form, which will have the key 'create_branch', we will set the title and body (description) of the code/branch.

Step three of the form, which will have the key 'confirm', will show the submitted values to the user before saving the nodes to the system.

Techniques used

There are a few specific techniques that will be used in this tutorial, that need to be looked at before diving into the form.

Passing entities to forms

The overall concept of our form is that we will use it as a sort of edit wrapper for our entities - in this case, the company and branch nodes. To do this, we will pass two nodes to the form definition. This form will actually serve a dual purpose - it will allow us to create a new company and/or branch, and it will allow us to edit existing companies and/or branches. As such we will either pass in empty nodes (to create new nodes) or existing nodes (to edit the existing nodes).

While this example uses nodes for its example, in fact any entity can be used instead of nodes, whether they be Drupal entities (user, taxonomy, comment etc), or PHP OOP entities that have nothing to do with Drupal whatsoever other than being edited in a Drupal form. The technique works the same regardless.

Using form snippets provided by the Drupal Field API

In order to ensure that our system is consistent as possible between the actual node forms, and our custom form that we will be creating, we will be using the function field_attach_form() to create our form elements for the body element of our nodes. This function allows us to get the form element as the field API defines it, based on the settings you have created through the form interface.

Altering elements by reference

The first steps of our form definition will be to save our nodes to the $form_state. We will be editing these nodes in the submit handlers of our various form steps, and as such, we need to alter the nodes directly in the $form_state, rather than editing a copy of the node. Therefore, throughout the tutorial you will code blocks like this:

Note the & symbol in front of $form_state. This passes $form_state['company] by reference, rather than making a copy of $form_state['company']. This means that any changes made to $company, will also (or rather, in fact) be made to $form_state['company'], allowing us to preserve our values between steps.

Tutorial steps

The tutorial will break down into the following steps:

  1. Step 1: Create the page that will hold the form
  2. Step 2: Create the form definition that will call the form steps
  3. Step 3: Create the AJAX callback function
  4. Step 4: Create step one of our form: create company
  5. Step 5: Create step two of our form: create branch
  6. Step 6: Create step three of our form: confirm
  7. Step 7: Complete the form and return to step one

So, let's get to it!

Step 1: Create the page that will hold the form

The first thing we need to do in our tutorial is to create a path at which to access our form, using hook_menu(), and a page callback function in which to display the page that will show the form. I will not go into depth with these, and if you don't understand the concepts, then this tutorial is probably too advanced for you, and you should spend some time reading about hook_menu() and some time researching and understanding Drupal render arrays.

First the hook_menu() implementation

  1. /**
  2.  * Implements hook_menu().
  3.  */
  4. function company_wizard_menu() {
  5. // Define the system path /create_company
  6. $menu['create_company'] = [
  7. 'title' => 'Create company',
  8. 'page callback' => 'company_wizard_create_company_page',
  9. 'access callback' => TRUE,
  10. ];
  11.  
  12. return $menu;
  13. }

Next we will create our callback function for the path we defined above. In this function, we will do the following:

  1. Either load or create default $company and $branch nodes
  2. Pass these nodes to our form
  3. Make the form part of our page render array
  1. /**
  2.  * Callback function for the path /create_company
  3.  *
  4.  * @param int $company_id
  5.  * The ID of a company which should be updated/edited. Leave FALSE to create a new company
  6.  * @param int $branch_id
  7.  * The ID of a branch which should be updated/edited. Leave FALSE to create a new branch
  8.  *
  9.  * @return array
  10.  * A render array representing the page
  11.  */
  12. function company_wizard_create_company_page($company_id = FALSE, $branch_id = FALSE) {
  13. // If $company_id is not FALSE, we need to load
  14. // the company node with the given ID
  15. if ($company_id) {
  16. // Load the company
  17. $company = node_load($company_id);
  18. // If the company node does not exist, we show the user a 'page not found' page
  19. if (!$company) {
  20. return MENU_NOT_FOUND;
  21. }
  22. }
  23. else {
  24. // $company_id was not set, so we create a new empty company node
  25. $company = company_wizard_create_empty_node('company');
  26. }
  27.  
  28. // If $branch_id is not FALSE, we need to load
  29. // the branch node with the given ID
  30. if($branch_id) {
  31. // Load the branch
  32. $branch = node_load($branch_id);
  33. // If the branch node does not exist, we show the user a 'page not found' page
  34. if(!$branch) {
  35. return MENU_NOT_FOUND;
  36. }
  37. }
  38. else {
  39. // $branch_id was not set, so we create a new empty branch node
  40. $branch = company_wizard_create_empty_node('branch');
  41. }
  42.  
  43. // Now we define our page render array
  44. $page = [
  45. '#prefix' => '<div id="company_wizard_create_company_page">',
  46. '#suffix' => '</div>',
  47. // We pass in the form ID of our wizard, as well as the $company and $branch
  48. // created above
  49. 'form' => drupal_get_form('company_wizard_create_company_form', $company, $branch),
  50. ];
  51.  
  52. return $page;
  53. }

 

Now we have a page that will display our form, so the next thing to do is define our form.

 

Step 2: Create the form definition that will call the form steps

In this step, we will create the overall form. We will define the actual form elements for each step of the form in different functions (and steps of this tutorial). As such, this function defines more of a 'wrapper', so that we can track the various steps of our form quickly and easily.

The main points to note in this function are how we are storing the $company, the $branch, and the form step as part of $form_state, so that these can each be altered in future steps. Also note that any form elements common to every step of the form would be placed in this function, though in our example that is only one element: $form_state['actions'].

So on to the form definition:

  1. /**
  2.  * The form callback that defines our multi-step form. This form creates
  3.  * a company node, and a branch node, and sets the company node as a
  4.  * node reference within the branch node.
  5.  *
  6.  * @param array $form
  7.  * The form object
  8.  * @param array $form_state
  9.  * The form state of the form
  10.  * @param stdClass $company
  11.  * A company node object that is either empty, when a new company is to be created, or is
  12.  * a populated company node if the company is to be edited
  13.  * @param stdClass $branch
  14.  * A branch node object that is either empty, when a new branch is to be created, or is
  15.  * a populated branch node if the branch is to be edited
  16.  *
  17.  * @return array
  18.  * A Drupal render array defining the form
  19.  */
  20. function company_wizard_create_company_form($form, &$form_state, stdClass $company, stdClass $branch) {
  21. // As this is a multi-step form, in which we will be altering the $company node in the steps,
  22. // we want to set $form_state['company'] as the most up to date version of the company node. On
  23. // initial load, this will be the the $company node we passed in our arguments, but in
  24. // future steps, it will be the node contained in $form_state['company']. These two lines of code
  25. // ensure that the most up to date version of the node is retained
  26. $company = isset($form_state['company']) ? $form_state['company'] : $company;
  27. $form_state['company'] = $company;
  28.  
  29. // As this is a multi-step form, in which we will be altering the $branch node in the steps,
  30. // we want to set $form_state['branch'] as the most up to date version of the company node. On
  31. // initial load, this will be the the $branch node we passed in our arguments, but in
  32. // future steps, it will be the node contained in $form_state['branch']. These two lines of code
  33. // ensure that the most up to date version of the node is retained
  34. $branch = isset($form_state['branch']) ? $form_state['branch'] : $branch;
  35. $form_state['branch'] = $branch;
  36.  
  37. // As this is a multi-step form, we need to track which step we are on. We can do this
  38. // in $form_state['step']. As this value is not set on page load, we pass the name of
  39. // the first step of our form as the default: 'create_company'
  40. $step = isset($form_state['step']) ? $form_state['step'] : 'create_company';
  41. $form_state['step'] = $step;
  42.  
  43. // This is the form wrapper that will be used in our ajax callbacks, so that we can
  44. // load the various steps of the form.
  45. $form['#prefix'] = '<div id="company_wizard_create_company_form_wrapper">';
  46. $form['#suffix'] = '</div>';
  47.  
  48. // Our form definition will consist of multiple steps. However, some elements may
  49. // be used in each step of the form. An example of this is the following element,
  50. // which will contain the forward and back buttons in our form. As such, we set this element
  51. // before calling the various steps of our form. However, as we have called it before other
  52. // form elements, it will appear above the rest of the form elements, which makes no sense,
  53. // so we set a heavy weight on it to ensure it is always the last element in the form.
  54. $form['actions'] = [
  55. '#type' => 'actions',
  56. '#weight' => 999,
  57. ];
  58.  
  59. // Next we will set the form elements for the step of the form that we are on
  60. switch($step) {
  61. // This section creates the form elements for the first step, create_company
  62. case 'create_company':
  63. $form = company_wizard_create_company_form_create_company_step($form, $form_state);
  64.  
  65. break;
  66.  
  67. // This section creates the form elements for the second step, create_branch
  68. case 'create_branch':
  69. $form = company_wizard_create_company_form_create_branch_step($form, $form_state);
  70.  
  71. break;
  72.  
  73. // This section creates the form elements for the third step, confirm
  74. case 'confirm':
  75. $form = company_wizard_create_company_form_confirm_step($form, $form_state);
  76.  
  77. break;
  78. }
  79.  
  80. // Finally we return our completed form
  81. return $form;
  82. }

 

We now have a form definition to which we can easily add new steps as necessary in the future.

 

Step 3: Create the AJAX callback function

One of the principles of our multistep form is that the entire form is replaced for each step. This means that we can use a common function as the #ajax callback for every submit button. So we will define that next. Since the entire form is replaced with each step, the entire $form is returned from our callback.

  1. /**
  2.  * The AJAX callback for the form company_wizard_create_company_form()
  3.  *
  4.  * @param array $form
  5.  * The form object
  6.  * @param array $form_state
  7.  * The form state of the form
  8.  *
  9.  * This callback will be used as the ajax callback for all submit buttons on every step.
  10.  */
  11. function company_wizard_create_company_form_ajax_callback($form, &$form_state) {
  12. //Every submit button in our form will use the same wrapper, that wraps the
  13. //entire form, and therefore, we need to return the entire form from our callback.
  14. return $form;
  15. }

Step 4: Create step one of our form: create company

We now have a wrapper for our form in which our form steps are defined, and an AJAX callback that can be used by any buttons in our form. So the next thing to do is to create the form elements for each of the steps for our form. This next function will create the form elements for the first step of the form.

A couple key points to note here. First, the title of nodes is not a field created through the Drupal Field API, so we will create our own form element for this. The body field however is part of the Field API, so we will use field_attach_form() to create our body form element. However, in order for the form to not get confused between the body of the $company field and the $branch field, we need to assign it to a different element than the default.

Using field_attach_form(), any Field API form element can be attached to the form in the same manner as is shown for the body.

First, the definition of the form elements

  1. /**
  2.  * Form elements for the create_company step of company_wizard_create_company_form()
  3.  *
  4.  * @param array $form
  5.  * The form object
  6.  * @param array $form_state
  7.  * The form state of the form
  8.  *
  9.  * @return array
  10.  * A Drupal render array defining the form elements for the create_company step of the form
  11.  */
  12. function company_wizard_create_company_form_create_company_step($form, &$form_state) {
  13. // As this form is multistep, we want to preserve values saved from step to step for the situation
  14. // in which the user navigates back and forth through steps in the form. Therefore when required,
  15. // we will alter the $company node, in order to preserve these values. In the case in which an
  16. // existing company node is edited, the values will be pre-populated. Therefore, we need the
  17. // $company node to work with.
  18. $company = $form_state['company'];
  19.  
  20. $form['company_name'] = [
  21. // We could use the #required attribute of the form API to set our element as required, however
  22. // this will validate our field even when the back button is used, and the default text for required
  23. // form fields is a little too techy for regular users. Therefore, we add the form required asterisk
  24. // to our form title, but we will set our own validation further down to ensure the values is filled out.
  25. '#title' => t('Company name') . '<span title="' . t('This field is required.') . '" class="form-required">*</span>',
  26. '#type' => 'textfield',
  27. '#default_value' => isset($company->title) ? $company->title : '',
  28. // We want our title to be the top element of the form, so we set a light weight to it
  29. '#weight' => -999,
  30. ];
  31.  
  32. // Next, we want to attach the 'body' field to our form. We want to use the same body field as in
  33. // The actual node form, to ensure consistency across our site. As such, we use field_attach_form()
  34. // to generate the form element. This technique can be used to generate any field form snipped
  35. // that is part of a node form, simply submit 'body' for the field name of the element to be displayed
  36. field_attach_form('node', $company, $form, $form_state, NULL, array('field_name' => 'body'));
  37.  
  38. // When using the above code, the node body field is attached to the form as $form['body']. However,
  39. // we will be using the body element of the $branch node in the next step. If both elements have the same
  40. // form key (body), the system gets confused, and mixes values. As such, we want our body element
  41. // to have a unique form key, and therefore we assign $form['body'] to $form['company_body'], and unset
  42. // $form['body'].
  43. $form['company_body'] = $form['body'];
  44. unset($form['body']);
  45.  
  46. // On this step of the form, we need a single submit button, that will validate the submitted values
  47. // for this step, save the submitted values for this step, and set up the form for the next step.
  48. $form['actions']['create_company_submit'] =[
  49. '#type' => 'submit',
  50. '#value' => t('Continue'),
  51. // #validate is an array containing the validation handler(s) for any elements in this step of the form
  52. '#validate' => array('company_wizard_create_company_form_create_company_step_validate'),
  53. // #submit is an array containing the submit handler(s) for this step of the form
  54. '#submit' => array('company_wizard_create_company_form_create_company_step_submit'),
  55. '#ajax' => [
  56. // This is the #prefix we set on the form in the original form definition
  57. 'wrapper' => 'company_wizard_create_company_form_wrapper',
  58. // This is the ajax callback function we defined that will return our form
  59. 'callback' => 'company_wizard_create_company_form_ajax_callback',
  60. ],
  61. ];
  62.  
  63. return $form;
  64. }

Next, validation of any elements created in this step

  1. /**
  2.  * Validation handler for the create_company step of company_wizard_create_company_form()
  3.  *
  4.  * This function will validate any form elements in the create_company step.
  5.  *
  6.  * @param array $form
  7.  * The form object
  8.  * @param array $form_state
  9.  * The form state of the form
  10.  */
  11. function company_wizard_create_company_form_create_company_step_validate($form, &$form_state) {
  12. // The name of the company is required, so we check to ensure that the user has submitted a value,
  13. // and set an error if they have not.
  14. if (!strlen($form_state['values']['company_name'])) {
  15. form_set_error('company_name', t('Please enter the name of the company'));
  16. }
  17. }

Finally, the submit handler for step one, that saves our values, and directs the user to the next step

  1. /**
  2.  * Submit handler for the create_company step of company_wizard_create_company_form()
  3.  *
  4.  * This function will save the submitted values to the $company node, and redirect the user
  5.  * to the next step of the form.
  6.  *
  7.  * @param array $form
  8.  * The form object
  9.  * @param array $form_state
  10.  * The form state of the form
  11.  */
  12. function company_wizard_create_company_form_create_company_step_submit($form, &$form_state) {
  13. // We will add our submitted values to the $company node, to preserve them through the various
  14. // form steps. As such, we want to access the node that we have stored in the $form_state. Note that
  15. // it is required to pass $form_state['company'] as a reference (by using the & symbol) so that any
  16. // changes we make to the node happen to the node we have saved in $form_state['company']
  17. $company = &$form_state['company'];
  18.  
  19. // Save the submitted title to $company->title
  20. $company->title = $form_state['values']['company_name'];
  21. // Save the submitted body to $company->body. This will save everything for the entire
  22. // body element, including the text format, and a summary if one has been set.
  23. $company->body = $form_state['values']['company_body'];
  24.  
  25. // Set the next step of the form that is to be displayed to the user: create_branch
  26. $form_state['step'] = 'create_branch';
  27. // $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
  28. // the multistep form to work.
  29. $form_state['rebuild'] = TRUE;
  30. }

The above three functions make up the first step of the form: definition, validation, and submission. Next, we will move on to the next step of our form, creating the branch.

Step 5: Create step two of our form: create branch

This step is mostly the same as the previous step, since we are only dealing with the same two types of fields: title and body. However, this form definition comes with an additional form element for the 'back' button, allowing the user to go back to the previous step. This button has its own submit handler, and therefore requires an additional function that the previous step didn't have (since there was no back button). Note that even in the back button, we save the submitted values, so that the user can come back to this step of the form and pick up where they left off.

First, the definition of the form elements

  1. /**
  2.  * Form elements for the create_branch step of company_wizard_create_company_form()
  3.  *
  4.  * @param array $form
  5.  * The form object
  6.  * @param array $form_state
  7.  * The form state of the form
  8.  *
  9.  * @return array
  10.  * A Drupal render array defining the form elements for the create_branch step of the form
  11.  */
  12. function company_wizard_create_company_form_create_branch_step($form, &$form_state) {
  13. // As this form is multistep, we want to preserve values saved from step to step for the situation
  14. // in which the user navigates back and forth through steps in the form. Therefore, when required,
  15. // we will alter the $branch node, in order to preserve these values. In the case in which an
  16. // existing company node is edited, the values will be pre-populated. Therefore, we need the
  17. // $branch node to work with.
  18. $branch = $form_state['branch'];
  19.  
  20. $form['branch_name'] = [
  21. // We could use the #required attribute of the form API to set our element as required, however
  22. // this will validate our field even when the back button is used, and the default text for required
  23. // form fields is a little too techy for regular users. Therefore, we add the form required asterisk
  24. // to our form title, but we will set our own validation further down to ensure the values is filled out.
  25. '#title' => t('Branch name') . '<span title="' . t('This field is required.') . '" class="form-required">*</span>',
  26. '#type' => 'textfield',
  27. '#default_value' => isset($branch->title) ? $branch->title : '',
  28. // We want our title to be the top element of the form, so we set a light weight to it
  29. '#weight' => -999,
  30. ];
  31.  
  32. // Next, we want to attach the 'body' field to our form. We want to use the same body field as in
  33. // The actual node form, to ensure consistency across our site. As such, we use field_attach_form()
  34. // to generate the form element. This technique can be used to generate any field form snipped
  35. // that is part of a node form, simply submit 'body' for the field name of the element to be displayed
  36. field_attach_form('node', $branch, $form, $form_state, NULL, ['field_name' => 'body']);
  37.  
  38. // When using the above code, the node body field is attached to the form as $form['body']. However,
  39. // we used the body element of the $company node in the previous step. If both elements have the same
  40. // form key (body), the system gets confused, and mixes values. As such, we want our body element
  41. // to have a unique form key, and therefore we assign $form['body'] to $form['branch_body'], and unset
  42. // $form['body'].
  43. $form['branch_body'] = $form['body'];
  44. unset($form['body']);
  45.  
  46. // On this step of the form, we need two submit buttons. This is the first, that will validate the submitted
  47. // values for this step, save the submitted values for this step, and set up the form for the next step.
  48. $form['actions']['create_branch_submit'] = [
  49. '#type' => 'submit',
  50. '#value' => t('Continue'),
  51. // #validate is an array containing the validation handler(s) for any elements in this step of the form
  52. '#validate' => ['company_wizard_create_company_form_create_branch_step_validate'],
  53. // #submit is an array containing the submit handler(s) for this step of the form
  54. '#submit' => ['company_wizard_create_company_form_create_branch_step_submit'],
  55. '#ajax' =>[
  56. 'wrapper' => 'company_wizard_create_company_form_wrapper',
  57. 'callback' => 'company_wizard_create_company_form_ajax_callback',
  58. ],
  59. ];
  60.  
  61. // Next is the back button, that will take us to the previous step.
  62. // Notice that both buttons (and in fact every button in every step) use the exact same
  63. // #ajax callback and wrapper.
  64. $form['actions']['create_branch_back'] = [
  65. '#type' => 'submit',
  66. '#value' => t('Back'),
  67. // #submit is an array containing the submit handler(s) for the back button. This will allow us
  68. // to redirect the user to the previous step in the form. Note that only want to validate the form
  69. // elements when going to the next step, and therefore no #validate handlers are attached to
  70. // this button.
  71. '#submit' => ['company_wizard_create_company_form_create_branch_step_back'],
  72. '#ajax' => [
  73. 'wrapper' => 'company_wizard_create_company_form_wrapper',
  74. 'callback' => 'company_wizard_create_company_form_ajax_callback',
  75. ],
  76. ];
  77.  
  78. return $form;
  79. }

Next, the submit handler for the back button

  1. /**
  2.  * Submit handler for the back button in the create_branch step of company_wizard_create_company_form()
  3.  *
  4.  * This function saves any elements submitted in the create_branch step of the form to the $branch node,
  5.  * and redirects the user to the previous step.
  6.  *
  7.  * @param array $form
  8.  * The form object
  9.  * @param array $form_state
  10.  * The form state of the form
  11.  */
  12. function company_wizard_create_company_form_create_branch_step_back($form, &$form_state) {
  13. // We will add our submitted values to the $branch node, to preserve them even though we are going
  14. // back to the previous step. This is so that if the user suddenly realizes they forgot something on the
  15. // previous step, any data they have entered on this step will not be lost when returning to this step.
  16. // Note that it is required to pass $form_state['branch'] as a reference (by using the & symbol) so
  17. // that any changes we make to the node happen to the node we have saved in $form_state['branch']
  18. $branch = &$form_state['branch'];
  19.  
  20. // Save the form elements from this step to the $branch node
  21. $branch->title = $form_state['values']['branch_name'];
  22. $branch->body = $form_state['values']['branch_body'];
  23.  
  24. // Direct the user to the previous step: create_company
  25. $form_state['step'] = 'create_company';
  26. // $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
  27. // the multistep form to work.
  28. $form_state['rebuild'] = TRUE;
  29. }

Now, the validation handler for the form elements in this step

  1. /**
  2.  * Validation handler for the create_branch step of company_wizard_create_company_form()
  3.  *
  4.  * This function will validate any form elements in the create_branch step.
  5.  *
  6.  * @param array $form
  7.  * The form object
  8.  * @param array $form_state
  9.  * The form state of the form
  10.  */
  11. function company_wizard_create_company_form_create_branch_step_validate($form, &$form_state) {
  12. // The name of the branch is required, so we check to ensure that the user has submitted a value,
  13. // and set an error if they have not.
  14. if (!strlen($form_state['values']['branch_name']) {
  15. form_set_error('branch_name', t('Please enter the name of the branch'));
  16. }
  17. }

And finally, the submit handler to save the submitted values, and redirect the user to the next step

  1. /**
  2.  * Submit handler for the create_branch step of company_wizard_create_company_form()
  3.  *
  4.  * This function will save the submitted values to the $branch node, and redirect the user
  5.  * to the next step of the form.
  6.  *
  7.  * @param array $form
  8.  * The form object
  9.  * @param array $form_state
  10.  * The form state of the form
  11.  */
  12. function company_wizard_create_company_form_create_branch_step_submit($form, &$form_state)
  13. {
  14. // We will add our submitted values to the $branch node, to preserve them through the various
  15. // form steps. As such, we want to access the node that we have stored in the $form_state. Note that
  16. // it is required to pass $form_state['branch'] as a reference (by using the & symbol) so that any
  17. // changes we make to the node happen to the node we have saved in $form_state['branch']
  18. $branch = &$form_state['branch'];
  19.  
  20. // Save the form elements from this step to the $branch node
  21. $branch->title = $form_state['values']['branch_name'];
  22. $branch->body = $form_state['values']['branch_body'];
  23.  
  24. // Direct the user to the next step: confirm
  25. $form_state['step'] = 'confirm';
  26. // $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
  27. // the multistep form to work.
  28. $form_state['rebuild'] = TRUE;
  29. }

The above four functions make up the second step of the form: definition, back button, validation, and submission. Next, we will move on to the final step of our form, showing the submitted values to the user for confirmation.

Step 6: Create step three of our form: confirm

The last step of our form will simply show the submitted values to the user, and give them the option to either save the data, or to go back, so that they can alter their submitted values. As such we need some text display, a back button, and a submit button. As there are no elements to add/change any form values in this step, we do not need validation.

First, the definition of the form elements

  1. /**
  2.  * Form elements for the confirm step of company_wizard_create_company_form()
  3.  *
  4.  * This step of the form shows the submitted values to the user as a final step, at which
  5.  * point they can save their submission, or go back to edit it on previous pages.
  6.  *
  7.  * @param array $form
  8.  * The form object
  9.  * @param array $form_state
  10.  * The form state of the form
  11.  *
  12.  * @return array
  13.  * A Drupal render array defining the form elements for the confirm step of the form
  14.  */
  15. function company_wizard_create_company_form_confirm_step($form, &$form_state) {
  16. // We need our company node to display our submitted company values
  17. $company = $form_state['company'];
  18. // We need our branch node to display our submitted company values
  19. $branch = $form_state['branch'];
  20.  
  21. // Header
  22. $form['confirm'] = [
  23. '#prefix' => '<p>',
  24. '#suffix' => '</p>',
  25. '#markup' => t('Would you like to create the company %company with the branch %branch?', ['%company' => $company->title, '%branch' => $branch->title]),
  26. ];
  27.  
  28. // Display the company output
  29. $form['company_output'] =[
  30. '#prefix' => '<div id="company_output"><h2>'. t('Company') . '</h2>',
  31. '#suffix' => '</div>',
  32. 'title' => [
  33. '#prefix' => '<p>',
  34. '#suffix' => '</p>',
  35. '#markup' => t('Company Name: %title', array('%title' => $company->title)),
  36. ),
  37. 'body' => [
  38. '#prefix' => '<div id="company_body">',
  39. '#suffix' => '</div>',
  40. '#markup' => check_markup($company->body[LANGUAGE_NONE][0]['value'], $company->body[LANGUAGE_NONE][0]['format']),
  41. ],
  42. ];
  43.  
  44. // Display the branch output
  45. $form['branch_output'] = [
  46. '#prefix' => '<div id="branch_output"><h2>'. t('Branch') . '</h2>',
  47. '#suffix' => '</div>',
  48. 'title' => [
  49. '#prefix' => '<p>',
  50. '#suffix' => '</p>',
  51. '#markup' => t('Branch Name: %title', array('%title' => $branch->title)),
  52. ),
  53. 'body' => [
  54. '#prefix' => '<div id="branch_body">',
  55. '#suffix' => '</div>',
  56. '#markup' => check_markup($branch->body[LANGUAGE_NONE][0]['value'], $branch->body[LANGUAGE_NONE][0]['format']),
  57. ],
  58. ];
  59.  
  60. // Add our submit button to complete the form
  61. $form['actions']['confirm_submit'] = [
  62. '#type' => 'submit',
  63. '#value' => t('Create'),
  64. '#submit' => array('company_wizard_create_company_form_confirm_step_submit'),
  65. '#ajax' => [
  66. 'wrapper' => 'company_wizard_create_company_form_wrapper',
  67. 'callback' => 'company_wizard_create_company_form_ajax_callback',
  68. ],
  69. ];
  70.  
  71. // Add our back button to go back to the previous step
  72. $form['actions']['confirm_back'] = [
  73. '#type' => 'submit',
  74. '#value' => t('Back'),
  75. '#submit' => ['company_wizard_create_company_form_confirm_step_back'],
  76. '#ajax' => [
  77. 'wrapper' => 'company_wizard_create_company_form_wrapper',
  78. 'callback' => 'company_wizard_create_company_form_ajax_callback',
  79. ],
  80. ];
  81.  
  82. return $form;
  83. }

Next, the submit handler for the back button

  1. /**
  2.  * Submit handler for the back button in the confirm step of company_wizard_create_company_form()
  3.  *
  4.  * This function redirects the user to the previous step of the form: create_branch
  5.  *
  6.  * @param array $form
  7.  * The form object
  8.  * @param array $form_state
  9.  * The form state of the form
  10.  */
  11. function company_wizard_create_company_form_confirm_step_back($form, &$form_state) {
  12. // There are no form elements in this step, so we do not need to save anything to the $company or $branch nodes.
  13. // We simply need to redirect the user to the previous step.
  14.  
  15. // Redirect the user to the previous step of the form, 'create_branch'
  16. $form_state['step'] = 'create_branch';
  17. // $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
  18. // the multistep form to work.
  19. $form_state['rebuild'] = TRUE;
  20. }

Now we have our confirmation step to show to the users, and the final thing to do is to tie it altogether, and save our values to the database.

Step 7: Complete the form and return to step one

This last step is where all the magic takes place. In this step, we will take our completed $company and $branch nodes, and save them to the database. As we want to add an Entity Reference on the branch node pointing at the company node, we will add the NID of the company node to the branch node before saving. After all has been saved, if we are using the form to create new nodes, we need to replace our saved nodes with new empty nodes, and redirect to the top page. If we are editing existing nodes however, we do not want to replace these nodes, as the user may want to go through and edit again.

  1. /**
  2.  * Submit handler for the confirm step of company_wizard_create_company_form()
  3.  *
  4.  * This function completes the form, saving the new (or updated) $company and $branch
  5.  * nodes, and adding the $company node as an entity reference of the branch node.
  6.  *
  7.  * @param array $form
  8.  * The form object
  9.  * @param array $form_state
  10.  * The form state of the form
  11.  */
  12. function company_wizard_create_company_form_confirm_step_submit($form, &$form_state) {
  13. // We will be updating our $company and our $branch in this function, so we need them as reference.
  14. $company = &$form_state['company'];
  15. $branch = &$form_state['branch'];
  16.  
  17. // We determine whether we are updating existing nodes, or creating new nodes. If nid is set for
  18. // either node, then the form was passed existing nodes at the start, and therefore we are updating.
  19. $updating = isset($company->nid) || isset($branch->nid);
  20.  
  21. // We prepare our company node by calling node_submit()
  22. $company = node_submit($company);
  23. // We save our company node by calling node_save()
  24. node_save($company);
  25.  
  26. // If the company node has saved properly, then $node->nid will be set, and will be a value greater
  27. // than zero (which equate to TRUE)
  28. if (isset($company->nid) && $company->nid) {
  29. // The company node has been successfully saved, therefore we want to set an entityreference
  30. // on the branch node, pointing at the company node.
  31. $branch->field_branch_company[LANGUAGE_NONE][0]['target_id'] = $company->nid;
  32. // We prepare our branch node by calling node_sumbit()
  33. $branch = node_submit($branch);
  34. // We save our branch node by calling node_save()
  35. node_save($branch);
  36.  
  37. // If the branch node has saved properly, then $branch->nid will be set, and will be a value greater
  38. // than zero (which equate to TRUE)
  39. if (isset($branch->nid) && $branch->nid) {
  40. // The final actions performed are different depending on whether the user is updating existing
  41. // nodes, or creating new ones. First we handle the case in which the user is updating existing nodes
  42. if ($updating) {
  43. // Show the user a successful update message
  44. drupal_set_message(
  45. t('Thank you, the company !company with the branch !branch has been updated', [
  46. '!company' => l($company->title, 'node/' . $company->nid),
  47. '!branch' => l($branch->title, 'node/' . $branch->nid),
  48. ])
  49. );
  50. }
  51. // Next we handle the case in which the user is creating new nodes
  52. else {
  53. // Show the user a successful creation message
  54. drupal_set_message(
  55. t('Thank you, the company !company with the branch !branch has been created', [
  56. '!company' => l($company->title, 'node/' . $company->nid),
  57. '!branch' => l($branch->title, 'node/' . $branch->nid),
  58. ])
  59. );
  60. }
  61.  
  62. // Because we are on the form in which the user can create new companies and branches,
  63. // we need to set new empty node objects in $form_state, so that after the user is redirected
  64. // back to the first step of the form, they can create a new company and new branch.
  65. $company = company_wizard_create_empty_node('company');
  66. $branch = company_wizard_create_empty_node('branch');
  67. }
  68. else {
  69. // For some reason, our branch node did not save, so we show an error to the user, and save the data
  70. // to the watchdog table for debugging.
  71. drupal_set_message(t('Sorry, an error occurred when saving the branch'));
  72. watchdog('company_wizard_save_company', t('Error in following branch node: !error', [
  73. !errror' => '<pre>' . print_r($branch, TRUE) . '</pre>',
  74. ]));
  75. }
  76. }
  77. else {
  78. // For some reason, our company node did not save, so we show an error to the user, and save the data
  79. // to the watchdog table for debugging.
  80. drupal_set_message(t('Sorry, an error occurred when saving the company'));
  81. watchdog('company_wizard_save_company', t('Error in following company node: !error', [
  82. '!errror' => '<pre>' . print_r($company, TRUE) . '</pre>',
  83. ]));
  84. }
  85.  
  86. // We redirect the user back to the first step of the form, create_company
  87. $form_state['step'] = 'create_company';
  88. // $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
  89. // the multistep form to work.
  90. $form_state['rebuild'] = TRUE;
  91. }

And with that, our form is complete.

Summary

In this tutorial, we learned how to create a 'wizard' form in Drupal, in order to create/edit either a single or multiple Entities/nodes through a single form that is different from the default node or entity forms provided by Drupal or other modules. We used the function field_attach_form() to ensure consistency between the default node forms and our custom form, and we set our entire form to be an ajax multistep form, providing a nice user experience to our users, from within Drupal.