Formproc Documentation
As with all contributions to Drupal, this code is freely available under the terms of the GNU General Public License.
File: README.txt (461 lines)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
THE FORMPROC MODULE
Alex Reisner (www.alexreisner.com/drupal)
Version 4.6
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Formproc defines an API for handling forms in Drupal in which all aspects of
a form (rendering, validation, and filtering) are defined in one place. It
parameterizes fields in a unified way and uses arrays instead of function
calls. Many thanks to PEAR's wonderful QuickForm package (see
http://pear.php.net/package/HTML_QuickForm) from which features and
inspiration have been drawn.
PART 1: FORMPROC BASICS
INTENDED USAGE
Call formproc_render_form($form_def) from your module's implementation of
hook_form(), and formproc_validate_form($form_def) from hook_validate(). See
CONVERTING YOUR MODULES below for more detailed instructions. See also the
formproc home page (www.alexreisner.com/drupal/modules/formproc) for examples.
INSTALLATION
To install Formproc:
1) Copy the entire formproc directory to your modules directory.
2) Go to admin/modules and enable the module.
THE $form_def ARRAY
The $form_def array defines how your form looks and functions and should
be accessible from anywhere in your module. The best way to do this is to
create a function module_form_def() which creates and returns it. $form_def
is an associative array: keys are whatever you like (but 'NODE', 'EACH',
and 'ATTRIBS' are reserved). One element in the array is REQUIRED:
$form_def['NODE'] =& $node; // MUST be a reference!
This is to make the $node accessible to (and modifiable by) validate,
filter, and render functions. Note that your module_form_def() function
must take &$node as a parameter ('&' for reference).
Elements of $form_def other than 'NODE', 'EACH', AND 'ATTRIBS' are
associative arrays defining fields and may use any of the following
properties ('name' is the only required property--see 'name' property
comments below).
GENERAL PROPERTIES
'type' => string, default 'textfield'; supported types are:
STANDARD FIELDS
'textfield' (or 'text')
'textarea'
'password'
'radios' (or 'radio')
'checkbox' (value will be 0 or 1)
'checkboxes' (value will be array)
'select'
'file'
'hidden'
STANDARD BUTTONS
'submit'
'image' (be sure to specify alt and src attribs)
'button'
'reset'
CUSTOM FIELD
'custom'
PSEUDOFIELDS
'header'
'separator'
'name' => string, required for all fields, optional for buttons and
pseudo-fields; name used in HTML tag and storage in node;
optional if type is submit, button, image, or reset in which
case default is 'op' (make sure you understand what you're
doing if you change this); if adding a field that is rendered
elsewhere (e.g., filter_form()), you must still specify the
name property so the field can find its value in the node
for validation and filtering purposes
RENDERING PROPERTIES
'label' => string, default ''; human-readable field name (if omitted,
the 'name' is used with the first letter capitalized and
underscores converted to spaces); for submit, button, image,
and reset types the label property sets the value attribute
of the HTML tag
'help' => string, default ''; help or descriptive text, usually shown
in small type beneath the field
'default' => mixed, default ''; string if field can have one value,
array if a multiple select or checkbox set; the default
appears in a field when creating (not when editing) a node
'length' => int, default 20; size of text input, cols of a textarea
'maxlength' => int, default unspecified; rows in a textarea
'attribs' => array, default empty; extra HTML tag attributes as
name => value pairs
'choices' => array, default empty; if type is radio, checkbox, or select
choices are specified as value=>label pairs; some functions
are also available for getting choices quickly (see
MULTIPLE CHOICE SHORTCUTS below for more info)
'required' => bool, default false
'visible' => bool, default true; if false, becomes a hidden field
'editable' => bool, default true; if false, displays as plain text
'special' => array, default empty; callbacks for adding special features,
usually JavaScript add-ons (see below for callback array
format)
'prehtml' => string, default ''; extra HTML (prepended)
'posthtml' => string, default ''; extra HTML (appended)
'script' => string, default ''; JavaScript related to the field; will
be inserted into the <head> unless FORMPROC_OMIT_SCRIPT is
set (use this if your script is in a separate file); you
should include <script> and </script> tags
'html' => string, default ''; if non-empty, automatic generation is
skipped (else stores auto-generated HTML for the field);
note that specifying HTML manually causes all other
rendering properties to be ignored
PROCESSING PROPERTIES
'value' => mixed; value of a hidden field; use with other field types
for debugging purposes ONLY (to force a value)
'rules' => array, default empty; callbacks (see below for format)
'filters' => array, default empty; callbacks (see below for format)
'flags' => bitmask, default 0; see below for FORMPROC_ flag definitions
RULES, FILTERS, & SPECIALS: CALLBACK FUNCTIONS
The 'rules', 'filters', and 'special' properties each specify a callback
function and its arguments. For example:
'rules' => array('formproc_max_length' => array(20, 'Too long.'))
calls the formproc_max_length() function with arguments 20 and 'Too long.'.
You may use any of the following formats:
1) array('function' => array(arg1, arg2, ...), ...)
2) array('function1', 'function2', ...) [no arguments]
3) 'function' [no arguments]
Note that a callback may be:
1) one of those included with Formproc
(see the .formproc files for a variety of useful options),
2) a built-in PHP function
(e.g., trim() is a good filter), or
3) one you write yourself
(see DEVELOPING RULES, FILTERS & SPECIAL FEATURES below).
GLOBAL FORM PROPERTIES (RULES, FILTERS, FLAGS & EDITABILITY)
There is also a special field in the $form_def array indexed by the string
'EACH' (case sensitive). Filters, rules, and flags defined here are
applied to each field, after any field-specific processing. The 'editable'
property can also be globally set (it's often useful to make an entire form
uneditable for quick display of values to the user).
AUTOMATIC VALIDATION
If a field has 'choices' defined (i.e., it's a radio, checkbox, or select)
the submitted value is always checked against the legal options to prevent
hackers from forging the field by writing their own HTML form and submitting
it. This rule is applied before the field's specific 'rules'. To turn it off
set the FORMPROC_SKIP_CHOICE_CHECK flag.
ERROR MESSAGE DISPLAY
By default, all errors are displayed at the top of the form, as they are
in normal Drupal operation. However, Formproc allows greater control of
error message display through the theme functions:
theme_form_element() // comes with Drupal
theme_formproc_global_form_error() // added by Formproc
To show only one general message at the top (eg, 'Please correct the fields
indicated below.') and specific errors next to each field that has a problem,
simply theme form_element to display the $error that is passed to it, and
theme formproc_global_form_error to display a general message regardless of
what specific errors are registered.
MULTIPLE CHOICE SHORTCUTS
Formproc provides several functions that help you specify the 'options'
property for multiple-choice fields. See comments near each for more details:
formproc_choices_numeric_list
formproc_choices_query
formproc_choices_enum_field
formproc_choices_taxonomy_nodes
formproc_taxonomy_form
formproc_taxonomy_node_form
PSEUDOFIELDS
Pseudofields are valueless fields used for display purposes. There are
two types: 'header' and 'separator'. To use them, just create a field with
type 'header' or 'separator'. For a header you should also set the 'label'
property so that something is displayed. Both are themeable through
theme_formproc_header() and theme_formproc_separator().
THE <FORM> TAG
If you want to modify the method or action attributes or add new attributes,
use $form_def['ATTRIBS']. This is an array of name => value pairs which may
or may not include "method" and "action" keys. If you're making form fields
for insertion into an existing form (as is usual in implementations of
hook_form()), call formproc_render_form() with the $print_container
parameter set to false. This will simply omit the <form> and </form> tags.
CUSTOM FIELDS
When you use a custom field you MUST define the following properties:
'type' = 'custom'
'name' = [a valid field name]
'callback' = [a valid function]
You may also specify other properties like 'flags', 'filters', 'label', etc,
but custom fields vary in which of these properties they support. The power
of custom fields, however, lies in additional field-specific properties. For
example, when using the custom date field included with Formproc you set a
'format' property to specify what components the field should have. Read the
documentation provided for each custom field to find out what properties it
supports, and which are required.
CONVERTING YOUR MODULES
To utilize formproc in a Drupal module you will have to modify two functions
and add a new one. To explain by way of example, below is the code that could
replace forum_form() and forum_validate() in the forum module included with
Drupal. This code recreates the current (version 4.6) functionality and also
adds some features to the 'body' field.
/**
* Implementation of hook_form().
*/
function forum_form(&$node) {
// Assign taxonomy value (copied from original forum.module).
if (!$node->nid) { // new topic
$node->taxonomy[] = arg(3);
}
else {
$node->taxonomy = array($node->tid);
}
/* Usually the implementation of hook_form() is just the two lines below,
the above is necessary due to the structure of the forum module.
*/
$form = forum_form_def($node);
return formproc_render_form($form, false); // false so <form> is omitted
}
/**
* Get the form definition for the forum module.
*
* @param object &$node
* @return array
* The form definition in format specified by formproc module.
*/
function forum_form_def(&$node) {
// Initialize form.
$form = array('NODE'=>&$node);
/* Taxonomy field (using special formproc function). Here we could add an if
block inside the foreach loop if we wanted to modify the field. To change
the label and add some validation we might do something like:
if ($vid == 3) {
$v['label'] = 'Your favorite colors';
$v['rules'] = array('formproc_max_choices' => 3);
}
*/
$tax = formproc_taxonomy_node_form('forum', $node);
foreach ($tax as $vid => $v) {
$form[] = $v;
}
// Body with real-time character count and validation added.
$form['body'] = array(
'name' => 'body',
'type' => 'textarea',
'length' => 60,
'maxlength' => 20,
'help' => 'All topics have a 500 character limit.',
'special' => array('formproc_charcount' => 500),
'rules' => array('formproc_max_length' => 500),
);
/* Format field. There's no special formproc function yet so we don't gain
control over appearance but we could still add rules and filters.
*/
$form['format'] = array(
'name' => 'format',
'html' => filter_form('format', $node->format),
);
return $form;
}
/**
* Implementation of hook_validate().
*
* Check in particular that only a "leaf" term in the associated taxonomy
* vocabulary is selected, not a "container" term.
*/
function forum_validate(&$node) {
$form = forum_form_def($node);
formproc_validate_form($form);
/* A module's implementation of hook_validate is usually a two-liner, but
here, because this validation code is specific to the forum module and
unlikely to be used again, I have left it intact except for one change:
form_set_error($field, $message)
is replaced by:
formproc_report_error($field, $message)
to take advantage of formproc's control of error message display and make
sure display is consistent with those errors found during formproc's
validate routine. If your validation code is likely to be useful in more
than one place, you should write a formproc rule. For details see the
DEVELOPING RULES, FILTERS & SPECIAL FEATURES below.
*/
// Make sure all fields are set properly:
$node->icon = $node->icon ? $node->icon : '';
if ($node->taxonomy) {
// Extract the node's proper topic ID.
$vocabulary = variable_get('forum_nav_vocabulary', '');
$containers = variable_get('forum_containers', array());
foreach ($node->taxonomy as $term) {
if (db_result(db_query('SELECT COUNT(*) FROM {term_data} WHERE tid = %d AND vid = %d', $term, $vocabulary))) {
if (in_array($term, $containers)) {
$term = taxonomy_get_term($term);
formproc_report_error('taxonomy', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => theme('placeholder', $term->name))));
}
else {
$node->tid = $term;
}
}
}
if ($node->tid && $node->shadow) {
$terms = array_keys(taxonomy_node_get_terms($node->nid));
if (!in_array($node->tid, $terms)) {
$terms[] = $node->tid;
}
$node->taxonomy = $terms;
}
}
}
PART 2: EXTENDING FORMPROC
EXTERNAL FILES
If you want to write your own validators, filters, custom fields or special
features for use with Formproc you should NOT modify your formproc.module
file. Simply create a valid PHP file with a .formproc extension, put it in
your modules directory, and Formproc will include it automatically.
DEVELOPING RULES, FILTERS & SPECIAL FEATURES
Should your validating, filtering, or special feature needs extend beyond
the capabilities of the included functions, you may write your own. One
parameter is always auto-prepended to the user-specified ones:
'rules': mixed, the field's value
'filters': mixed, the field's value
'special': array, the field
Your callback function should return something predictable:
'rules': mixed, an error message or boolean false if no error
'filters': mixed, a filtered value
'special': array, a field with special features added
Note that all validators MUST accept an empty value. The 'required' property
of the field should be set when empty string is not acceptable.
DEVELOPING CUSTOM FIELDS
In the event that you need some functionality not provided by Formproc, you
may write your own 'custom' field callback. A 'custom' field may be a field
but it may also be a validator that checks other fields, or a process that
creates a new value based on form data. For this reason we henceforth refer
to them as "custom elements".
Callback functions for custom elements must accept the following parameters:
$op string; operation requested (see details below)
$field array; associative array of field properties (from $form_def)
$node object (reference if desired)
The function should do something based on the $op:
'get_value' return the field's value
'render' return an HTML representation of the field
'validate' return nothing, but register errors (see formproc_report_error)
Note that custom fields can compose a new value regardless of whether they
actually appear in a form. For example, you may have your custom field
callback return empty string on 'render' but still return a value (say, the
concatenation of 'year', 'month', and 'day' fields) on 'get_value'. This
value will become a part of the $node like every regular fields' values,
indexed by the element's 'name' property.
Note that cross-field validation is accomplished through custom elements that
respond appropriately to $op 'validate'. See formproc_field_compare_rule()
in default.formproc for an example.
PART 3: TODO LIST
(in descending priority)
- Translations (.po) for date field. (About 15 ready to go, thanks to
QuickForm. If anyone wants to do this, I can send you the translations.)
- Special features:
- JavaScript calendar icon/date selector
- JavaScript <textarea> copy to clipboard
- JavaScript <textarea> overwrite with paste from clipboard
- Allow editable property for custom fields (add $op 'render_uneditable'?).
- Allow placing multiple elements in the same row without resorting to a
custom field. Unobtrusive solution: a 'group' property (arbitrary
string). Fields in the same 'group' display on same row (or according to a
common template/theme function). See QuickForm's rendering engine...
- Fix bugs in _formproc_get_value() for fields with []s in name:
- default value doesn't work because of order of logic in the if statements
- array for value (eg, a taxonomy field) won't work--value is not found
- Provide a way to use GET and bypass edit[...] field naming scheme (eg, for
search).
- Consider introducing select_multiple field type so don't have to manually
specify multiple="multiple" attribute (might make processing safer too).
Alternatively, the 'length' property could be used to specify the size
attribute.
- Create way for rules to render JavaScript for client-side processing.
- Nicer display of frozen file field (use user's local file name...tough).
- Clean up HTML generation for uneditable fields (eg, should there be a
hardcoded <br /> separating radios/checkboxes?).
- Add auto complete text field type (when upgrade to 4.7).
- Help module developer avoid stomping form fields that come before or after
hook_form() like 'name', 'date', etc.