Last updated 16 November 2016. Created on 15 July 2008.
Edited by serundeputy, danjuls, Max_Headroom, efolia. Log in to edit this page.

Note: The following tutorial assumes that your are developing your own module and that you have at least a passing familiarity with Drupal's Form API.

Buttons Up and Down

In most cases and environments, form buttons remain the most elegant and practical way for a user to interact quickly with the data that is displayed in a browser window. Drupal's Form API provides three (3) types of buttons: submit, button and image_button.

The 'image_button' type is simply a special case of the 'submit' button that allows an image to be used as a clickable area instead of the standard system buttons (i.e., the grey rectangle with a word in the middle). Beside having an image source (defined as '#src' => 'path/to/mybuttonimage.gif',) for all practical purposes the image_button behaves exactly like its 'submit' counterpart. In the case of the 'submit' and 'button' types, the '#value' property supplies at once the text displayed inside the button and the returned value that will be handled by the form processing functions. In other words, if a button is defined with '#value' => 'Click';, then it will display in the browser as a 'Click' button and $form_state['clicked_button']['#value'] will return 'Click'.

Of the three types of button, the 'submit' type is by far the most common one. By default, all clicked buttons will send the form back to the server, whether the form is to be processed or not. In other words, a button will always initiate a post whether '#executes_submit_callback' is set to true or false. The only difference between the 'button' and 'submit' element types is whether or not they will execute the corresponding submit callback function. Such callbacks are initiated in the following order of priority:

  1. If the form's element defines its own submit function through $form['my_element']['#submit'], Drupal will attempt to execute the latter. Where such a function is defined but cannot be found, the script will abort and display a PHP error.
  2. Where there is no '#submit' property defined for a given element (e.g., button), Drupal will attempt to execute the form's own submit callback, defined through $form[#submit][]. Again, where such a function is defined but cannot be found, the script will abort and display a PHP error.
  3. Finally, if neither the form nor the element took care of defining a submit callback, Drupal will attempt to execute the default submit function, named by concatenating the form's ID suffixed with "_submit" (e.g., my_form_submit). If no such function exist, processing will continue without any processing of the values in the form.

Disabling and Overriding Buttons

In order to keep the browser from posting the form when the user clicks on a button, you will need to override the default XHTML form behavior. This can be done with a simple line of embedded JavaScript in the '#attributes' property of the button definition. Adding '#attributes' => array('onclick' => 'return (false);'), to a form's button element enables the client's JavaScript processor to trap and void the mouse click event. Usually, this is used to allow JavaScript to hijack the XHTML element and process the event on its own. However, this voiding of a button's functionality can also be useful during the processing of the form in cases where you want hook_form_alter to disable a specific button (since you cannot apply the '#disabled' => TRUE, property to a button or submit element). In most cases however, seeing that this simple attribute renders the button completely useless, you may want to provide an additional call to a client-side scripting function.

Server-side Processing

You might wish for a button to process and/or expose your data in a different way. For example, you may want to change a set of values programatically and add 10 to every number in a range.

Following this fictional example, lets assume that your form defines such a range of data using this (rather useless) function:

function my_data_form($form_state, &$node) {
$form = array();
for ($i=1; $i<=5; $i++) {
  $form['range'][$i] = array(
    '#type' => 'item',
    '#title' => 'Item '. $i,
    '#value' => $i,
  );
}
}

Now we add a button that provides an easy way to add 10 to each of these values:

$form['add_10'] = array(
  '#type' => 'submit',
  '#value' => 'Add 10',
  '#submit' => array('my_add_10_submit'),
);

Finally, we define the above "submit" function:

