function hook_forms

You are here

7 system.api.php hook_forms($form_id, $args)
5 core.php hook_forms()
6 core.php hook_forms($form_id, $args)

Map form_ids to builder functions.

This hook allows modules to build multiple forms from a single form "factory" function but each form will have a different form id for submission, validation, theming or alteration by other modules.

The callback arguments will be passed as parameters to the function. Callers of drupal_get_form() are also able to pass in parameters. These will be appended after those specified by hook_forms().

See node_forms() for an actual example of how multiple forms share a common building function.

Parameters

$form_id: The unique string identifying the desired form.

$args: An array containing the original arguments provided to drupal_get_form().

Return value

An array keyed by form id with callbacks and optional, callback arguments.

Related topics

4 functions implement hook_forms()

Note: this list is generated by pattern matching, so it may include some functions that are not actually implementations of this hook.

node_forms in modules/node/node.module
Implementation of hook_forms(). All node forms share the same form handler
search_forms in modules/search/search.module
trigger_forms in modules/trigger/trigger.module
Implementation of hook_forms(). We reuse code by using the same assignment form definition for each node-op combination.
user_forms in modules/user/user.module
Implementation of hook_forms().
1 invocation of hook_forms()
drupal_retrieve_form in includes/form.inc
Retrieves the structured array that defines a given form.

File

developer/hooks/core.php, line 837
These are the hooks that are invoked by the Drupal core.

Code

function hook_forms($form_id, $args) {
  $forms['mymodule_first_form'] = array(
    'callback' => 'mymodule_form_builder',
    'callback arguments' => array('some parameter'),
  );
  $forms['mymodule_second_form'] = array(
    'callback' => 'mymodule_form_builder',
  );
  return $forms;
}

Comments

I had some trouble implementing hook_forms at first, but after some tinkering this is what I have worked out as a solution to printing multiple versions of the same form on one page:

  1. Create your form builder like normal:
    <?php
    function mymodule_form($form_state, $args){}
    ?>
  2. Call your form with drupal_get_form() (multiple times) with a key appended to the form_id:
    <?php
    //normal call to get form, but append a key (of your own creation, e.g. a nid, or a static counting variable) to the end of the argument.
    drupal_get_form('mymodule_form_'.$key)
    ?>
  3. Create mymodule_forms function:
    <?php

    function mymodule_forms($form_id,$args){
    //Check if the form_id passed to drupal_get_form() contains the string 'mymodule_form'
     
    if (strpos($form_id, 'mymodule_form') !== FALSE) {
    //Lets attach mymodule_form to $forms[$form_id].  This effectively allows you to use the same form builder function to build a form with any form_id of your choice.
         
    $forms[$form_id] = array(
           
    'callback' => 'mymodule_form',
            );
          return
    $forms;
       }
    }
    ?>
  4. Specify your submit function explicitly in your form builder function using the #submit element:
    <?php
    function mymodule_form($form_state, $args){
    //this is the function that will get called on form submission.
     
    $form['#submit'][] = 'mymodule_form_submit';
    }
    ?>

#2 save my life!
I would have been stack in here if it wasn't for you! Thank you!

Greetings,

Here is another example of a hook_forms implementation, using a more-or-less generic and reusable approach. One benefit of this method is that one has to go to fewer places to update form generation, validation, and submit code when dealing with multiple forms. I know this is a lengthy post, but this hook_forms process is somewhat non-intuitive, so I'm trying to give a complete (if silly) example for the sake of clarity.



hook forms

First set up a generic catch-all for your forms. In this case, I'm assuming that any $form_id value which starts with "mymodule_" will be a form generated by this module's code. In practice, you may want to narrow this down a little more, depending on the scope of your module's deployment, the uniqueness of your module's name (i.e. the collision probability), etc.

