[development] Forms API II: Return of the Chx (you really need to read this)

Jeff Eaton jeff at viapositiva.net
Fri Aug 18 20:08:13 UTC 2006


Greetings, folks! There's been talk for the past week or so about the
'pull model' patch for Forms API that chx rolled. It's been committed
after some furious rounds of polishing, and it removes many barriers for
advanced use of the Forms API. It DOES, however, require some
conversion/updates to existing functions that generate and display
forms. Here's the quick and easy guide to the changes.
 
1) What changed and why?
Previously, Forms API used a 'push model.' You built an array of form
elements, then called drupal_get_form($form_id, $form) to 'push' it into
the API for processing and rendering. This worked OK for many
situations, but also made it terribly cumbersome for programmatically
submitting forms (for import/export). It also made workflow-oriented
systems that chained multiple forms together a lot more hacky and kludgy
than necessary.
 
Now, the Forms API uses a 'pull model.' You call
drupal_get_form($form_id), and it 'pulls' the form from a builder
function, then processes and renders it as appropriate. That means that
existing modules need to separate their form-building code from their
general page and content rendering code -- most already do this, but
it's an important distinction, and making it 'official' allows the
system to treat forms more consistently.
 
2) So, how do I convert my code?
By default, Forms API will check for a function that shares the same
name as the form's ID. For example:
 
<?php
function mymodule_edit_record($record) {
  $output = 'This is my edit page!';
  $form['my_field'] = array(
    '#type' => 'textfield',
    '#title' => 'Record name',
    '#default_value' => $record->name,
  );
  $output .= drupal_get_form('record_edit_form', $form);
  return $output;
}
?>
 
Would need to change to the following:
 
<?php
function mymodule_edit_record($record) {
  $output = 'This is my edit page!';
  $output .= drupal_get_form('mymodule_edit_record_form', $record);
  return $output;
}
 
function mymodule_edit_record_form($record) {
  $form['my_field'] = array(
    '#type' => 'textfield',
    '#title' => 'Record name',
    '#default_value' => $record->name,
  );
  return $form;
}
?>
 
Once that 'builder' function is defined, you can call
drupal_get_form('mymodule_edit_record_form', $record) anywhere in your
code, and the API will handle retrieving it. In fact, if your form page
was simple and just consisted of building a form, then returning the
results of drupal_get_form(), you can now use the following trick when
you define the menus for your module:
 
<?php
function mymodule_menu($may_cache = TRUE) {
  $items[] = array(
    'path' => 'mymodule/form-page',
    'title' => t('edit a record'),
    'callback' => 'drupal_get_form',
    'callback arguments' => array('mymodule_form_builder')
    'type' => MENU_LOCAL_TASK
  );
  return $items;
?>
 
In the above example, instead of putting a custom page-generation
function in as your callback, and adding a single
drupal_get_form($form_id) call as its body, you can just tell the menu
system to call drupal_get_form() with your form ID.
 
3) What if I have several forms that use different IDs, but share the
same layout, submit and validate functions, and so on?
We're glad you asked. Modules can now implement hook_forms() to handle
complex mapping of form IDs to builder functions. It's how node.module
maps the id for each node type editing form to the central form-building
function for the node edit screen. Here's an example:
 
<?php
function mymodule_hook_forms() {
  $forms['mymodule_first_form'] = array(
    'callback' => 'mymodule_form_builder',
    'callback arguments' => array('some parameter'),
  );
  $forms['mymodule_second_form'] = array(
    'callback' => 'mymodule_form_builder',
    'callback arguments' => array('a different parameter'),
  );
  return $forms;
}
 
function mymodule_first_page() {
  return drupal_get_form('mymodule_first_form');
}
 
function mymodule_second_page() {
  return drupal_get_form('mymodule_second_form');
}
 
function mymodule_form_builder($param) {
  $form = array()
  // build the form here
 
  if ($param == 'some parameter') {
    // Add another field, change a default value...
  }
  
  // This is used the way $callback was in 4.7 Forms API: it is used as
the prefix for
  // _submit() and _validate() functions to process the form.
  $form['#base'] = 'mymodule_form';
  
  return $form;
}
?>
 
In the above code, the mymodule_first_page() and mymodule_second_page()
functions display slight variations on the same form. Because no
functions named 'mymodule_first_form' or 'mymodule_second_form' exist,
Forms API looks to hook_forms() to find the builder function. Each entry
in the array returned by hook_forms() can also define callback arguments
that will be passed to the builder function.
 
In the mymodule_form_builder() function, the use of $form['#base'] is
also important to note. It's a lot like the little-used but helpful
$callback parameter that was used in the 4.7 version of
drupal_get_form(). If $form['#base'] is set, its value will be used to
look up the proper submit, validate, and theme functions for the form
rather than the form's ID.
 
In the future, hook_forms() will also allow us to capture other meta
data about forms like access restrictions, workflow information, and so
on. For now, though, the callback and optional callback arguments are
all you need to worry about if you use it.
 
4) OK, that's great. How do I programmatically submit form data, then?
Aha, the meat of it. Now, it's possible to completely circumvent
drupal_get_form() and all of its submission, rendering, and redirection
logic. You just pull up a form using its ID, and populate the new
$form['#post']['edit'] element with the form values you'd like to
submit. Then call drupal_process_form(). For example:
 
<?php
function mymodule_create_user() {
  // register a new user
  $form = drupal_retrieve_form('user_register');
  $form['#post']['edit']['name'] = 'robo-user';
  $form['#post']['edit']['mail'] = 'robouser at example.com';
  $form['#post']['edit']['pass'] = 'password';
  drupal_process_form('user_register', $form);
  
  if (form_get_errors()) {
    // whoops, something went wrong!
  }
}
?>
 
And that, dear friends, is about it. To recap:
 
1) drupal_get_form() now takes a $form_id, NOT a $form_id and a $form.
2) You need a builder function with the name of the $form_id that
returns the fully constructed $form array
3) If your page content is JUST a form, you can use drupal_get_form as
your menu callback, with $form_id as your menu callback argument.
4) If you need to use the same $form for multiple $form_ids, use
hook_forms()
5) If you want the submit, validate, and theme functions to use naming
different than the $form_id, set $form['#base']
6) Use drupal_retrieve_form(), $form['#post']['edit'], and
drupal_process_form() to programmatically submit forms.
 
Have fun!
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.drupal.org/pipermail/development/attachments/20060818/3194cb13/attachment-0001.htm


More information about the development mailing list