function my_add_10_submit ($form, &$form_state) {
  // This function updates the form values by adding 10 to each of them
  foreach ($form_state['values']['range'] as $key=>$value) {
    $form_state['values']['range'][$key] = $value + 10;
  }
  return $form;

From now on, everytime the user presses the "Add 10" button, the browser will submit the form and rebuild it with the new, 'upgraded' values. The 'submit' function updates the displayed values by adding 10 to each of them. In the highly unlikely event that you would need to use the above code in your own form, be sure to provide an additional button (and the corresponding submit function) that allows the user to backtrack on his or her frenzy clicks:

$form['substract_10'] = array(
  '#type' => 'submit',
  '#value' => 'Substract 10',
  '#submit' => array('my_substract_10_submit'),
);
function my_substract_10_submit ($form, &$form_state) {
// This function updates the form values by subtracting 10 from each of them
// Don't forget to pass $form_state by reference 
  foreach ($form_state['values']['range'] as $key=>$value) {
    $form_state['values']['range'][$key] = $value - 10;
  }
  return $form;

Now you have a button that can process the elements of a form and modify their value using the $form_state variable. That's all fine and dandy, but what if you want to do more complex processing and allow the user to display related content or a different view through a single click?

Redirection

If simple data processing is insufficient, you can use a button to redirect the user to a different page altogether. For example, you might want to allow the user to jump from your editing form straight to a page displaying a related data grid. This can be accomplished by setting $form_state['redirect'] to the value of the path that you wish to use as a target. Do not use drupal_goto to redirect from a button, as the latter function bypasses important steps in the form processing flow. In most cases, it will simply fail to redirect the browser to the intended path. If you need to redirect from one path to another, do it by using a drupal_goto in your implementation of hook_menu. An example:

function my_module_menu() {
  $items = array();
  $items['my_module/redirected/path'] = array(
		'title' => 'My redirected path', 
		'page callback' => 'drupal_goto', 
		'page arguments' => array('my_module/target/path'), 
		'access arguments' => array('access my_module content'),
		'type' => MENU_CALLBACK, 
		);
	$items['my_module/target/path'] = array(
    // ...
  );
}
  1. Make sure that you have a menu item that points to the target path. This insures that Drupal will handle the context correctly.
  2. Your button definition should set a '#submit' property pointing to its own submit function (i.e., $form['button']['#submit]), rather than relying on the form's main (or default) submit property (i.e., $form['#submit'][]). The function name is not required to end with "_submit," although it will make your code easier to read if you do so. Providing each additional button with its own 'submit' function will substantially simplify the module's overall architecture and it will help you track down possible bugs. Alternatively, you can use a "switch" statement in the form's own 'submit' to parse the value of $form_state['clicked_button']['#value']. Note that the latter method, although common practice in Drupal versions prior to 6, is not recommended as it imposes a heavier load on the PHP processor and can make it easy for bugs to hide in the cracks of your code.
    $form = array();
    $form['button']['list'] = array(
      '#type' => 'submit',
      '#value' => 'list',
      '#submit' => array('my_form_button_list_submit'),
    ); 
    
  3. When the user clicks on the button, Drupal will bypass the form's own _submit and proceed to pass the form values to the submit function registered to the said button (e.g., my_form_button_list_submit()) . Here's a skeleton of such a function:
    function my_form_button_list_submit($form, &$form_state) {
    // Don't forget to pass $form_state by reference 
    // Optionally, do some processing here if you wish - such as saving the edited values
      my_node_update_function();
      unset($form_state['storage']); 
      $form_state['redirect'] = 'my_module/data/list'; // <- this path should be one of your menu items.
    }
    

Remember that redirection through $form_state['redirect'] will not work where the value of $form_state['rebuild'] is TRUE. Since 'rebuild' will automatically be set to TRUE if there is anything left in the $form_state['storage'], you should unset that part of the form_state array before redirecting. Note that in its current implementation, Drupal (6) does not set the 'storage' value on its own -- and most likely never will -- so this step is not required if you do not explicitly set the 'storage' variable.

Final Notes

Remember not to use the validation handler (either $form['#validate'][] or $form['my_button']['#validate']) to modify the values stored in $form_state. The validation handler is reserved for the specific purpose of filtering and verifying the validity of the data that passes through it. Using an improper function to process the form may lead to behavior that is difficult to correct later on.

If you only need to modify the display format of your elements or if you want to manipulate values that will not be stored by your module at any point, you should seriously consider using a client-side solution such as JQuery or JavaScript instead of relying too heavily on Form API. Remember that submitting a form increases the server load in no small proportion compared to the above-stated alternative. Where it might require a little more knowledge on your part, learning the basics of JQuery or even JavaScript will likely save you a lot of headaches later. An example of such a application of client-side scripting using JavaScript is provided here.

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

David Stosik’s picture

Most of the time, you don't want to:
- return false; to prevent form posting : http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/
- do this only in the 'onclick' handler, as Enter key would be overlooked

Talking of Enter key, how to designated the button I want to be "clicked" when I press Enter key inside a text field?

mattcasey’s picture

When you click "Enter" it will trigger the first button in the first form or you can set focus on an element with jQuery using $.focus().

grguth’s picture

If a form has required fields, how do you skip all the error messages if all you want to do is redirect to another page ?