<?php
/**
* Implementation of hook_forms
*
* @see http://api.drupal.org/api/drupal/developer--hooks--core.php/function/hook_forms/6
*
* @param $form_id array
*   String representing the name of the form itself. Typically this is the
*   name of the function that generated the form.
* @param $args array
*   An array containing the original arguments provided to drupal_get_form().
*
* @return array
*   An array keyed by form id with callbacks and optional, callback arguments.
*/
function mymodule_forms($form_id, $args) {

 

$forms = array();

 

// if the name of this form does not start with the name of
  // the module, then do nothing here
 
if (0 !== strpos($form_id, 'mymodule_')) {return $forms;}
 
 
// the requested form belong to this module, so provide the
  // information necessary to for drupal to call the function that
  // builds this form
 
$forms[$form_id] = array(
   
'callback' => 'mymodule_form_builder',
   
'callback arguments' => array($form_id, $args,)
  );
 
  return
$forms;
}
?>



form builder

Next, build the form-array definitions (ref http://api.drupal.org/api/drupal/modules--node--node.module/function/nod...). Note that the $form_state var will likely be empty at this point, but can hold useful data if this form is being re-generated.

<?php
/**
* Renders a module's form.
*
* @return array
*   The form array defind the form form for the given form_id
*/
function mymodule_form_builder(&$form_state, $form_id, $args) {
 
$form = array();
 
  switch(
$form_id) {
    case
'mymodule_special_form_one':
     
     
$form['title'] = array(
       
'#type' => 'textfield',
       
'#title' => t('Subject'),
       
'#default_value' => 'Oh Hie',
       
'#size' => 60,
       
'#maxlength' => 128,
       
'#required' => TRUE,
      );

     

$form['mything_nid'] = array(
       
'#type' => 'value',
       
'#value' => $args[0],
      );
     
     
$form['mything_uid'] = array(
       
'#type' => 'value',
       
'#value' => $args[1],
      );
     
     
$form['mything_submit'] = array(
       
'#type' => 'submit',
       
'#value' => t('Do It'),
       
'#weight' => 10,
      );
     
      break;
     
    case
'mymodule_special_form_two':
     
$form['foo'] = array(
       
'#type' => 'select',
       
'#title' => t('Bar'),
       
'#default_value' => array('teaser'),
       
'#options' => array(
         
'title' => t('Titles only'),
         
'teaser' => t('Titles plus teaser'),
         
'fulltext' => t('Full text'),
        ),
      );
     
     
$form['mything_submit'] = array(
       
'#type' => 'submit',
       
'#value' => t('Make It So'),
       
'#weight' => 10,
      );
     
      break;
     
    default:
     
$form['unknown'] = array(
       
'#type' => 'markup',
       
'#value' => '<div>'.t('Unknown form type, "%form_id%", requested in function mymodule_form_builder.', array('%form_id%'=>$form_id)).'</div>',
      );
      break;
  }
 

 

// all our forms go through the validate and submit processes we have set up for this module's use
 
$form['#validate'][] = 'mymodule_form_validate';
 
$form['#submit'][] = 'mymodule_form_submit';
 
  return
$form;
}
?>



form validation

Perform whatever validation you deem appropriate for each form.

<?php
/**
* Validates a mymodule form.
*
* @param $form array
*   array of form elements that comprise the form.
* @param $form_state array
*   A keyed array containing the current state of the form.
*
* @return void
*/
function mymodule_form_validate($form, &$form_state) {
 
  switch(
$form_state['values']['form_id']) {
    case
'mymodule_special_form_one':
      if (!
$form_state['values']['title']) {
       
form_set_error('title', t('No can haz empty title!'));
      }
      break;
    case
'mymodule_special_form_two':
     
// Do nothing - we blindly trust this set of values!  o.O
     
break;
    default:
     
// does your module need to validate something for every form it
      // owns? do so here...
     
break;
  }
}
?>



form submit / save

Validation has passed, so save the form values.

<?php
/**
* Processes a mymodule form.
*
* @param $form array
*   array of form elements that comprise the form.
* @param $form_state array
*   A keyed array containing the current state of the form.
*
* @return void
*/
function mymodule_form_submit($form, &$form_state) {
  switch(
$form_state['values']['form_id']) {
    case
'mymodule_special_form_one':
     
// save 'title', 'nid', and 'uid' to the database
   
case 'mymodule_special_form_two':
     
// save 'foo' to the database
     
break;
    default:
      break;
   
  }
}
?>



example usage

An example call to the new form might look something like this:

<?php
  $my_form
= drupal_get_form('mymodule_special_form_one', $nid, $uid);
?>

Or in a menu, like this:

<?php
/**
* Implementation of hook_menu
*
* @see http://api.drupal.org/api/function/hook_menu/6
*
* @return array
*   array of menu definitions key by url path
*/
function mymodule_menu() {
 
$items = array();
 
 
$items['mymodule/stuff'] = array(
   
'title' => 'Foo',
   
'description' => 'Foo Bar Sna Foo',
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('mymodule_special_form_two'),
   
'access callback' => 'user_is_logged_in',
   
'type'=>MENU_NORMAL_ITEM,
  );
 
  return
$items;
}
?>

Thanks for your example. I followed it and now I have my forms working on the same page.

I just want to mention a couple of issues that I had that may help others:

  • Once you modify your forms, you have to clear all caches. In my case I had tried to create two forms independently before finding out about this hook, and after making all the changes to follow this example, I was getting an error that dissapeared when I cleared the caches.
  • In the form builder function (mymodule_form_builder) the order of the arguments is different. In the second place, I am getting the extra $args and in the last place the $form_id

Apart from those two things, everything worked fine.

Hi, I found this article really helpful when I needed to create multiple forms on a single page with different IDs so that submitting one didn't trigger the others to submit. But when I attempted to invoke the hook again in the same module I could not get the second function to run.

My code below is almost identical to similiar functions declared earlier in my module for a different page (chic_request_status_page) and the work fine. But in this second declaration my function chic_bookings_listing_update_forms is never entered. and I get all the forms with the correct IDs but there are no select or input elements from my chic_bookings_listing_update_form_builder function.

Hope this makes sense to someone,

-johnny;j

<?php
/**
* Now display the booking listing page with the form to update the status
**/
function chic_bookings_listing_page(){
//getting the hotel node id for the query from the url
   
$hotelNodeId = arg(3);
// run the query to get the list.
 
$query = "SELECT DATE_FORMAT(V.createDate,GET_FORMAT(DATE,'JIS')) AS createDate, DATE_FORMAT(V.fromDate,GET_FORMAT(DATE,'JIS')) AS fromDate, DATE_FORMAT(V.toDate,GET_FORMAT(DATE,'JIS')) AS toDate, firstname, lastname, CustomerEMail, HotelName, field_status_value, CRHotelNodeId, field_rates_from_currency, field_booking_amount_value, BookingNodeID FROM vw_CR_Requests_BookingStatus V WHERE (field_status_value is NULL or field_status_value = 'pending') AND CRHotelNodeId = %d";
// Put together the query and identifier
 
$queryResource = db_query($query, $hotelNodeId);
// Retreive the resource as an array
 
$rsArray = array();
  while(
$row = db_fetch_object($queryResource)){
   
$rsArray[] = $row
  }   
// Build out the page
   
$booking_list ="<h3 class='title'>Please update the status of these bookings.</h3>";
   
$booking_list .= "<table class='views-table view-hotel-bookings-listing'><thead>
<tr><th>Date</th><th>From</th><th>To</th>
<th>Firstname</th><th>Lastname</th><th>Email</th><th>Status</th><th>Update</th></tr>
</thead><tbody>"
;
    foreach(
$rsArray as $rd){
       
$booking_list .="<tr class='even'><td>".$rd->createDate."</td>"
   
."<td>".$rd->fromDate."</td>"
   
."<td>".$rd->toDate."</td>"
   
."<td>".$rd->firstname."</td>"
   
."<td>".$rd->lastname."</td>"
   
."<td>".$rd->CustomerEMail."</td>"
   
."<td>".$rd->field_status_value."</td>"
   
."<td>".drupal_get_form('chic_bookings_listing_update_form_'.$rd->BookingNodeID, $rd)."</td>"
   
."</tr>";
    }
   
$booking_list .="</tbody></table>";
    return
$booking_list;
}
// chic_booking_listing_page

function chic_bookings_listing_update_forms($form_id, $status){
   
$forms = array();
firep($forms);
    if(
0 !== strpos($form_id, "chic_bookings_listing_update_")){
      return
$forms;
    }
   
$forms[$form_id] = array(
       
"callback" => "chic_bookings_listing_update_form_builder",
       
"callback arguments" => array($form_id, $status,)
    );   
    return
$forms;
}

/**
* Implementation of hook_form() to add the update form to the booking listing page
*/
function chic_bookings_listing_update_form_builder(&$form_state, $form_id, $status) {
firep($status, "vw_CR_Requests_BookingStatus");
  static
$js_added = FALSE;
  if (!
$js_added) {
   
$js_added = TRUE;
   
drupal_add_js(drupal_get_path('module', 'chic_request_status') . '/bookings_edit.js');
  }
   
$currency = check_plain($status->field_rates_from_currency);
   
$booking_amount = $status->field_booking_amount_value;
 
$form = array();

     

$form['status'] = array(
       
'#type' => 'select',
       
'#options' => array(
         
0 => t('Change status'),
         
BOOKING_STATUS_STAYED => t('Stayed'),
         
BOOKING_STATUS_DENIED => t('Denied'),
        ),
       
'#attributes' => array('class' => 'booking-status-select'),
      );

     

$form['amount'] = array(
       
'#type' => 'textfield',
       
'#title' => t('Amount [@currency]', array('@currency' => $currency)),
       
'#size' => 3,
       
'#attributes' => array('class' => 'booking-amount-value'),
      );

     

$form['nid'] = array(
       
'#type' => 'hidden',
       
'#value' => intval($status->BookingNodeID),
      );

     

$form['submit'] = array(
       
'#type' => 'submit',
       
'#value' => t('Update'),
      );
  return
$form;
}

function

chic_bookings_listing_update_form_validate($form, &$form_state){
  if (empty(
$form_state['values']['nid'])) {
   
form_set_error('', t('Could not find nid value.'));
  }
 
$status = $form_state['values']['status'];
  if (empty(
$status)) {
   
form_set_error('status', t('Please select a status'));
  }

  if (

$status != BOOKING_STATUS_STAYED) {
    return;
  }

 

$amount = $form_state['values']['amount'];
  if (empty(
$amount) ){
   
form_set_error('amount', t('Please enter an amount value'));
    }
    if(!
is_numeric($amount)) {
   
form_set_error('amount', t('Please enter an numeric amount value'));
  }   
}
//chic_bookings_listing_update_form_validate

function chic_bookings_listing_update_form_submit($form, &$form_state){
 
$amount = $form_state['values']['amount'];
 
$status = check_plain($form_state['values']['status']);
 
$nid = $form_state['values']['nid'];
 
$booking_node = node_load($nid);

  if (

$status == BOOKING_STATUS_STAYED) {
   
$booking_node->field_booking_amount = array(array('value' => $amount));
   
$booking_node->field_status = array(array('value' => $status));
   
node_save($booking_node);
  } elseif (
$status == BOOKING_STATUS_DENIED) {
   
$booking_node->field_booking_amount = array(array('value' => 0));
   
$booking_node->field_status = array(array('value' => $status));
   
node_save($booking_node);
  }

 

drupal_set_message(t('Your booking was updated.'));
}
//chic_bookings_listing_update_submit
?>