Home » HTML » HTML_QuickForm2 » Manual
HTML_QuickForm2 is a PHP5 rewrite of HTML_QuickForm and HTML_QuickForm_Controller packages. It provides methods to create, validate and render HTML forms. Supports all form elements defined by HTML standard, provides several custom elements. Server-side and client-side validation, several common rules provided. Multipage forms (tabbed forms and wizards) Pluggable elements, rules, renderers and renderer plugins Major advantages over PHP4 versions: Most of the package's functionality is covered by unit tests. DOM-like API for building the form structure, new streamlined API for elements' values handling. Default rendering without tables (inspired by HTML_QuickForm_Renderer_Tableless). Renderer plugins for elements with complex rendering needs. Ability to chain validation rules with 'and' and 'or'. Client-side validation can run "live" on changing the form fields, validation errors are displayed near the fields instead of in alert() (similar to HTML_QuickForm_DHTMLRulesTableless).
Introduction
This chapter is intended both for those completely unfamiliar with HTML_QuickForm2 and for users of PHP4 version of HTML_QuickForm who need either an overview of package features or tips for migrating their scripts. The chapter also includes a list of examples installed with the package: those are longer and more complex than examples within documentation.
QuickForm2 Tutorial
QuickForm2 Tutorial – Description of basic package features
Your first QuickForm2-based form
Builds and processes a form with a single input field
<?php
// Load the main class
require_once 'HTML/QuickForm2.php';
// Instantiate the HTML_QuickForm2 object
$form = new HTML_QuickForm2('tutorial');
// Set defaults for the form elements
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'name' => 'Joe User'
)));
// Add some elements to the form
$fieldset = $form->addElement('fieldset')->setLabel('QuickForm2 tutorial example');
$name = $fieldset->addElement('text', 'name', array('size' => 50, 'maxlength' => 255))
->setLabel('Enter your name:');
$fieldset->addElement('submit', null, array('value' => 'Send!'));
// Define filters and validation rules
$name->addFilter('trim');
$name->addRule('required', 'Please enter your name');
// Try to validate a form
if ($form->validate()) {
echo '<h1>Hello, ' . htmlspecialchars($name->getValue()) . '!</h1>';
exit;
}
// Output the form
echo $form;
?>
Lets review the above example step by step.
Building the form
The line
<?php
$form = new HTML_QuickForm2('tutorial');
?>creates an instance of HTML_QuickForm2
that will contain objects representing
form elements and other necessary information. We only pass the form's id to the constructor, which means that default
values will be used for other parameters. In particular, the form's method will default to
POST and the form's action to the current file. When using QuickForm2, it is
easier to keep all the form related logic in one file.
Next we add a DataSource
<?php
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'name' => 'Joe User'
)));
?>containing the default value 'Joe User' for name element.
DataSources are objects that provide elements' incoming values, they are searched in the order
they were added to the form. When constructor of HTML_QuickForm2
considers the form submitted, it will automatically add a DataSource with submit values, so
search will not reach the DataSource added above. However, when submit DataSource is not there,
default value will be taken from the above DataSource.
To improve the presentation, we are adding a fieldset element to the form first:
<?php
$fieldset = $form->addElement('fieldset')->setLabel('QuickForm2 tutorial example');
?>and set its label (it will be output as <legend>, actually).
We then add a text input box and a submit button to the fieldset:
<?php
$name = $fieldset->addElement('text', 'name', array('size' => 50, 'maxlength' => 255))
->setLabel('Enter your name:');
$fieldset->addElement('submit', null, array('value' => 'Send!'));
?>Note that most of the setter methods are returning $this, so it is
possible to chain method calls. Note also that we can arbitrarily nest fieldsets and other
containers (you can't nest another HTML_QuickForm2 instance, however).
Checking input
The line
<?php
$name->addFilter('trim');
?>adds a filter for the element's value - the function that will be applied to it when getValue() is called. In this case it is a builtin trim() function, but can be any valid callback. Thus we will strip all leading and trailing whitespace from the name, as we do not need it and as we want to be sure that a name was entered, not just some spaces.
Next we define a rule for the field:
<?php
$name->addRule('required', 'Please enter your name');
?>This means that QuickForm2 will display an error message if the name was not entered. Note also that QuickForm2 will automatically mark required fields in the form.
Validating and processing
We now have the form built and rules defined and need to decide whether to process it or display:
<?php
if ($form->validate()) {
// Do some stuff
}
?>
HTML_QuickForm2::validate() method
will consider the form valid (i.e. return TRUE) if some data was actually submitted and all
the rules defined for the form were
satisfied. In our case this means that 'name' element was not left
empty.
If the form is validated we need to process the values
<?php
echo '<h1>Hello, ' . htmlspecialchars($name->getValue()) . '!</h1>';
exit;
?>This is an example, in your scripts you'll usually want to store the values somewhere and to redirect to some other page to prevent a duplicate submit.
The last line is pretty easy:
<?php
echo $form;
?>If the form is not valid, which means that it either was not yet submitted or that there were errors, it will be displayed. Error messages (if any) will be displayed near the corresponding elements.
Migration from HTML_QuickForm
Migration from HTML_QuickForm – Step-by-step guide for porting your scripts to HTML_QuickForm2
Creating elements
Constructors of all the elements now have the same list of parameters:
- string
$name Element name
- string|array
$attributes HTML attributes
- array
$data Additional element-specific data. A
'label'key in this array is understood by every element and is used for element's label.
Consequently HTML_QuickForm2_Factory::createElement() and HTML_QuickForm2_Container::addElement() that pass their arguments to the element's constructor have fixed signatures, too.
For example, the following HTML_QuickForm code which adds a radio button and a select element to the form
<?php
$form->addElement('radio', 'iradTest', 'Test Radio Buttons:', 'Check the radio button #1', 1,
array('class' => 'fancyRadio'));
$form->addElement('select', 'iselTest', 'Test Select:', array('A'=>'A', 'B'=>'B', 'C'=>'C', 'D'=>'D'),
array('size' => 3, 'multiple' => 'multiple'));
?>in HTML_QuickForm2 will be transformed to
<?php
$form->addElement('radio', 'iradTest', array('class' => 'fancyRadio', 'value' => 1),
array('label' => 'Test Radio Buttons:', 'content' => 'Check the radio button #1'));
$form->addElement('select', 'iselTest', array('size' => 3, 'multiple' => 'multiple'),
array('label' => 'Test Select:', 'options' => array('A'=>'A', 'B'=>'B', 'C'=>'C', 'D'=>'D'));
?>Another possibility is using fluent interfaces
<?php
$form->addElement('radio', 'iradTest', array('value' => 1))
->addClass('fancyRadio')
->setLabel('Test Radio Buttons:')
->setContent('Check the radio button #1');
$form->addElement('select', 'iselTest', array('size' => 3, 'multiple' => 'multiple'))
->setLabel('Test Select:')
->loadOptions(array('A'=>'A', 'B'=>'B', 'C'=>'C', 'D'=>'D'));
?>which has the benefits of improved readability and code completion in IDEs.
Adding elements to the form
Unlike HTML_QuickForm, where nesting of form elements was limited (elements could be added either
directly to the form or to a group added directly to the form) HTML_QuickForm2 supports unlimited
nesting. Elements that can contain other elements are subclasses of HTML_QuickForm2_Container
which implements DOM-like API: appendChild(), insertBefore(), removeChild(), getElementById(), getElementsByName().
Convenience method addElement() that creates an
element of the given type and adds it to the Container is still available, too. Thanks to
method overloading you can also perform addEltype() calls where
'eltype' is an element type known to HTML_QuickForm2_Factory:
<?php
$form->addText('testTextBox', array('style' => 'width: 20ex;'));
?>
Note that HTML_QuickForm2 does not use HTML tables for rendering the form, so there is no equivalent to 'header' element of HTML_QuickForm, fieldsets should be used to group form elements. Also there is no special HTML_QuickForm::addGroup(), groups are treated as any other element (addGroup() will work, though, thanks to abovementioned method overloading). Thus code from HTML_QuickForm
<?php
$form->addElement('header', 'personal', 'Personal Information');
$form->addGroup(array(
HTML_QuickForm::createElement('text', 'first', 'First', 'size=10'),
HTML_QuickForm::createElement('text', 'last', 'Last', 'size=10')
), 'name', 'Name:', ', ');
?>can be changed to the following in HTML_QuickForm2
<?php
$fieldset = $form->addFieldset('personal')->setLabel('Personal Information');
$group = $fieldset->addGroup('name')->setLabel('Name:')->setSeparator(', ');
$group->addText('first', 'size=10', array('label' => 'First'));
$group->addText('last', 'size=10', array('label' => 'Last'));
?>
Setting default values
Elements' incoming values in HTML_QuickForm2 are kept in an array of DataSources, which are searched for a value in the order they were added to the form. A DataSource containing submit values will be added automatically to that list if the form was submitted. The equivalent to HTML_QuickForm::setDefaults() call
<?php
$form->setDefaults(array(
'foo' => 'default foo value',
'bar' => 'default bar value'
));
?>is the following call to HTML_QuickForm2::addDataSource():
<?php
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'foo' => 'default foo value',
'bar' => 'default bar value'
)));
?>
It is possible to replace the whole array of form's DataSources using HTML_QuickForm2::setDataSources(), so you can change HTML_QuickForm::setConstants() call
<?php
$form->setConstants(array(
'const' => 'this will override submitted value'
));
?>to
<?php
$datasources = $form->getDataSources();
array_unshift($datasources, new HTML_QuickForm2_DataSource_Array(array(
'const' => 'this will override submitted value'
)));
$form->setDataSources($datasources);
?>Here we use the fact that new DataSource will be searched before one containing submitted values, so its values will override submitted ones.
Getting the elements' values
While HTML_QuickForm contained several methods for getting form's and elements' values, HTML_QuickForm2 only has getValue() and getRawValue(), the difference being that the former applies filters to the value and the latter does not. There is no longer a special "submit value" stored separately from form element and returned by HTML_QuickForm::getSubmitValue() / HTML_QuickForm::getSubmitValues().
Calling getValue() on an element returns the element's submit value or NULL for elements that do not currently have a submit value or can not have such a value at all. The value also passes "intrinsic validation" to make sure that it could possibly originate from that element (e.g. select will only return values for options that were added to select). This behaviour is closest to that of old HTML_QuickForm::exportValue().
Calling getValue() on a HTML_QuickForm2 object is equivalent to old HTML_QuickForm::exportValues().
Filters
Main differences to filters in HTML_QuickForm:
- Filters are added to the element rather than to the form and applied to the element's value rather than a separate "submit value".
- Additional arguments can be passed to filter callbacks.
- There are separate HTML_QuickForm2_Node::addFilter() and HTML_QuickForm2_Node::addRecursiveFilter() methods. The former will be applied directly to element's value on getValue() call and the latter will be propagated to contained elements if the element is a Container or applied recursively to the value if the element's value is an array.
The following HTML_QuickForm::applyFilter() calls in HTML_QuickForm
<?php
$form->applyFilter('__ALL__', 'trim');
$form->applyFilter('amount', 'intval');
?>can be converted to the following in HTML_QuickForm2
<?php
$form->addRecursiveFilter('trim');
$amount->addFilter('intval');
?>
Rules and validation
Main differences to HTML_QuickForm:
- Rules are added to the element rather than to the form and applied to the element's value rather than a separate "submit value".
- Rules can be chained with and_() and or_() methods allowing for very complex validation scenarios even with builtin Rules.
Simple addRule() call in HTML_QuickForm
<?php
$form->addRule('username', 'Username should be at least 5 symbols long', 'minlength', 5, 'client');
?>can be changed to the following in HTML_QuickForm2
<?php
$username->addRule('minlength', 'Username should be at least 5 symbols long', 5,
HTML_QuickForm2_Rule::CLIENT_SERVER);
?>
There is no longer a special HTML_QuickForm::addGroupRule() method. 'nonempty' /
'required' Rule can directly validate a group or other Container making sure
that it contains a given number of nonempty elements, there is also a special
'each' Rule that applies a given template Rule to each element in a Container.
Thus the following addGroupRule() calls
<?php
$form->addGroupRule('phoneNo', 'Please fill all phone fields', 'required', null, 3, 'client');
$form->addGroupRule('phoneNo', 'Values must be numeric', 'regex', '/^\\d+$/', 3, 'client');
?>can be changed to
<?php
$phoneNo->addRule('required', 'Please fill all phone fields', 3,
HTML_QuickForm2_Rule::CLIENT_SERVER);
$phoneNo->addRule('each', 'Values must be numeric',
$phoneNo->createRule('regex', '', '/^\\d+$/'),
HTML_QuickForm2_Rule::CLIENT_SERVER);
?>
There is no longer a special addFormRule() method. You can achieve similar behaviour by creating a subclass of HTML_QuickForm2_Rule, implementing its validateOwner() and setOwnerError() methods and adding an instance of that Rule directly to the form:
<?php
class FormRule extends HTML_QuickForm2_Rule
{
protected function validateOwner()
{
$fooValue = $this->owner->getElementById('foo')->getValue();
$barValue = $this->owner->getElementById('bar')->getValue();
return someComplexCheck($fooValue, $barValue);
}
protected function setOwnerError()
{
$this->owner->getElementById('foo')->setError('foo error');
$this->owner->getElementById('bar')->setError('bar error');
}
}
$form->addRule(new FormRule($form));
?>Note, though, that it may be easier to use Rule chaining instead in most cases.
File
elements no longer require special 'uploadedfile' and
'filename' rules, standard 'required' and
'regex' can be used (client-side as well).
There is now a javascript library provided with the package which contains code necessary for client-side validation to run. This library should be included in the page if you intend to use client-side validation.
Outputting the form
Basic form output in HTML_QuickForm2 is quite easy thanks to a magic __toString() method:
<?php
echo $form;
?>Under the hood the package uses Renderer setup similar to that of HTML_QuickForm. Currently HTML_QuickForm2 contains ports of Default and Array renderers from HTML_QuickForm, renderers for specific template engines are unlikely to be added to the base package.
Method that accepts a Renderer instance to output the element is now called render() rather than accept() as in HTML_QuickForm.
Default renderer in HTML_QuickForm2 is different from that in HTML_QuickForm:
- Form is output without HTML tables, similar to HTML_QuickForm_Renderer_Tableless. There is a default CSS file to properly style its output.
- Template syntax in Default renderer is a bit less verbose.
- Rules for finding an appropriate template for an element are more complex.
Consider reviewing the examples installed with the package, they contain useful bits on styling the form.
It is also quite easy to output the form manually by iterating over its elements,
<?php
foreach ($form as $element) {
echo '<label for="' . $element->getId() . '">' . $element->getLabel()
. '</label> ' . $element->__toString() . '<br />';
}
?>but client-side validation rules are built in the course of the render() call, so there is a special Stub renderer to assist with manual output.
If you are using client-side validation or javascript-backed elements like hierselect or repeat, you should take care that necessary javascript libraries are included in the page before the form. Consult the section on Javascript support.
Additional examples
Additional examples – Usage examples installed with the package
Locating examples
If you installed HTML_QuickForm2 with PEAR installer, then its usage examples
are in HTML_QuickForm2/examples directory under PEAR's
doc_dir. If you have trouble finding where doc_dir
is, you can use config-show command of PEAR
installer.
List of example files
basic-elements.phpForm showing all built-in elements.
builtin-rules.phpForm showing all built-in rules with server-side and client-side validation.
default-renderer.phpShows how to customize output of Default Renderer.
dualselect.phpA custom element with a Renderer plugin and additional Javascript library.
hierselect-ajax.phpShows how to use AJAX requests to load additional options for a Hierselect element.
repeat.phpDemonstrates the Repeat element.
controller/simple.phpSingle-page form taking advantage of HTML_QuickForm2_Controller infrastructure.
controller/tabbed.phpTabbed multipage form: contains buttons that allow going to any page of the form whether current one is valid or not. The global
'Submit'button will not allow form processing, however, unless all pages are valid.
controller/wizard.phpWizard-like multipage form: pages contain
'Next'and'Back'buttons and you can't go to the next page unless the current page is valid.
renderers/array-twig.phpShows how to use Array Renderer together with Twig template engine.
Form elements
This chapter lists all element classes available in HTML_QuickForm2 and describes their base and element-specific API.
Base Element API
Base Element API – Common methods for Elements and Containers
Base classes
Element classes in HTML_QuickForm2, including HTML_QuickForm2 itself, are descended from either HTML_QuickForm2_Container or HTML_QuickForm2_Element, class tree for those two abstract classes given below. Common API for elements is mostly defined in HTML_QuickForm2_Node.
-
HTML_Common2
-
HTML_QuickForm2_Node
- HTML_QuickForm2_Container - a common parent class for form elements that can contain other elements (e.g. fieldsets, groups).
- HTML_QuickForm2_Element - a common parent class for "scalar" form elements.
-
HTML_QuickForm2_Node
Handling HTML attributes
As can be seen from the above tree, all elements are descended from HTML_Common2, so you have access to attribute-handling methods defined in that class. Please refer to HTML_Common2 documentation for more info.
Many elements in HTML_QuickForm2 take advantage of $watchedAttributes
feature of HTML_Common2 to prevent e.g. changing method
attribute of <form> tag and type attribute of
<input> tag or to update the element's value when its name changes.
As all elements need an id attribute for proper
<label> output and for client-side validation, such an attribute will be
automatically generated based on element's name if not given explicitly.
Automatic generation of id attributes
<?php
echo HTML_QuickForm2_Factory::createElement(
'text', 'name', array('id' => 'explicitId')
)->getAttribute('id') . "\n";
echo HTML_QuickForm2_Factory::createElement('text', 'name')
->getAttribute('id') . "\n";
echo HTML_QuickForm2_Factory::createElement('text', 'name')
->getAttribute('id') . "\n";
echo HTML_QuickForm2_Factory::createElement('text', 'name[key]')
->getAttribute('id') . "\n";
echo HTML_QuickForm2_Factory::createElement('submit')
->getAttribute('id') . "\n";
?>
The above code will output:
explicitId
name-0
name-1
name-key-0
qfauto-0
Note that unique ids are generated and that by default an index is appended to a generated
id. If you want to generate an id attribute equal to
name by default, you can set an option named
'id_force_append_index' to FALSE
Automatic generation of id attributes without mandatory indexes
<?php
HTML_Common2::setOption('id_force_append_index', false);
echo HTML_QuickForm2_Factory::createElement(
'text', 'name', array('id' => 'explicitId')
)->getAttribute('id') . "\n";
echo HTML_QuickForm2_Factory::createElement('text', 'name')
->getAttribute('id') . "\n";
echo HTML_QuickForm2_Factory::createElement('text', 'name')
->getAttribute('id') . "\n";
echo HTML_QuickForm2_Factory::createElement('text', 'name[key]')
->getAttribute('id') . "\n";
echo HTML_QuickForm2_Factory::createElement('submit')
->getAttribute('id') . "\n";
?>
The above code will output:
explicitId
name
name-0
name-key
qfauto-0
Methods defined in HTML_QuickForm2_Node
Constructors of all the elements have the same list of parameters, as defined in HTML_QuickForm2_Node:
- string
$name Element name
- string|array
$attributes HTML attributes
- array
$data Additional element-specific data. A
'label'key in this array is understood by every element and is used for element's label. Other possible keys are described in the list of built-in elements.
As a result, HTML_QuickForm2_Factory::createElement() and HTML_QuickForm2_Container::addElement() that pass their arguments to the element's constructor also have constant list of parameters, the first being element type and subsequent ones corresponding to constructor parameters.
Getters and setters for miscellaneous element properties: setName() / getName(), setId() / getId(), setLabel() / getLabel(), getContainer(), getData(), getType().
Setter methods in HTML_QuickForm2 tend to return
$this, so that it is possible to chain their invocations:<?php $text->setName('aText') ->setLabel('Text input field:'); ?>
Methods for getting and changing elements' values: setValue(), getValue(), getRawValue(). Their detailed description is in the section on values and data sources.
Filtering the elements' values is done by addFilter() and addRecursiveFilter() methods. The former is applied directly to element's value on getValue() call and the latter is propagated to contained elements if the element is a Container or applied recursively to the value if element's value is an array.
Validation-related methods: setError() / getError(), createRule(), addRule() / removeRule(), isRequired(). Described in the section on validation.
Output-related methods
The elements implement magic __toString() method so they can be used in string contexts:
<?php
$element = new HTML_QuickForm2_Element_InputText(
'textBox', array('size' => 20, 'id' => 'textBoxId')
);
echo $element;
?>with output being
<input type="text" id="textBoxId" size="20" name="textBox" />
Complex output needs of form elements (including HTML_QuickForm2 itself) are covered by render() method accepting a specialized Renderer object.
A helpful feature of HTML_QuickForm2 is the ability to "freeze" form elements, displaying their values without HTML input tags. This may be used for an additional confirmation step after form submit or for sending a filled form via email, among other things. Two methods deal with this: toggleFrozen() and persistentFreeze(), the former toggling the "frozen" status and the latter "persistent freeze" behaiour. If persistent freeze is on, element's value will be kept (and possibly submitted) in a hidden field.
Freezing the element
<?php
$box = new HTML_QuickForm2_Element_InputCheckbox(
'aBox', array('value' => 'boxValue', 'checked' => 'checked')
);
echo $box . "\n\n";
$box->toggleFrozen(true);
echo $box . "\n\n";
$box->persistentFreeze(false);
echo $box;
?>the above code results in
<input type="checkbox" value="boxValue" checked="checked" name="aBox" id="aBox-0" />
<code>[x]</code><input type="hidden" name="aBox" value="boxValue" id="aBox-0" />
<code>[x]</code>
Base Container API
Base Container API – Methods defined in HTML_QuickForm2_Container class
Working with child elements
In addition to methods defined in HTML_QuickForm2_Node, Container defines DOM-like API for handling of child elements: appendChild(), insertBefore(), removeChild(), getElementById(), getElementsByName(). Those who have worked with Javascript or PHP's DOM extension should find these familiar.
DOM-like API for Container
<?php
$fieldset = new HTML_QuickForm2_Container_Fieldset();
$radioOne = $fieldset->appendChild(
new HTML_QuickForm2_Element_InputRadio('aRadio', array('id' => 'radioOne'))
);
$radioThree = $fieldset->appendChild(
new HTML_QuickForm2_Element_InputRadio('aRadio', array('id' => 'radioThree'))
);
$radioTwo = $fieldset->insertBefore(
new HTML_QuickForm2_Element_InputRadio('aRadio', array('id' => 'radioTwo')),
$radioThree
);
echo $fieldset->getElementById('radioOne') . "\n\n";
$fieldset->removeChild($radioOne);
foreach ($fieldset->getElementsByName('aRadio') as $radio) {
echo $radio . "\n";
}
?>will output
<input type="radio" value="on" id="radioOne" name="aRadio" />
<input type="radio" value="on" id="radioTwo" name="aRadio" />
<input type="radio" value="on" id="radioThree" name="aRadio" />
A few convenience methods are also available: getElements() returns an
array with all the Container's elements and addElement() creates an
element of a given type and adds it to the Container. Thanks to method overloading you can also
perform addEltype() calls where 'eltype' is an element type
known to HTML_QuickForm2_Factory
(the next section contains the
list of such types and relevant examples).
SPL interfaces
Container also implements Countable and IteratorAggregate SPL interfaces, allowing to easily count the immediate children and iterate over them. It also has getRecursiveIterator() method which returns an instance of HTML_QuickForm2_ContainerIterator for recursive iteration over all child elements.
SPL interfaces support
<?php
$outer = new HTML_QuickForm2_Container_Fieldset();
$inner = $outer->addElement('fieldset')->setId('inner');
$inner->addElement('text', 'textName')->setId('textId');
echo count($outer) . "\n";
foreach ($outer as $child) {
echo $child->getId() . "\n";
}
echo "\n";
foreach ($outer->getRecursiveIterator() as $child) {
echo $child->getId() . "\n";
}
?>The above code will output
1
inner
inner
textId
Form API
Form API – Methods of HTML_QuickForm2 class
Overview
HTML_QuickForm2 is a class representing HTML form. It is a subclass of HTML_QuickForm2_Container and thus inherits the API of Container and its ancestors. Only a few additional form-specific methods are defined (and a few previously protected methods are declared public).
Those familiar with HTML_QuickForm package should
notice that this approach is quite different from that of HTML_QuickForm
class which defined dozens of custom methods. As a result QuickForm2.php
file containing HTML_QuickForm2 class is an order of magnitude smaller
than QuickForm.php containing HTML_QuickForm.
Constructor of HTML_QuickForm2
HTML_QuickForm2::__construct() accepts the following parameters:
- string
$id idattribute for<form>tag
- string
$method='post' HTTP method used to submit the form.
- string|array
$attributes= NULL Additional attributes for
<form>tag.
- boolean
$trackSubmit= TRUE Whether to track if the form was submitted.
The only way to set id and method attributes is through the
constructor. Attempts to change them afterwards will result in an Exception.
When $trackSubmit is on, a hidden field with a specially crafted name is
added to the form. If that name is present in $_REQUEST then the form is
considered submitted. This is especially useful if you have several
HTML_QuickForm2-backed forms on one page or if the form uses
GET submit method and the page may receive other $_GET
parameters.
When $trackSubmit is off the form only checks that either
$_GET or $_POST array (depending on form submit method) is
not empty.
Element behind $trackSubmit
<?php
$form = new HTML_QuickForm2('trackMe');
// iterating over supposedly empty form
foreach ($form as $element) {
echo $element;
}
?>
output:
<input type="hidden" id="qf:trackMe" name="_qf__trackMe" />
Form values and validation
Unlike simpler elements, form's values cannot be set via setValue()
method, data
sources should be used. getDataSources() returns the list
of form's data sources, setDataSources() replaces that list
with a new one and addDataSource() adds a data source to
the list. Data source containing submit values (based on a superglobal $_GET
or $_POST array) is added automatically if the form is considered submitted.
Coincidentally isSubmitted() method checks whether the list of form data sources contains an instance of HTML_QuickForm2_DataSource_Submit, which usually happens when the form was submitted.
validate() method performs server-side validation.
<?php if ($form->isSubmitted() && $form->validate()) { // form is valid, process its values doSomeProcessing($form->getValue()); } ?>is redundant, validate() checks submit status and will return FALSE if the form wasn't submitted.
Built-in Elements
Built-in Elements – List of built-in Elements, element-specific methods and configuration
List of elements
The following is a list of non-abstract descendants of HTML_QuickForm2_Node. These elements are pre-registered with HTML_QuickForm2_Factory and thus can be instantiated with HTML_QuickForm2_Factory::createElement() and added to a Container with either HTML_QuickForm2_Container::addElement() or its overloaded addEltype() method using "Type name" from tables below.
<?php
// will create an instance of HTML_QuickForm2_Element_Textarea
$area = HTML_QuickForm2_Factory::createElement('textarea', 'areaName');
// will add a new instance of HTML_QuickForm2_Element_Select to $container
$select = $container->addElement('select', 'selectName');
// will add a new instance of HTML_QuickForm2_Element_InputText to $container
$text = $container->addText('textName');
?>
| Type name | Class | Description, extra $data keys, extra methods |
|---|---|---|
'button' |
HTML_QuickForm2_Element_Button |
<button></button> elements. $data may
contain 'content' key with HTML to add between
<button></button> tags. Implements setContent()
/ getContent() methods.
|
'checkbox' |
HTML_QuickForm2_Element_InputCheckbox |
<input type="checkbox" /> elements.
$data may contain 'content' key with a label that
should be "glued" to checkbox. Implements setContent()
/ getContent() methods.
|
'fieldset' |
HTML_QuickForm2_Container_Fieldset | <fieldset></fieldset> elements, labels for them will be
rendered as <legend></legend>. |
'file' |
HTML_QuickForm2_Element_InputFile |
<input type="file" /> elements. This element validates
itself by checking a relevant 'error' field in $_FILES
array. $data may contain 'messageProvider'
and 'language' keys for setting up localized error messages.
|
'hidden' |
HTML_QuickForm2_Element_InputHidden | <input type="hidden" /> elements |
'image' |
HTML_QuickForm2_Element_InputImage |
<input type="image" /> elements
|
'inputbutton' |
HTML_QuickForm2_Element_InputButton | <input type="button" /> elements |
'password' |
HTML_QuickForm2_Element_InputPassword | <input type="password" /> elements |
'radio' |
HTML_QuickForm2_Element_InputRadio |
<input type="radio" /> elements. $data
may contain 'content' key with a label that should be "glued" to
radiobutton. Implements setContent()
/ getContent() methods.
|
'reset' |
HTML_QuickForm2_Element_InputReset | <input type="reset" /> elements |
'select' |
HTML_QuickForm2_Element_Select |
|
'submit' |
HTML_QuickForm2_Element_InputSubmit | <input type="submit" /> elements |
'text' |
HTML_QuickForm2_Element_InputText | <input type="text" /> elements |
'textarea' |
HTML_QuickForm2_Element_Textarea | <textarea></textarea> elements |
| Type name | Class | Description, extra $data keys, extra methods |
|---|---|---|
'date' |
HTML_QuickForm2_Element_Date | Group of selects used to input dates (and times). Described in a separate section. |
'group' |
HTML_QuickForm2_Container_Group | Group of form elements. Several elements may be grouped into a single entity and this entity used as a single element. Date and Hierselect are based on Group. |
'hierselect' |
HTML_QuickForm2_Element_Hierselect | Two or more select elements, selecting the value in the first changes options in the second and so on. Described in a separate section. |
'repeat' |
HTML_QuickForm2_Container_Repeat | Repeats a given 'prototype' Container multiple times. Described in a separate section. |
'script' |
HTML_QuickForm2_Element_Script | Class for adding inline javascript to the form, based on Static. |
'static' |
HTML_QuickForm2_Element_Static | A pseudo-element used to add text or markup to the form, when that text should be
output similar to form element.
Implements setContent() / getContent() methods and setTagName(). |
basic-elements.phpexample installed with the package shows all built-in elements.
Registering additional elements
New element types can be registered by HTML_QuickForm2_Factory::registerElement(), HTML_QuickForm2_Factory::isElementRegistered() checks whether an element is known to Factory.
<?php
HTML_QuickForm2_Factory::registerElement(
'dualselect', 'HTML_QuickForm2_Element_DualSelect'
);
// ...
if (HTML_QuickForm2_Factory::isElementRegistered('dualselect')) {
$form->addElement('dualselect', 'dualselectDemo', $attributes, $config);
}
?>
setContent() / getContent() methods
"Content" these methods deal with differs from element to element. For Button
elements it is the text to output between <button></button> tags,
for Static elements this content is
essentially the element itself, it may or may not be wrapped in a tag. For
Checkbox and Radio elements this is the label
that is returned "glued" to the element by its __toString() method,
as opposed to a regular label that is only output by a Renderer.
Setting elements' content
<?php
echo HTML_QuickForm2_Factory::createElement(
'button', 'aButton', array('type' => 'submit', 'id' => 'buttonId')
)->setContent('Button text') . "\n";
echo HTML_QuickForm2_Factory::createElement('static')
->setContent('A static element') . "\n";
echo HTML_QuickForm2_Factory::createElement('static')
->setId('staticId')
->setTagName('div')
->setContent('Another static element') . "\n";
echo HTML_QuickForm2_Factory::createElement(
'radio', 'aRadio', array('id' => 'radioId', 'value' => 'yes')
)->setContent('Click the radio') . "\n";
echo HTML_QuickForm2_Factory::createElement(
'checkbox', 'aBox', array('id' => 'boxId', 'value' => 'yes')
)->setContent('Click the checkbox')
->setLabel('Will not be printed here') . "\n";
?>
output:
<button id="buttonId" type="submit" name="aButton">Button text</button>
A static element
<div id="staticId">Another static element</div>
<input type="radio" value="yes" id="radioId" name="aRadio" /><label for="radioId">Click the radio</label>
<input type="checkbox" id="boxId" value="yes" name="aBox" /><label for="boxId">Click the checkbox</label>
Adding options to Select elements
<option>s and <optgroup>s can be added either
one by one using addOption() and
addOptgroup() method or
loaded from an associative array using loadOptions().
Two ways to add options
<?php
$selectSlow = new HTML_QuickForm2_Element_Select('slow');
$selectSlow->addOption('PEAR', 1);
$groupOne = $selectSlow->addOptgroup('HTML');
$groupOne->addOption('HTML_AJAX', 2);
$groupOne->addOption('HTML_Common2', 3);
$groupOne->addOption('HTML_QuickForm2', 4, array('style' => 'background: #808080'));
$groupTwo = $selectSlow->addOptgroup('HTTP');
$groupTwo->addOption('HTTP_Download', 5);
$groupTwo->addOption('HTTP_Request2', 6);
$selectFast = new HTML_QuickForm2_Element_Select('fast');
$selectFast->loadOptions(array(
1 => 'PEAR',
'HTML' => array(
2 => 'HTML_AJAX',
3 => 'HTML_Common2',
4 => 'HTML_QuickForm2'
),
'HTTP' => array(
5 => 'HTTP_Download',
6 => 'HTTP_Request2'
)
));
echo $selectSlow . "\n" . $selectFast;
?>
output:
<select name="slow" id="slow-0">
<option value="1">PEAR</option>
<optgroup label="HTML">
<option value="2">HTML_AJAX</option>
<option value="3">HTML_Common2</option>
<option style="background: #808080" value="4">HTML_QuickForm2</option>
</optgroup>
<optgroup label="HTTP">
<option value="5">HTTP_Download</option>
<option value="6">HTTP_Request2</option>
</optgroup>
</select>
<select name="fast" id="fast-0">
<option value="1">PEAR</option>
<optgroup label="HTML">
<option value="2">HTML_AJAX</option>
<option value="3">HTML_Common2</option>
<option value="4">HTML_QuickForm2</option>
</optgroup>
<optgroup label="HTTP">
<option value="5">HTTP_Download</option>
<option value="6">HTTP_Request2</option>
</optgroup>
</select>
Note, however, that only the first way allows passing additional attributes.
Static elements
Static elements are used to add text or markup to the form that will be later output as if it was a form element. Unlike standard form elements it obviosly cannot have a submit value so will only consider getting its value (setValue() is equivalent to setContent() for these elements) from data sources that do not implement HTML_QuickForm2_DataSource_Submit.
Being descended from HTML_Common2, Static elements can have attributes as do all other elements. It is also possible to use setTagName() to provide a name for a tag that will use these attributes and wrap around element's content.
<?php
if ($pic = loadPictureDataFromSomewhere()) {
$form->addStatic('picture', array(
'src' => $pic['url'], 'width' => $pic['width'],
'height' => $pic['height'], 'alt' => $pic['title']
))
->setTagName('img', false)
->setLabel('Existing picture:');
} else {
$form->addFile('picture_upload')
->setLabel('Upload new picture');
}
?>
Static elements will prevent setting tag name to a name of form-related tag (i.e.
'div'will work,'input'will not).
Groups
Groups – Combining several form elements into a single entity
Groups overview
HTML_QuickForm2_Container_Group is a specialized subclass of Container which allows to combine several form elements into an entity behaving like a single form element. Key features:
- A named group prepends its name to the contained elements' names;
- HTML_QuickForm2_Container_Group implements setValue() method;
- Grouped elements are processed in a different way than ungrouped ones by renderers.
Groups can be used for
- Keeping together elements having the same name (checkboxes and radios).
- Visual grouping of elements (e.g. 'Back', 'Next' and 'Cancel' buttons of a wizard).
- Logical grouping of elements (e.g. fields to input first and last name of a person).
Groups are also used as a base for custom elements like Date and Hierselect.
Names of grouped elements
If you add an element to a group having a name itself, group's name will be prepended to the element's name. If an element is added to a group without a name, its name will not be changed:
<?php
$named = $form->addGroup('groupName');
$innerNamed = $named->addText('elementName');
$innerComplex = $named->addText('elementOuter[elementInner]');
$nameless = $form->addGroup();
$innerNameless = $nameless->addText('elementName');
echo $innerNamed->getName() . ', ' . $innerComplex->getName()
. ', ' . $innerNameless->getName();
?>results in the following output
groupName[elementName], groupName[elementOuter][elementInner], elementName
Note also the difference between a nameless element in a named group and vice versa:
<?php
$namedGroup = $form->addGroup('groupName');
$namelessElement = $namedGroup->addText();
$namelessGroup = $form->addGroup();
$namedElement = $namelessGroup->addText('elementName');
echo $namelessElement->getName() . ', ' . $namedElement->getName();
?>which results in
groupName[], elementName
The only elements which will work reliably if given a name like
foo[]are checkboxes (assuming they have uniquevalueattributes). Do not give such names to any other elements, use explicit indexes:foo[0],foo[1].
Groups' values
Unlike other Container-based elements, Group
implements a working setValue() method. Values for the
Group should be given as an associative array having a structure similar
to what $_GET / $_POST will contain on form submit:
<?php
$foo = $form->addGroup('foo');
$foo->addText('bar');
$foo->addText('baz[quux]');
$foo->setValue(array(
'bar' => 'bar value',
'baz' => array('quux' => 'baz[quux] value')
));
print_r($foo->getValue());
?>
getValue() will return array having the same structure, output of the above code being
Array
(
[bar] => bar value
[baz] => Array
(
[quux] => baz[quux] value
)
)
Outputting groups
$data parameter for group's constructor may
contain the custom 'separator' key. It is either a string or an array of
strings that will be used to separate elements' HTML in output. setSeparator() / getSeparator() methods
are also available:
<?php
$group = new HTML_QuickForm2_Container_Group(
'foo', null, array('separator' => "<br />\n")
);
$group->addText('first');
$group->addText('second');
$group->addText('third');
echo $group;
echo "\n\n";
$group->setSeparator(array(' ', "<br />\n"));
echo $group;
?>results in output
<input type="text" name="foo[first]" id="first-0" /><br />
<input type="text" name="foo[second]" id="second-0" /><br />
<input type="text" name="foo[third]" id="third-0" />
<input type="text" name="foo[first]" id="first-0" /> <input type="text" name="foo[second]" id="second-0" /><br />
<input type="text" name="foo[third]" id="third-0" />
The default output for groups is quite simple, containing only elements' HTML and separators. You will need to use render() instead of __toString() to customize the output (the latter uses Default Renderer under the hood, but does not allow output customization). Consult the section on Default Renderer for additional info.
Custom Group-based elements
Custom Group-based elements – Date and Hierselect
Date element
HTML_QuickForm2_Element_Date is a
group of <select></select> elements used to input dates (and
times). Child selects are created according to 'format' parameter.
$data parameter for element's
constructor may contain the following custom keys:
'messageProvider'- Message provider for localized names of months and weekdays.
'language'-
Date language, using
'locale'will display month / weekday names according to the current locale.
'format'-
Format of the date, based on PHP's date(). Format characters
recognized:
'D','l','d','M','F','m','Y','y','h','H','i','s','a','A'.
'minYear'- Minimum year in year select
'maxYear'- Maximum year in year select
'addEmptyOption'-
Whether an empty option should be added to the top of each select box. May be also set on a
per-element basis:
array('Y' => false, 'd' => true);
'emptyOptionValue'- The value passed by the empty option
'emptyOptionText'-
The text displayed for the empty option. May be also set on a per-element basis:
array('Y' => 'Choose year', 'd' => 'Choose day');
'optionIncrement'-
Step to increase the option values by (works for
'i'and's')
'minHour'- Minimum hour in hour select (only for 24 hour format)
'maxHour'- Maximum hour in hour select (only for 24 hour format)
'minMonth'- Minimum month in month select
'maxMonth'- Maximum month in month select
Child selects of Date element are named according to
'format' parameter, so Date's value can be set using the standard
approach for groups:
<?php
$date = $form->addElement(
'date', 'testDate', null,
array('format' => 'd-F-Y', 'minYear' => date('Y'), 'maxYear' => 2001)
);
// sets the date to January 1st, 2012
$date->setValue(array('d' => 1, 'F' => 1, 'Y' => 2012));
?>
Additionally Date's setValue() method may accept a string containing a date or a number representing Unix timestamp:
<?php
// also sets the date to January 1st, 2012
$date->setValue('2012-01-01');
// once again sets the date to January 1st, 2012
$date->setValue(1325408400);
?>String representation of a date is processed by strtotime() function, so consider its limitations.
Hierselect element
Hierselect requires
quickform.jsandquickform-hierselect.jsfiles being included in the page to work.
HTML_QuickForm2_Element_Hierselect is
a group of two or more chained <select></select> elements.
Selecting a value in the first select changes options in the second and so on.
$data parameter for element's
constructor may contain the following custom keys:
'options'- Data to populate child elements' options with. Passed to HTML_QuickForm2_Element_Hierselect::loadOptions().
'size'- number of selects in hierselect. If not given will be set from size of options array or size of array passed to HTML_QuickForm2_Element_Hierselect::setValue().
Everything else except 'label' is passed on to created selects.
Options for select elements are added using HTML_QuickForm2_Element_Hierselect::loadOptions() method:
<?php
$categories = array(
1 => 'HTML',
2 => 'HTTP'
);
// first keys are needed to find related options
$packages = array(
1 => array(
1 => 'HTML_AJAX',
2 => 'HTML_Common2',
3 => 'HTML_QuickForm2'
),
2 => array(
1 => 'HTTP_Download',
2 => 'HTTP_Request2'
)
);
$hierselect->loadOptions(array($categories, $packages));
?>
Unlike old hierselect element from HTML_QuickForm you don't need to provide all possible options. You may instead give server-side and client-side callbacks that will receive values of previous selects and return additional options as needed:
<?php
$hierselect->loadOptions(
array($categories, array()),
array($loader, 'getPackages'), 'loadPackagesSync'
);
?>
hierselect-ajax.phpexample file installed with the package shows how to use AJAX requests to load additional options.
Repeat element
Repeat element – Repeating a given Container several times
Overview
Repeat requires
quickform.jsandquickform-repeat.jsfiles being included in the page to work.
HTML_QuickForm2_Container_Repeat is an element that accepts a 'prototype' Container (a Fieldset or a Group, but not another Repeat) and repeats it several times in the form. Repeated items can be dynamically added / removed via Javascript, server-side part automatically understands these changes. Server-side and client-side validation can be easily leveraged: rules added to prototype Container and its children are repeated as well.
repeat.phpexample file installed with the package shows how to use repeat elements with Fieldsets and Groups.
Adding elements to Repeat
HTML_QuickForm2_Container_Repeat has only one immediate child element: a
prototype Container, either set using setPrototype() method
or passed as 'prototype' key of $data parameter to
Repeat's constructor.
appendChild(), insertBefore(), removeChild() are available for Repeat element as usual, but these are proxies for prototype's methods working with its children and will throw exceptions if prototype was not yet set. An exception will also be thrown if you attempt to add another Repeat element to a prototype.
Adding elements to Repeat
<?php
$fieldset = new HTML_QuickForm2_Container_Fieldset();
$repeat = new HTML_QuickForm2_Container_Repeat();
$repeat->setPrototype($fieldset);
echo "repeat: " . count($repeat) . "; fieldset: " . count($fieldset) . "\n";
$repeat->addText('title');
echo "repeat: " . count($repeat) . "; fieldset: " . count($fieldset) . "\n";
?>
the above code outputs
repeat: 1; fieldset: 0
repeat: 1; fieldset: 1
Names of repeated elements
As is the case with groups, repeat element will change names of its children, additionally
id attributes will be changed. Instead of prepending the repeat's name,
however, a special "index template" '[:idx:]' will be appended to
the name and _:idx: to the id.
':idx:' string in these will later be replaced by an actual index of the
repeated item to produce unique names / ids.
Default names for repeated elements
<?php
$form = new HTML_QuickForm2('repeatFieldset');
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'title' => array('foo', 'bar', 'key' => 'baz')
)));
$fieldset = new HTML_QuickForm2_Container_Fieldset();
$repeat = $form->addRepeat()->setPrototype($fieldset);
$repeat->addText('title', array('id' => 'title'));
$renderer = $repeat->render(
HTML_QuickForm2_Renderer::factory('array')
);
$array = $renderer->toArray();
foreach ($array['elements'] as $fsArray) {
echo $fsArray['attributes'] . "\n";
echo " " . $fsArray['elements'][0]['html'] . "\n";
}
?>
outputs
id="qfauto-0_:idx:" class="repeatItem repeatPrototype"
<input type="text" id="title_:idx:" name="title[:idx:]" />
id="qfauto-0_0" class="repeatItem"
<input type="text" id="title_0" name="title[0]" value="foo" />
id="qfauto-0_1" class="repeatItem"
<input type="text" id="title_1" name="title[1]" value="bar" />
id="qfauto-0_key" class="repeatItem"
<input type="text" id="title_key" name="title[key]" value="baz" />
There are a few things worth noting in this output:
-
Keys for repeated items are discovered automatically from data source using the values for the
'title'field. Sometimes it is better to explicitly set the field to use for discovering indexes using setIndexField(). -
Keys are not required to be numeric, they should however match the
HTML_QuickForm2_Container_Repeat::INDEX_REGEXP regular expression:
'/^[a-zA-Z0-9_]+$/'. -
The output contains an extra item which still has its index templates intact. It should be
hidden thanks to
repeatPrototypeCSS class and will be used by Javascript code to add new visible repeated items.
If you are using a named group as a repeat prototype, you may want to use another name structure:
group[index][element] instead of group[element][index]. It
is possible to do so if you manually put [:idx:] into group's name, Repeat will
not mangle names further if this string is already present in them.
Custom names for repeated elements
<?php
$form = new HTML_QuickForm2('repeatGroup');
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'tags' => array(
array('title' => 'foo'),
array('title' => 'bar'),
'key' => array('title' => 'baz')
)
)));
$group = new HTML_QuickForm2_Container_Group('tags[:idx:]');
$repeat = $form->addRepeat()->setPrototype($group);
// custom id as well
$group->addText('title', array('id' => 'tags-:idx:-title'));
$renderer = $repeat->render(
HTML_QuickForm2_Renderer::factory('array')
);
$array = $renderer->toArray();
foreach ($array['elements'] as $groupArray) {
echo $groupArray['elements'][0]['html'] . "\n";
}
?>
outputs
<input type="text" id="tags-:idx:-title" name="tags[:idx:][title]" />
<input type="text" id="tags-0-title" name="tags[0][title]" value="foo" />
<input type="text" id="tags-1-title" name="tags[1][title]" value="bar" />
<input type="text" id="tags-key-title" name="tags[key][title]" value="baz" />
For radios and checkboxes it is also possible to put :idx: into
value attribute, this way their names will not be changed, essentially
allowing to use one set of radios or checkboxes for all repeated items.
Indexes of repeated items
As was noted in the above example, indexes for repeated items can be automatically discovered from data sources. The field name to use for discovering indexes can be either set explicitly with setIndexField() or left for the Repeat to choose automatically using names of prototype's child elements. When guessing, it will only consider elements that are expected to always have a submit value, so elements like buttons, checkboxes and multiple selects will not be used.
Indexes can be also set manually using setIndexes(), the corresponding getIndexes() is also available. Duplicate indexes and those not matching HTML_QuickForm2_Container_Repeat::INDEX_REGEXP will be ignored by setIndexes().
Working with indexes
<?php
$form = new HTML_QuickForm2('repeatIndexes');
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'title' => array('foo', 'bar', 'key' => 'baz')
)));
$fieldset = new HTML_QuickForm2_Container_Fieldset();
$repeat = $form->addRepeat()->setPrototype($fieldset);
// no element to guess indexes present
var_dump($repeat->getIndexes());
// explicitly set field to discover indexes
$repeat->setIndexField('title');
var_dump($repeat->getIndexes());
// explicit setIndexes() with a few errors
$repeat->setIndexes(array('foo', 'bar', 'baz', 'qu\'ux', 'baz'));
var_dump($repeat->getIndexes());
?>
outputs
array(0) {
}
array(3) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
string(3) "key"
}
array(3) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
[2]=>
string(3) "baz"
}
Setting indexes for repeat elements works quite similar to setting values for other elements, so keeping correct order really helps.
Message Providers
Message Providers – Supplying (localized) messages to form elements
Overview
Some of the form elements need to automatically obtain (possibly localized) messages:
- Date element needs month and weekday names;
-
HTML_QuickForm2_Element_InputFile
needs error messages to display if its internal validation (checking
'error'field within$_FILESarray) fails.
Instead of giving all possible messages to each element, an object implementing HTML_QuickForm2_MessageProvider interface (or a callback with a signature similar to HTML_QuickForm2_MessageProvider::get()) is given which returns messages on-demand.
Default message provider
Default message provider will be used by 'date' and 'file'
elements if another one is not explicitly given. It contains an array of pre-translated messages
and allows overriding them and setting additional translations for new languages. As this message
provider is a Singleton, the updated translations will be available throughout the application.
Adding a new "translation"
<?php
HTML_QuickForm2_MessageProvider_Default::getInstance()->set(
array('date', 'months_long'),
'elderscrolls',
array("Morning Star", "Sun's Dawn", "First Seed", "Rain's Hand",
"Second Seed", "Mid Year", "Sun's Height", "Last Seed",
"Heartfire", "Frostfall", "Sun's Dusk", "Evening Star")
);
$date = HTML_QuickForm2_Factory::createElement(
'date', 'test', array(),
array('format' => 'd F Y', 'language' => 'elderscrolls')
)->setValue(
'2012-04-01'
);
// remove all tags from the output
$date->toggleFrozen(true);
$date->persistentFreeze(false);
echo $date;
?>
the output of the above code being
01 Rain's Hand 2012
If 'language' field is not explicitly given to element's constructor,
'language' option set with
<?php
HTML_Common2::setOption('language', '...');
?>will be used, defaulting to 'en'.
Strftime message provider
This message provider will only work for 'date' elements and relies on strftime() function to generate
lists of months and weekdays. You will need to properly set LC_TIME locale
category for it to work.
Using Strftime message provider
<?php
setlocale(LC_TIME, 'ru_RU.CP1251', 'Russian_Russia.1251');
// Strftime message provider will be used if 'locale' is given as 'language' value
$date = HTML_QuickForm2_Factory::createElement(
'date', 'test', array(), array('format' => 'd F Y', 'language' => 'locale')
)->setValue(
'2012-04-01'
);
// remove all tags from the output
$date->toggleFrozen(true);
$date->persistentFreeze(false);
echo $date;
?>
which outputs (in CP1251 encoding, actually):
01 Апрель 2012
Element values and validation
Element values and Data sources
Element values and Data sources – Setting and getting values for the whole form and for individual elements
Individual elements' values
Each element in HTML_QuickForm2 implements getRawValue() and getValue() methods that return its unfiltered and filtered submit value, respectively, and setValue() that sets the element's display value. These are not always the same, consider
<?php
$text = new HTML_QuickForm2_Element_InputText(
'testText', array('disabled' => 'disabled')
);
$text->setValue('a value');
echo $text . "\n";
var_dump($text->getValue());
?>which produces
<input type="text" disabled="disabled" name="testText" id="testText-0" value="a value" />
NULL
as disabled elements cannot have a submit value.
If an element can not have a submit value (e.g. reset button, static element) or does not currently have one (e.g. disabled element, unchecked checkbox) then getValue() will return NULL. The value also passes "intrinsic validation" which ensures that it could possibly come from that element:
<?php
$select = HTML_QuickForm2_Factory::createElement('select', 'testSelect',
array('multiple' => 'multiple'))
->loadOptions(array('a' => 'letter A', 'b' => 'letter B', 'c' => 'letter C'));
$select->setValue(array('a', 'z'));
// select will only return values for options that were present in it
var_dump($select->getValue());
?>will output
array(1) {
[0]=>
string(1) "a"
}
On the other hand, some of the elements cannot have a display value (<input
type="image" />, file uploads), setValue() will be a
no-op for such elements and they will only get their values (click coordinates and file upload
data, respectively) from submit data sources.
getValue() / getRawValue() also work for Containers and return an array with values of contained elements. setValue() is only implemented for groups, data sources should be used to set the values for the whole form.
Data sources overview
Instance of HTML_QuickForm2 contains an array of Data sources - objects storing elements' incoming values. These values may either originate from HTTP request data (submit values) or be provided by the programmer (default values). Data sources implement HTML_QuickForm2_DataSource interface defining a single getValue() method, which receives element name and returns either element value or NULL if such value is not present.
There is also a HTML_QuickForm2_DataSource_Submit interface that defines an additional
getUpload() method that receives file upload name and returns either file
upload data from $_FILES array or NULL if it doesn't contain this data.
A data source containing submit values will be added to the list automatically if constructor of HTML_QuickForm2 considers the form submitted. You can add another data source to the list using HTML_QuickForm2::addDataSource() and completely replace the list using HTML_QuickForm2::setDataSources().
An element will try to update its value from data sources in the following cases:
- It is added to the form;
- Its name is changed;
- Form data sources list is changed.
To perform that update it gets the data sources array from the form and iterates over it calling getValue() until a non-NULL value is returned. This value is then used as element's value.
Some of the elements (submit buttons, obviously file uploads) will only consider getting their values from instances of HTML_QuickForm2_DataSource_Submit. Some (e.g. checkboxes) will stop iterating over the array as soon as an instance of HTML_QuickForm2_DataSource_Submit is found, even if its getValue() method returns NULL. Static elements will only consider a datasource if it is not an instance of HTML_QuickForm2_DataSource_Submit.
Implementations of HTML_QuickForm2_DataSource
The package contains several classes implementing the HTML_QuickForm2_DataSource interface, but the only one that should be used directly is HTML_QuickForm2_DataSource_Array, other classes are used internally by the package.
An instance of HTML_QuickForm2_DataSource_Array wraps around an array
with a structure similar to that of superglobal $_GET /
$_POST arrays. This wrapper allows searching for values of elements with
complex names:
<?php
$ds = new HTML_QuickForm2_DataSource_Array(array(
'foo' => 'foo value',
'bar' => array('bar 1', 'bar 2'),
'baz' => array('first' => array('second' => 'found a value'))
));
echo $ds->getValue('foo') . "\n";
echo $ds->getValue('bar[1]') . "\n";
echo $ds->getValue('baz[first][second]');
?>outputs
foo value
bar 2
found a value
Instead of loading data from a database unconditionally and passing it to this data source in an array, it may make sense to create your own data source implementation, which will only perform a query when asked for a value. This way you will probably save a query on form submit, as all values will be found in a submit data source.
Maintaining correct order
A most popular value-related error happens when a programmer uses an element's setValue() method before adding that element to the form:
<?php
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'testDefault' => 'duh!'
)));
$element = HTML_QuickForm2_Factory::createElement('text', 'testDefault')
->setValue('my carefully prepared value');
$form->appendChild($element);
echo $element->getValue();
?>As described above, the element will immediately try to update its value from form's data sources and overwrite whatever was set on the previous step, so the above results in
duh!
A less obvious problem is adding data sources after elements:
<?php
// an element updates its value from existing (e.g. submit) data sources
$form->addElement('text', 'testDefault');
// it updates its value again
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'testDefault' => 'my carefully prepared value'
)));
// ...and yet again
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'unrelated' => 'a completely unrelated value'
)));
?>That code behaves as expected, but performs lots of unneeded work.
To sum it up, a correct order is:
- Add all data sources to the form;
- Add all elements to the form;
- Call setValue() on some elements, if still needed.
Rules and validation
Rules and validation – Checking that you get the values you need
Introduction
Server-side validation in HTML_QuickForm2 is performed by HTML_QuickForm2::validate() method. Validation rules doing actual checks on element values are implemented as subclasses of HTML_QuickForm2_Rule, they are added to elements via HTML_QuickForm2_Node::addRule().
Basically, the form is invalid if it contains at least one invalid element. The element is considered invalid if it has an error message (accessible by HTML_QuickForm2_Node::getError()) set and valid otherwise. That error can appear in two different ways:
- You can manually set an error message for an element using HTML_QuickForm2_Node::setError().
- A rule added to the element will set an error message if it has such a message and its validation routine returned FALSE.
The latter happens in the course of executing HTML_QuickForm2::validate(). It iterates over all form's elements, for each element calling validate() methods of all its rules in the order they were added. As soon as an error is set on an element, its validation stops.
Do not forget to provide an error message to the rule, otherwise the element will be considered valid even if rule's validation routine returns FALSE. Not setting an error message is only useful when chaining (see below).
Some of the elements may perform additional hardcoded validation. For example, file uploads will check the value of
'error'field in$_FILESand assign a relevant error message when file upload was attempted but failed.
Instantiating Rule objects directly
<?php
require_once 'HTML/QuickForm2.php';
require_once 'HTML/QuickForm2/Rule/Required.php';
require_once 'HTML/QuickForm2/Rule/Regex.php';
$form = new HTML_QuickForm2('tutorial');
$username = $form->addElement('text', 'username');
$form->addElement('submit', null, array('value' => 'Send!'));
$username->addRule(new HTML_QuickForm2_Rule_Required(
$username, 'Username is required!'
));
$username->addRule(new HTML_QuickForm2_Rule_Regex(
$username, 'Username should contain only letters, digits and underscores', '/^[a-zA-Z0-9_]+$/'
));
if ($form->validate()) {
// process form
}
echo $form;
?>
Of course, you will rarely need to instantiate Rule subclasses directly, Rule objects can be created by HTML_QuickForm2_Node::createRule() or automatically by addRule() if first parameter is a string representing registered rule type.
Automatic creation of Rule objects
<?php
require_once 'HTML/QuickForm2.php';
$form = new HTML_QuickForm2('tutorial');
$username = $form->addElement('text', 'username');
$form->addElement('submit', null, array('value' => 'Send!'));
$username->addRule('required', 'Username is required!');
$username->addRule('regex', 'Username should contain only letters, digits and underscores',
'/^[a-zA-Z0-9_]+$/');
if ($form->validate()) {
// process form
}
echo $form;
?>
Validating containers
Most of the built-in rules are designed to check scalar values and will not work properly if
added to a Container (this includes Groups and Group-based elements like Date and Hierselect), as
Containers return their values in an associative array. One notable exception is
nonempty / required rule that can validate a container (or
<select multiple="multiple" />):
Checks that at least two checkboxes in a group are selected
<?php
$boxGroup = $form->addElement('group', 'boxes')->setLabel('Check at least two:');
$boxGroup->addElement('checkbox', null, array('value' => 'first'))->setContent('First');
$boxGroup->addElement('checkbox', null, array('value' => 'second'))->setContent('Second');
$boxGroup->addElement('checkbox', null, array('value' => 'third'))->setContent('Third');
$boxGroup->addRule('required', 'Check at least two boxes', 2);
?>
It is of course possible to implement a custom rule that will properly handle an associative
array as the element's value. It is also possible to leverage existing "scalar" rules
to validate Containers by using each rule, it applies a template rule to all the
elements in a Container and considers Container valid if its validation routine returns TRUE
for all of them:
Checks that all phone fields in a group contain numeric data
<?php
$phones = $form->addElement('group', 'phones')->setLabel('Phones (numeric):')
->setSeparator('<br />');
$phones->addElement('text', '0');
$phones->addElement('text', '1');
$phones->addRule('each', 'Phones should be numeric',
$phones->createRule('regex', '', '/^\\d+([ -]\\d+)*$/'));
?>
More specific rules are run first: rules added to container will be checked after rules added to its contained elements.
Chaining the rules
HTML_QuickForm2 allows validation of elements based on values and validation status of other elements. This is done by building a "chain" of validation rules using HTML_QuickForm2_Rule::and_() and HTML_QuickForm2_Rule::or_() methods. Execution of the chain starts with a rule that was added to an element, then results of other rules' validation routines are combined using corresponding logical operators. Error is only set on the element if the whole chain returned FALSE.
Behaviour of and_() and or_() is similar to PHP's
and and or operators:
- and_() has higher precedence than or_().
- Evaluation is short-circuited. If first argument of and_() evaluates to FALSE then FALSE is returned without evaluating second argument, if first argument of or_() evaluates to TRUE then TRUE is returned without evaluating second argument.
Rules that are added to the chain behave the same way as the rules that are added directly to the
element they validate (this is not necessarily the same element the chain is added to), they will
set an error if the rule itself returns FALSE, not the chain. Thus it is often needed
not to provide error messages to the rules. It may also make sense to add a
chain of rules to a chain (this is similar to adding parentheses to a PHP expression with
and and or).
Skips checking email field if "receive email" box is not checked
<?php
$emailPresent = $email->createRule('nonempty', 'Supply a valid email if you want to receive our spam');
// note lack of error message here, error should only be set by previous rule
$emailValid = $email->createRule('callback', '', array('callback' => 'filter_var',
'arguments' => array(FILTER_VALIDATE_EMAIL)));
// note lack of error message for 'empty' rule, we don't want error on a checkbox
$spamCheck->addRule('empty')
->or_($emailPresent->and_($emailValid));
?>
Checks password fields in password change form
<?php
$newPassword->addRule('empty')
->and_($repPassword->createRule('empty'))
->or_($newPassword->createRule('minlength', 'The password is too short', 6))
->and_($repPassword->createRule('eq', 'The passwords do not match', $newPassword))
->and_($oldPassword->createRule('nonempty', 'Supply old password if you want to change it'));
?>
Client-side validation
Validation library
Client-side validation depends on a JS library residing in
quickform.jsfile. Neither a link to that file nor its contents is automatically included in the output generated by a renderer, the next section describes how you can properly handle including it.
You can tell a rule to also generate Javascript necessary for client-side validation. This is
done by passing a $runAt parameter with
HTML_QuickForm2_Rule::CLIENT flag set to addRule():
<?php
// if first parameter to addRule() is a string:
$username->addRule('required', 'Username is required', null,
HTML_QuickForm2_Rule::SERVER | HTML_QuickForm2_Rule::CLIENT);
// if first parameter to addRule() is a Rule instance:
$username->addRule($username->createRule('required', 'Username is required'),
HTML_QuickForm2_Rule::CLIENT_SERVER); // using a shorthand for above constants
?>If more rules were chained to the added one with and_() and or_(), Javascript will be generated for the whole chain.
Since release 0.6.0 it is possible to run client-side rules for an element on changing its value
or on it losing input focus ('onchange' and 'onblur'
events) in addition to form submit ('onsubmit' event). This is triggered by
passing a $runAt parameter with
HTML_QuickForm2_Rule::ONBLUR_CLIENT flag set to
addRule(). If a rule has chained rules, then validation will be triggered by
all elements appearing in a chain.
<?php
// here validation will be run onchange / onblur of both $newPassword and $repPassword fields
$newPassword->addRule('empty', '', null, HTML_QuickForm2_Rule::ONBLUR_CLIENT_SERVER)
->and_($repPassword->createRule('empty'))
->or_($repPassword->createRule('eq', 'The passwords do not match', $newPassword));
?>
Another change introduced in 0.6.0 is that validation errors are now output near the elements instead of being shown in Javascript alert(). In a nuthshell, client-side validation behaviour in HTML_QuickForm2 0.6.0+ is more similar to that of HTML_QuickForm_DHTMLRulesTableless than to that of old HTML_QuickForm.
Most of the built-in rules are able to run client-side, the only exceptions are
maxfilesize and mimetype rules specific for file uploads.
If you want to run callback rule client-side, you will obviously need to
implement a callback in Javascript as well as in PHP. If you don't explicitly set
'js_callback' configuration parameter, callback rule
will try to run Javascript function having the same name as provided PHP
'callback'. This may be especially useful if you use
HTML_AJAX to proxy PHP classes or callbacks in
Javascript.
When running regex rules client-side, you should stick to regular expression
syntax common in PHP and Javascript:
-
Use a slash
/as a delimiter. -
Use only
i,m,upattern modifiers. Ifumodifier is used, PHP's Unicode escapes \x{NNNN} are automatically converted to Javascript's Unicode escapes \uNNNN when creating a client-side rule. - Do not use regular expession features that are not supported in Javascript (e.g. lookbehind assertions, recursive patterns).
While it is possible to add a client-side only rule
<?php $username->addRule('minlength', 'Username should be at least 4 characters long', 4, HTML_QuickForm2_Rule::CLIENT); ?>it is not recommended unless you perform the same validation server-side using some other rule.
Built-in validation Rules
Built-in validation Rules – List of built-in validation Rules
Registered Rules
For your convenience, all rules included in the package are already registered with HTML_QuickForm2_Factory and can be easily created with createRule() / addRule(). Some of the rule classes are registered under several names with different configuration data to save keystrokes and improve readability:
<?php
// these calls are identical
$username->addRule('minlength', 'Username should be at least 4 characters long', 4);
$username->addRule('length', 'Username should be at least 4 characters long', array('min' => 4));
// as are these
$start->addRule('lt', 'Start should be less than finish', $finish);
$start->addRule('compare', 'Start should be less than finish',
array('operator' => '<', 'operand' => $finish));
?>
| Rule name | Class name | Description | Configuration |
|---|---|---|---|
nonempty |
HTML_QuickForm2_Rule_Nonempty | Checks that the field is not empty | Minimum number of nonempty values for Containers / arrays, integer |
empty |
HTML_QuickForm2_Rule_Empty | Checks that the field is empty | |
required |
HTML_QuickForm2_Rule_Required | Like nonempty, but the field is marked as required in output |
Like nonempty |
compare |
HTML_QuickForm2_Rule_Compare | Compares the value of the field with some other value using the given operator |
Either of the following:
'==='. Operand can either be a
literal value or another form element.
|
eq |
As compare rule with hardcoded '===' operator |
Operand. The values are compared as strings. | |
neq |
As compare rule with hardcoded '!==' operator |
||
lt |
As compare rule with hardcoded '<' operator |
Operand. The values are compared as numbers. | |
lte |
As compare rule with hardcoded '<=' operator |
||
gt |
As compare rule with hardcoded '>' operator |
||
gte |
As compare rule with hardcoded '>=' operator |
||
regex |
HTML_QuickForm2_Rule_Regex | Checks that the field value matches the given regular expression. | Regular expression, string. Use slashes for delimiters if you intend to do client-side validation. |
email |
HTML_QuickForm2_Rule_Email | Checks that the field value is a valid email address. Currently the most common address format is recognised, support for additional RFC 5321 features and DNS checks is planned. | |
callback |
HTML_QuickForm2_Rule_Callback | Checks the value using a provided callback function (method). It is expected to return TRUE if the element is valid | Either of
|
length |
HTML_QuickForm2_Rule_Length | Checks that the value's length is within the given limits |
Either of
|
minlength |
Checks that the value's length is at least the given number of characters | Minimal length, integer | |
maxlength |
Checks that the value's length is at most the given number of characters | Maximal length, integer | |
notcallback |
HTML_QuickForm2_Rule_NotCallback | Checks the value using a provided callback function (method). It is expected to return FALSE if the element is valid. | Like callback |
notregex |
HTML_QuickForm2_Rule_NotRegex | Checks that the field value does not match the given regular expression. | Like regex |
| Rules specific for file uploads | |||
maxfilesize |
HTML_QuickForm2_Rule_MaxFileSize | Checks that uploaded file size does not exceed the given limit | Maximum allowed file size, integer |
mimetype |
HTML_QuickForm2_Rule_MimeType | Checks that uploaded file is of the correct MIME type | Allowed MIME type or an array of types |
| Rules specific for containers | |||
each |
HTML_QuickForm2_Rule_Each | Validates all elements in a Container using a template Rule | Template Rule, instance of HTML_QuickForm2_Rule |
Usage of builtin rules is covered in
builtin-rules.phpexample installed with the package.
Registering new rules
New rule types are registered by HTML_QuickForm2_Factory::registerRule() which accepts rule type name, corresponding class name and optionally file name containing that class and default configuration data for all rules of the given type.
<?php
// registers a callback rule with a specific callback
HTML_QuickForm2_Factory::registerRule(
'inarray', 'HTML_QuickForm2_Rule_Callback', null,
array('callback' => 'in_array')
);
$field->addRule('inarray', 'wrong variable name!', array('foo', 'bar', 'baz'));
?>
Javascript validation library
Javascript validation library – Library API and customization possibilities for client-side validation
API docs generation
Javascript code has API documentation comments that can be extracted with JsDoc toolkit. SVN checkout of HTML_QuickForm2 contains Phing build file for this.
Helper objects and functions
All Javascript code defined in HTML_QuickForm2 lives in qf
"namespace". Deeper namespaces are used to group related functionality.
qf.Map is a class for Hash Map data structure. It is used internally to store
validation errors and Container values.
var map = new qf.Map();
map.set('a key', 'a value');
alert(map.get('a key')); // a value
alert(map.hasKey('another key') ? 'true' : 'false'); // false
alert(map.length()); // 1
var map2 = new qf.Map({foo: 'foo value', bar: 'bar value'});
map.merge(map2);
map.remove('foo');
alert(map.getKeys().join(', ')); // a key, bar
alert(map.getValues().join(', ')); // a value, bar value
map.clear();
alert(map.isEmpty() ? 'true' : 'false'); // true
qf.form namespace contains methods for getting and setting elements' values:
qf.form.getValue(), qf.form.getSubmitValue() (shorthand
qf.$v()), qf.form.getContainerSubmitValue() (shorthand
qf.$cv()), qf.form.setValue().
Methods for handling CSS classes on elements live in qf.classes namespace:
qf.classes.add(), qf.classes.remove(),
qf.classes.has().
qf.events namespace contains helper methods for crossbrowser events support:
qf.events.addListener(), qf.events.removeListener(),
qf.events.fixEvent(). The latter is expected to be used in event handlers in
the following way
qf.Validator.submitHandler = function(event)
{
event = qf.events.fixEvent(event);
// ...
};
HTML_QuickForm2 does not contain a full-blown crossbrowser event library because it does not need one, the above are simple convenience methods. There are numerous well-known JS frameworks with good crossbrowser events support, use one if needed.
qf.rules and qf.elements namespaces are intended for rule
implementations and element support methods, respectively. For example, Hierselect element puts
its code into qf.elements.hierselect.
qf.Validator object
Client-side validation is performed by an instance of qf.Validator. Its
constructor accepts DOM object of a form it needs to validate, sets up
necessary event handlers on that object and adds itself as a validator
property of it.
It is easiest to disable client-side validation for a form by clearing
validatorproperty from form's DOM object:document.getElementById(formId).validator = null;
qf.Validator exposes several methods which can be overriden to change the way
validation results are presented to user:
- onStart()
- Called before starting the validation. May be used e.g. to clear the errors from form elements.
- onFieldError()
- Called on setting the element error.
- onFieldValid()
- Called on successfully validating the element.
- onFormValid()
- Called on successfully validating the whole form.
- onFormError()
-
Called on failed form validation. List of errors is available in
this.errorsproperty, which is an instance ofqf.Map.
These can be overriden either for a particular instance of qf.Validator or for
all its instances if qf.Validator.prototype is changed.
For example, if you want to display a list of validation errors in an alert() (as was done in HTML_QuickForm and pre-0.6.0 HTML_QuickForm2) you can do the following:
var form = document.getElementById(formId);
form.validator.onFormError = function() {
alert('Invalid information entered:\n - ' +
this.errors.getValues().join('\n - ') +
'\nPlease correct these fields.');
};
// don't set errors on elements
form.validator.onFieldError = function() {};
form.validator.onFieldValid = function() {};
If you want to disable submit button to prevent duplicate form submits:
document.getElementById(formId).validator.onFormValid = function() {
document.getElementById(submitId).disabled = true;
};
Form output and Javascript support
Renderers
Renderers – Customizing the form output
Introduction
Renderers in HTML_QuickForm2 are classes that output the form. They either generate the resultant HTML themselves or represent the form in some intermediate format that can later be used by e.g. a template engine.
Renderers also contain a Javascript builder object that aggregates code needed for client-side validation and Javascript-backed elements. This means that rendering step is mandatory if you are using any of these.
A new feature in HTML_QuickForm2 compared to HTML_QuickForm is the ability to extend Renderer functionality by plugins.
Typical form output workflow:
<?php
require_once 'HTML/QuickForm2/Renderer.php';
$renderer = HTML_QuickForm2_Renderer::factory('custom');
// common options
$renderer->setOption(array(
'group_errors' => true,
'required_note' => 'Fields labeled in 36pt bold red font are required'
));
// renderer-specific setup
$renderer->doSomeOutputCustomization();
// ...
$renderer->doAnotherOutputCustomization();
// process the form
$form->render($renderer);
// Output the links to JS libraries, if used
foreach ($renderer->getJavascriptBuilder()->getLibraries() as $link) {
echo $link . "\n";
}
// Assuming the renderer implements __toString()
echo $renderer;
?>
The following renderers are installed with the package:
-
Default - supports the
possibility to do
<?php echo $form; ?> - Callback - uses PHP callback functions to output the form.
- Array - converts form structure to an array.
- Stub - does minimal form processing when actual output is done manually.
Common Renderer API
setOption() method is used to set the values of configuration parameters and getOption() method to read them. Passing an unknown parameter name to these methods will result in an Exception. The following parameters are defined in the base class and known to all Renderers:
| Parameter name | Description | Expected type | Default value |
|---|---|---|---|
group_hiddens |
Whether to group hidden elements together or render them where they were added. | boolean | TRUE |
group_errors |
Whether to group error messages or render them alongside elements they apply to. | boolean | FALSE |
errors_prefix |
Leading message for grouped errors. | string | 'Invalid information entered:' |
errors_suffix |
Trailing message for grouped errors. | string | 'Please correct these fields.' |
required_note |
Note displayed if the form contains required elements. | string | '<em>*</em> denotes required fields.' |
setJavascriptBuilder() sets a Javascript builder object used during form rendering and getJavascriptBuilder() returns that object (if an object was not set previosly a new instance is created). Package users will mostly need to interact with this object to properly include Javascript library files.
reset() method clears all accumulated data. It is called automatically when rendering a HTML_QuickForm2 object, but must be called manually when rendering form elements separately.
Creating Renderer objects
It is only possible to instantiate built-in Renderers through HTML_QuickForm2_Renderer::factory() as their __construct() methods are declared protected. Moreover, factory() returns an instance of HTML_QuickForm2_Renderer_Proxy wrapping around a specific Renderer instance. This setup is needed for plugin support and serves as a workaround for PHP shortcomings:
- It is impossible (before PHP 5.4) to add methods to an object at runtime without using some esoteric extension like runkit;
- It is impossible to define properties/methods accessible to class and related classes (e.g. Java's protected modifier allows access from classes of the same package).
Proxy and factory() are used to aggregate methods from several classes and to limit access to them from outside: all fields and methods of a Renderer instance are public, but the only path to that instance is through a Proxy that only allows access to explicitly "published" methods and methods defined in base class. Plugins, however, have direct access to a Renderer instance and thus can freely use its API.
It is definitely possible to create a subclass of a built-in Renderer with a public __construct() method and do not use factory(). This will, however, prevent plugins from working.
Renderer Plugins
Plugins are the means to enhance Renderer functionality at runtime. From user's point of view the Renderer object simply gets a new method:
<?php
require_once 'HTML/QuickForm2/Renderer.php';
HTML_QuickForm2_Renderer::registerPlugin('foo', 'Foo_PluginBar');
$foo = HTML_QuickForm2_Renderer::factory('foo');
$foo->doBar();
// This will also work
HTML_QuickForm2_Renderer::registerPlugin('foo', 'Foo_PluginMore');
$foo->performMore();
?>
The main puprose of plugins is to allow custom elements which require some complex output behaviour to define that behaviour in separate classes leveraging existing Renderer code. Previously it was usually implemented in the element class itself, leading to unnecessary bloat and code duplication.
dualselect.phpexample installed with the package shows, among other things, how to create a renderer plugin for a custom element.
Default Renderer
Default Renderer – Directly generates HTML using primitive templates
Overview
HTML_QuickForm2_Renderer_Default is essentially a primitive template engine (only supporting blocks and variable placeholders) that is intended for quick prototyping. This Renderer is used under the hood by a magic __toString() method of HTML_QuickForm2_Container (and consequently HTML_QuickForm2 itself). It is similar to HTML_QuickForm_Renderer_Default of HTML_QuickForm though has different template format and more customization possibilities.
Generated HTML is returned by a magic __toString() method, so the renderer can be used in string contexts.
Template syntax
The elements are output in the order they were added using an appropriate template, which looks similar to the following:
<div class="row">
<p class="label">
<qf:required><span class="required">*</span></qf:required>
<qf:label><label for="{id}">{label}</label></qf:label>
</p>
<div class="element<qf:error> error</qf:error>">
<qf:error><span class="error">{error}<br /></span></qf:error>
{element}
</div>
</div>
Here {foo} are placeholders that will be replaced by actual element data and
<qf:bar>...</qf:bar> are blocks that will be either removed if an
element does not have a bar property or will be retained without their
<qf:bar> delimiters if said property is present.
The following placeholders are recognized and will be replaced:
{id}-
'id'attribute of an element.
{class}-
'class'attribute of an element. Mostly needed to put repeat-specificrepeatItemandrepeatPrototypeCSS classes on a outer<div>for a group.
{attributes}- Complete attribute string for an element.
{error}- Error message for an element if it failed validation.
{label}- (Main) label for an element. If an array was passed to setLabel() then this placeholder will be replaced by first element of that array.
{label_more}-
Additional label for an element. If an array was passed to setLabel() then
it will be replaced by be an element of that array with
'more'key.
{element}- String representation of an element returned by its __toString() method. Only in templates for "scalar" elements.
{content}- Contained elements. Only in templates for Containers.
{hidden}-
Collected hidden elements if
group_hiddensis on. Only in template for form.
{errors}-
Collected form errors if
group_errorsis on. Only in template for form.
{reqnote}- Required note if form has required elements. Only in template for form.
{message}- Leading and trailing message for grouped errors. Only in template for errors.
The following blocks are possible:
<qf:required>- Block containing the "element is required" mark. Will be kept if an element has a Required Rule attached.
<qf:error>, <qf:label>, <qf:label_more>, <qf:reqnote>, <qf:message>-
These blocks usually wrap around placeholders having the same name. In the example above,
<qf:error>is also used to set a special CSS class for an invalid element.
Template customization
Form element templates are set by setTemplateForId(),
setTemplateForClass(),
setElementTemplateForGroupId()
and setElementTemplateForGroupClass().
The latter two methods apply to elements that are inside Groups. Note that the word
"class" in these method names refers to PHP class name rather than to CSS class name.
The renderer will also try going up the class hierarchy, so it will use a template for
HTML_QuickForm2_Element to render <input type="text"
/> if more specific templates for
HTML_QuickForm2_Element_InputText and
HTML_QuickForm2_Element_Input are not available.
Another template-related method is setErrorTemplate()
which sets templates for errors output when group_errors is TRUE.
When searching for an appropriate template for a given element, the following order is used:
- If a template was set for that element by setTemplateForId(), it will be used, no matter if the element belongs to a group or not.
- If the element does not belong to a group, templates set via setTemplateForClass() are checked using the element class and going up the class hierarchy.
- If the element belongs to a group, templates set by setTemplateForClass() are ignored, instead templates set by setElementTemplateForGroupId() are searched first using the containing group id, then templates set by setElementTemplateForGroupClass() using the containing group class and going up the class hierarchy.
- If no template was found on previous steps (which is unlikely as Renderer is set up with default templates), some minimal hardcoded template is used.
Rendering groups
When rendering elements inside Container, two templates are actually used:
- Container template, having
{content}placeholder; - Element template.
Elements are first wrapped in their own templates, then {content} placeholder
in Container template is replaced by HTML for all contained elements.
This is true for Groups as well, but is less obvious:
- Default (outer) template for groups looks like template for a scalar element;
- Default (inner) template for elements inside group is minimal:
'{element}'. So you will only get elements' HTML, maybe with separators in between, no additional markup.
The above also means that you will see neither labels for grouped elements, nor their validation errors by default. If you want to output these, you'll need to set a new template for grouped elements using either of setTemplateForId(), setElementTemplateForGroupId(), setElementTemplateForGroupClass():
<?php
// setElementTemplateForGroupId() may be used if you want to customize a specific group
$renderer->setElementTemplateForGroupClass(
'HTML_QuickForm2_Container_Group', 'HTML_QuickForm2_Element',
'a complex template with {element}, {label} and {error} placeholders'
);
?>
controller/wizard.phpexample installed with the package shows setting a complex template for grouped elements.
Array Renderer
Array Renderer – Represents the form as an array
Overview
HTML_QuickForm2_Renderer_Array does not generate HTML itself but rather converts the form structure to an array, which can later be used for generating the actual output. It is a port of HTML_QuickForm_Renderer_Array from HTML_QuickForm though generated arrays have some differences due to differences in form structure.
Array Renderer defines a new configuration parameter for setOption() / getOption():
| Parameter name | Description | Expected type | Default value |
|---|---|---|---|
static_labels |
Applies only to elements having several labels. If FALSE, label key of
the element's array will contain an array of labels as returned by getLabel(). If TRUE element's
array will contain several label_* keys corresponding to the keys in
label array.
|
boolean | FALSE |
HTML_QuickForm2_Renderer_Array::toArray() returns the resultant array and HTML_QuickForm2_Renderer_Array::setStyleForId() adds some (opaque) style information to the element's array that can later be used by a template engine.
renderers/array-twig.phpexample installed with the package shows how to use Array Renderer together with Twig template engine.
Structure of the resultant array
array(
'id' => form's "id" attribute (string),
'frozen' => whether the form is frozen (bool),
'attributes' => attributes for <form> tag (string),
// if form contains required elements:
'required_note' => note about the required elements (string),
// if 'group_hiddens' option is true:
'hidden' => array with html of hidden elements (array),
// if form has some javascript for setup or validation:
'javascript' => form javascript (string)
// if 'group_errors' option is true:
'errors' => array(
'1st element id' => 'Error for the 1st element',
...
'nth element id' => 'Error for the nth element'
),
'elements' => array(
element_1,
...
element_N
)
);
where members of the 'elements' array have the following structure
array(
'id' => element id (string),
'type' => type of the element (string),
'frozen' => whether element is frozen (bool),
// if element has a label:
'label' => 'label for the element',
// note that if 'static_labels' option is true and element's label is an
// array then there will be several 'label_*' keys corresponding to
// labels' array keys
'required' => whether element is required (bool),
// if a validation error is present and 'group_errors' option is false:
'error' => error associated with the element (string),
// if some style was associated with an element:
'style' => some information about element style (e.g. for Smarty),
// if element is not a Container
'value' => element value (mixed),
'html' => HTML for the element (string),
// if element is a Container
'attributes' => container attributes (string)
// only for groups, if separator is set:
'separator' => separator for group elements (array),
'elements' => array(
element_1,
...
element_N
)
);
Stub Renderer
Stub Renderer – Minimum overhead renderer to use when actual form output is done manually
Purpose of Stub Renderer
You may often want to output form elements manually, especially if the form has a complex layout:
<complex html><?= $form->getElementById('someElement'); ?><more complex html>
or, if using a template engine
<complex html>{{ form.getElementById('someElement') }}<more complex html>
However, you will require a rendering step if your form uses client-side validation or contains Javascript-backed elements like Hierselect. Stub renderer can be used in such circumstances to reduce overhead as the results of a more complex renderer like Default will be discarded.
Stub renderer serves as a container for JavascriptBuilder object and does some minimal form
processing: it can group errors and hidden elements if group_errors and
group_hiddens parameters are set to true. The accumulated data is available
through getErrors() and
getHidden() methods,
respectively. You can also use hasRequired() method to
check whether form contains required elements.
Using Stub Renderer
<?php
$renderer = $form->render(
HTML_QuickForm2_Renderer::factory('stub')
->setOption('group_errors', true);
);
// outputting JS libraries
foreach ($renderer->getJavascriptBuilder()->getLibraries() as $link) {
echo $link . "\n";
}
// outputting form errors
foreach ($renderer->getErrors() as $id => $error) {
echo "<a href=\"#{$id}\">{$error}</a><br />";
}
// ...
// form output code goes here
// ...
echo $renderer->getJavascriptBuilder()->getFormJavascript();
?>
Javascript support
Javascript support – Client-side validation and Javascript-backed elements
Overview
If client-side validation in QuickForm2 doesn't work, you probably did not include the file it depends upon.
While HTML_QuickForm implemented client-side validation and provided several Javascript-backed elements, support for Javascript there was quite limited. Basically, all scripts were inlined and there were some rudimentary checks to prevent outputting the same (library) code twice.
This was addressed in HTML_QuickForm2, Javascript handled by the package is logically split into several parts:
- Libraries;
- Form setup code;
- Inline script.
A page containing two Javascript-backed forms will look like this:
...
[JS libraries used by form1 and form2]
...
[form1 html, including inline javascript]
[form1 setup code]
...
[form2 html, including inline javascript]
[form2 setup code]
...
An instance of HTML_QuickForm2_JavascriptBuilder takes care of libraries and setup code, inline code can be added using HTML_QuickForm2_Element_Script element.
The new approach allows keeping the code that does not change (libraries) in external
*.js files, instead of bloating the resultant HTML. If form's structure does
not change either, generated form setup code may also be moved to an external file. Only code
that changes from one request to the other (e.g. setting the element's value) has to be inlined.
Javascript builder class
HTML_QuickForm2_JavascriptBuilder is used together with renderers. Form Javascript is generated in the course of HTML_QuickForm2::render() call, so that call is mandatory if you are using Javascript in your form.
Javascript library files are registered via addLibrary(). You will
only need to call this directly if you create custom Elements or Rules or maybe want to override validation
behaviour. Correct $webPath and
$absPath need to be provided so that later call to getLibraries() can
generate both links to external files and inline code.
Form setup code usually contains client-side validation rules added with addRule() and element
setup code added with addElementJavascript(),
the latter dealing with event handlers necessary for element behaviour. For example, built-in
Hierselect element adds
onchange handlers for contained selects, onreset handler
for a containing form and onload handler for window. Once
again, you will only directly call addElementJavascript() if you are creating
a custom element or want to add some custom setup code. You don't need to call
addRule() at all, as it is done automatically if you added a Rule to an
Element with HTML_QuickForm2_Rule::CLIENT flag set. Javascript added by
these two methods is later returned by getFormJavascript()
method which is usually called automatically by a renderer. It is also possible to use separate
getSetupCode() and
getValidator()
methods.
HTML_QuickForm2_JavascriptBuilder also contains a static helper method
encode() which encodes a
PHP value as a Javascript literal. This is similar to json_encode(), but does not
enforce UTF-8 charset.
Including JS library files
There are currently three library files installed with the package:
quickform.js- Helper methods and validation library.
quickform-hierselect.js- Support functions for hierselect elements.
quickform-repeat.js- Support functions for repeat elements.
Human-readable versions of these files are installed into HTML_QuickForm2/js directory under PEAR's
data_dir and minified versions are installed into HTML_QuickForm2/js/min. If you have trouble finding where
data_dir is, you can use config-show command of PEAR installer.
While form setup code and inline Javascript is automatically added to output when rendering a form, libraries are not added automatically. The main reasons for this are
-
Links to the library files are usually placed in
<head></head>section of HTML document, while renderer outputs only the form itself. -
There is no reliable way to generate
<script src="..."></script>tags referencing installed*.jsfiles. Inlining the code works for package examples but leads to useless bloat in most other circumstances.
Libraries should be added to a page before outputting forms and definitely before outputting form setup code. Both inline scripts and form setup code can contain calls to library functions and changes to library properties.
It is possible to just copy *.js files from PEAR's
data_dir to a directory accessible via HTTP and manually add necessary
<script src="..."></script> tags to the page. You will
however need to manually update this list if you decide to use a new Element or a new Rule which
requires an additional Javascript file. A better approach is to let
HTML_QuickForm2_JavascriptBuilder keep track of the used libraries and
rely on its getLibraries() method to include them.
There are two ways to include the library code in your page using HTML_QuickForm2_JavascriptBuilder::getLibraries():
-
Inline the libraries, including their contents into the page
This is the approach taken by package example files as it is guaranteed to work with no additional steps required from the user. It may also be useful while developing new package-related<?php require_once 'HTML/QuickForm2/Renderer.php'; $renderer = HTML_QuickForm2_Renderer::factory('default'); $form->render($renderer); echo $renderer->getJavascriptBuilder()->getLibraries(true, true); echo $renderer; ?>*.jsfiles as you won't have problems with browser caching them. -
Copy/symlink
*.jsfiles installed with the package to some directory under your website's document root and provide this information to JavascriptBuilder:<?php require_once 'HTML/QuickForm2/Renderer.php'; require_once 'HTML/QuickForm2/JavascriptBuilder.php'; $renderer = HTML_QuickForm2_Renderer::factory('default'); // Here '/path/to/libraries' is whatever directory available via HTTP you copied libraries to $renderer->setJavascriptBuilder(new HTML_QuickForm2_JavascriptBuilder('/path/to/libraries')); $form->render($renderer); // This will output necessary <script src="/path/to/libraries/..."></script> tags foreach ($renderer->getJavascriptBuilder()->getLibraries() as $link) { echo $link . "\n"; } echo $renderer; ?>
The latter approach is obviously recommended for production use.
If your page contains several forms, you'll only need to have one set of libraries for them. Use the same instance of HTML_QuickForm2_JavascriptBuilder when rendering all forms.
dualselect.phpexample shows among other things how to create a custom element with an additional JS library.
Outputting form setup code
Form setup code is returned by HTML_QuickForm2_JavascriptBuilder::getFormJavascript(). This method is automatically called by built-in Renderers and thus the code is included in their output.
Note that HTML_QuickForm2's Javascript library does not use
DOMContentLoaded event like jQuery and similar libraries
do:
$(document).ready(function() {
// some code that will be run when complete DOM tree is built
});
Instead, form setup code is run immediately and should be output after the form, when form's DOM tree is already available.
Multipage forms
Overview of QuickForm2_Controller
Overview of QuickForm2_Controller – Easy building of multipage forms
What is HTML_QuickForm2_Controller?
If you are already familiar with MVC frameworks, then you probably know: controller is a
component that accepts user input and instructs other components to perform actions based on that
input. HTML_QuickForm2_Controller is somewhat smaller in scope than a typical
framework controller since it only deals with forms. When using the Controller, an action name is
sent in GET or POST data, usually by clicking a specially
named button. This name contains id of one of the form pages added to
Controller and name of action handler to call (e.g. 'next' for going forward
in a wizard), that data is extracted and an appropriate action handler is called.
Key features:
- Allows forms spanning multiple pages, stores form data in session between HTTP requests;
- Binds OO action handlers to buttons that submit the form;
- Includes default action handlers that allow easy building of multipage forms;
- Even if you only use it for single-page forms, form-related logic is kept in classes rather than in procedural code.
Key classes and interfaces:
- HTML_QuickForm2_Controller
- Extracts the action name from request and calls the appropriate handler. Contains several Pages and a wrapper for values stored in session.
- HTML_QuickForm2_Controller_Page
- Class representing a single page of the form. Contains an instance of HTML_QuickForm2.
- HTML_QuickForm2_Controller_Action
- Action handlers implement this interface.
HTML_QuickForm2_Controller is a rewrite of PHP4 HTML_QuickForm_Controller, so if you are already using the latter you can go to migration guide which contains step-by-step instructions for porting your scripts to new package.
Basic usage example
Session initialization
This simple example does not use sessions since there is no need to pass data between pages. You'll need to use sessions when dealing with a real multipage form, though. HTML_QuickForm2_Controller does not start a session automatically, you should explicitly call session_start() before instantiating the controller class.
More complex usage examples are installed with the package. Along with a form similar to the one below they include two multipage forms: a tabbed form and a wizard.
The same example form that was used in the package's tutorial will now be rewritten using the Controller:
Builds and processes a form with a single input field, using Controller
<?php
// Load the main class
require_once 'HTML/QuickForm2.php';
// Load the controller
require_once 'HTML/QuickForm2/Controller.php';
// Load the Action interface (we will implement it)
require_once 'HTML/QuickForm2/Controller/Action.php';
// Class representing a form page
class TutorialPage extends HTML_QuickForm2_Controller_Page
{
protected function populateForm()
{
// Add some elements to the form
$fieldset = $this->form->addElement('fieldset')->setLabel('QuickForm2_Controller tutorial example');
$name = $fieldset->addElement('text', 'name', array('size' => 50, 'maxlength' => 255))
->setLabel('Enter your name:');
// We set the name of the submit button so that it binds to default 'submit' handler
$fieldset->addElement('submit', $this->getButtonName('submit'),
array('value' => 'Send!'));
// The action to call if a user presses Enter rather than clicks on a button
$this->setDefaultAction('submit');
// Define filters and validation rules
$name->addFilter('trim');
$name->addRule('required', 'Please enter your name');
}
}
// Action to process the form after successful validation
class TutorialProcess implements HTML_QuickForm2_Controller_Action
{
public function perform(HTML_QuickForm2_Controller_Page $page, $name)
{
$values = $page->getController()->getValue();
echo '<h1>Hello, ' . htmlspecialchars($values['name']) . '!</h1>';
}
}
$page = new TutorialPage(new HTML_QuickForm2('tutorial'));
// We only add the custom 'process' handler, Controller will care for default ones
$page->addHandler('process', new TutorialProcess());
$controller = new HTML_QuickForm2_Controller('tutorial');
// Set defaults for the form elements
$controller->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'name' => 'Joe User'
)));
$controller->addPage($page);
// Process the request
$controller->run();
?>
You may note that the above code is more verbose than the original. While that is definitely
true, to make a three page wizard you'll only need to create three subclasses of HTML_QuickForm2_Controller_Page, a
'process' handler and add them to Controller, which already has default
handlers for typical 'Back' and 'Next' buttons. It will
require a non-trivial amount of programming without the Controller infrastructure.
Creating form pages
To define pages for your controller you need to subclass HTML_QuickForm2_Controller_Page implementing its abstract populateForm() method. Note that this method will be called on demand (i.e. only when the user sees the form in question), so it is better performance-wise to keep all form-building code in it than to pass a pre-populated instance of HTML_QuickForm2 to page's constructor.
Most of the code in the method just repeats what was done in the original tutorial, however some of it requires explanation:
<?php
// We set the name of the submit button so that it binds to default 'submit' handler
$fieldset->addElement('submit', $this->getButtonName('submit'),
array('value' => 'Send!'));
// The action to call if a user presses Enter rather than clicks on a button
$this->setDefaultAction('submit');
?>The first line uses getButtonName() to
set a special name for form's submit button and thus trigger a 'submit'
action on a 'tutorial' form when that button is clicked (default handler for
such action is implemented in HTML_QuickForm2_Controller_Action_Submit). The second one sets an action to
trigger when no submit button is clicked (e.g. user presses Enter instead of
clicking). Under the hood this is done by adding an instance of HTML_QuickForm2_Controller_DefaultAction to the form.
Creating custom action handlers
You'll usually need custom handlers for only two actions:
'process'- Called when the form is submitted and passes validation. In case of multipage forms that means that all the pages are valid.
- 'display'
- Called when a form page needs to be displayed. It does have a default handler, but usually you'll want to tweak the output.
'process' handler is, obviously, completely application-specific and will
usually deal with storing the form values somewhere. When creating a custom
'display' handler it is easiest to subclass HTML_QuickForm2_Controller_Action_Display and override its renderForm()
method to use a Renderer or do any other tweaks you like.
Setting up the controller
First we instantiate the custom Page class and add a custom action handler to it
<?php
$page = new TutorialPage(new HTML_QuickForm2('tutorial'));
// We only add the custom 'process' handler, Controller will care for default ones
$page->addHandler('process', new TutorialProcess());
?>We don't bother with other action handlers as Controller will automatically load and use them.
Then we instantiate the controller
<?php
$controller = new HTML_QuickForm2_Controller('tutorial');
?>Note that Controller needs an id and it should be unique if you have several
Controllers in application, as it is used for storing values in session.
Then we add a DataSource and the page to the Controller
<?php
// Set defaults for the form elements
$controller->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'name' => 'Joe User'
)));
$controller->addPage($page);
?>While it is possible to add DataSource to an instance of HTML_QuickForm2 within Page instead, the above code allows setting the default values for the whole (possibly multipage) form. Note also that Controller's DataSources, unlike form's, are stored in session.
Finally we call the Controller's run() method
<?php
$controller->run();
?>which takes care of finding the name of the current action and calling the necessary handler.
Migration from HTML_QuickForm_Controller
Migration from HTML_QuickForm_Controller – Step-by-step guide for porting your scripts to HTML_QuickForm2_Controller
Overview
This guide is intended for current users of HTML_QuickForm_Controller who want to update their scripts to HTML_QuickForm2, which now includes a rewrite of older controller package. It covers major API changes and provides links to further documentation.
It should be noted that API of HTML_QuickForm2_Controller is more similar to API of HTML_QuickForm_Controller than that of HTML_QuickForm2 and HTML_QuickForm. That being said, there are some important differences in method names and behaviour.
Controller class and session data
Of the methods you are most likely to use in your applications, former HTML_QuickForm_Controller::addAction() is now HTML_QuickForm2_Controller::addHandler() and HTML_QuickForm_Controller::exportValues() is now HTML_QuickForm2_Controller::getValue().
As is the case with HTML_QuickForm2 itself, HTML_QuickForm2_Controller no longer has setDefaults() and setConstants() methods. So instead of former call to HTML_QuickForm_Controller::setDefaults()
<?php
$controller->setDefaults(array(
'foo' => 'default foo value',
'bar' => 'default bar value'
));
?>you should use HTML_QuickForm2_Controller::addDataSource():
<?php
$controller->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
'foo' => 'default foo value',
'bar' => 'default bar value'
)));
?>Like with older defaults and constants, Controller DataSources are stored in session. Note that Controller itself does not have a method for replacing the DataSource array similar to HTML_QuickForm2::setDataSources(), use HTML_QuickForm2_Controller_SessionContainer::storeDatasources().
Instead of the former HTML_QuickForm_Controller::container() method that both returned a reference to a session variable storing Controller data and cleared this variable if requested, there is now getSessionContainer() method that returns an instance of HTML_QuickForm2_Controller_SessionContainer wrapping around session variable and destroySessionContainer() method that clears the session variable.
You no longer need to directly access the session variable to store some custom values:
<?php
// Note the references
// on source page:
$data =& $controller->container();
$data['_my_stuff'] = $stuff;
// later on target page:
$data =& $controller->container();
$stuff = $data['_my_stuff'];
?>you should use instead the storeOpaque() and getOpaque() methods of HTML_QuickForm2_Controller_SessionContainer:
<?php
// on source page:
$controller->getSessionContainer()->storeOpaque('my_stuff', $stuff);
// later on target page:
$stuff = $controller->getSessionContainer()->getOpaque('my_stuff');
?>SessionContainer also has methods for storing and getting Controller DataSources, form values and form validation statuses, these should be used when writing custom action handlers.
Form page class
The main difference between HTML_QuickForm_Page and HTML_QuickForm2_Controller_Page is that the latter no longer extends the form class, its constructor accepting an instance of HTML_QuickForm2:
<?php
class TutorialPage extends HTML_QuickForm2_Controller_Page
{
// ...
}
$page = new TutorialPage(new HTML_QuickForm2('tutorial'));
?>This allows using custom subclasses of HTML_QuickForm2 with Controller and prevents problems like HTML_QuickForm_DHTMLRulesTableless faced, having to include both HTML_QuickForm_DHTMLRulesTableless and HTML_QuickForm_PageDHTMLRulesTableless the former extending HTML_QuickForm and the latter HTML_QuickForm_Page.
$controller is no longer a public property of Page, use getController() to
access it. Similarly, use getForm() to access the
instance of HTML_QuickForm2.
As with HTML_QuickForm_Controller, former HTML_QuickForm_Page::addAction() is now HTML_QuickForm2_Controller_Page::addHandler(), old HTML_QuickForm_Page::buildForm() is renamed to HTML_QuickForm2_Controller_Page::populateForm().
In new HTML_QuickForm2_Controller_Page::populateForm() one no longer has to do something like
<?php
$this->_formBuilt = true;
?>as was needed in old HTML_QuickForm_Page::buildForm(), the Page itself now makes sure that populateForm() is called only once.
Controller action handlers
Controller action handlers – Using available handlers and writing custom ones
Overview
Action handlers in HTML_QuickForm2_Controller define what should happen when request to a script containing the form is made. To make Controller execute a specific action handler after form submit you give a special name to a form's submit button:
<?php
$form->addElement('submit', $this->getButtonName('foo'),
array('value' => 'This button does foo!'));
?>
Action handlers are called via HTML_QuickForm2_Controller_Page::handle()
<?php
$page->handle('foo');
?>which is usually done by HTML_QuickForm2_Controller::run() after finding page id and action name from
request, but can also be done manually. In fact, built-in handlers liberally call other action
handlers, the execution ending with either 'display',
'jump' or 'process'.
Action handlers can be added either to the form Page or to the Controller:
<?php
$page->addHandler('foo', new SpecificActionFoo());
$controller->addHandler('foo', new GenericActionFoo());
?>When Page's handle() method is called it first checks for a handler added via HTML_QuickForm2_Controller_Page::addHandler() and calls its perform() method if present. If a handler is missing it calls Controller's handle() which checks for a handler added via HTML_QuickForm2_Controller::addHandler() and calls its perform() method. If a handler is missing here but its name is known and a default handler is available (see below), it is loaded and added automatically, otherwise an Exception is thrown.
Built-in action names and action handlers
| Built-in name | Default handler | Description |
|---|---|---|
'back' |
HTML_QuickForm2_Controller_Action_Back | This handler should be bound to the "Back" button of
(usually) a wizard-type multipage form, it redirects to the previous page whether current
page is valid or not. |
None, actual name should be equal to id of some
form page. |
HTML_QuickForm2_Controller_Action_Direct | Used to go to a specific page of the form, most useful for non-wizard forms, obviously. |
'next' |
HTML_QuickForm2_Controller_Action_Next | This handler should be bound to the "Next" button of
(usually) a wizard-type multipage form, it redirects to the next page if the current
one is valid (or the form is not wizard). On the last page of a multipage form it behaves
like 'submit'. |
'submit' |
HTML_QuickForm2_Controller_Action_Submit | This handler should be bound to a "global" submit button for a form. It can be
the "submit" button of a single-page or tabbed multi-page form, "finish"
button of a wizard. Default handler checks whether all the pages of the form are valid, then
either calls the 'process' handler or displays the invalid
page. |
| Built-in name | Default handler | Description |
|---|---|---|
'display' |
HTML_QuickForm2_Controller_Action_Display | Displays the form using Default renderer. You should subclass this handler and override its renderForm() method if you want to customize the form output. |
'jump' |
HTML_QuickForm2_Controller_Action_Jump | Performs a HTTP redirect to a given page. |
'process' |
None, application-specific | This is the action called by default 'submit' and
'next' (on the last page of the wizard only) handlers after
successful (i.e. without validation errors) form submit. This action doesn't have a
default handler, you should define the custom one yourself and implement all the necessary
logic to process the form's values in it. |
Writing a custom handler
Basically you need to create a class implementing HTML_QuickForm2_Controller_Action and add necessary logic to its perform() method.
If you intend to bind this action to some submit button via getButtonName(), you should make sure that you store the submitted values in the session container. This is most easily done via HTML_QuickForm2_Controller_Page::storeValues().
As usual, see the built-in handlers' source for the inspiration.
Can actions be bound to something other than submit buttons?
Controller is able to properly handle actions bound to <input type="image"
/> controls, too. You don't have to do anything special, just set the
control's name via getButtonName().
If you want to bind an action to something like a hyperlink, you must consider the following: the form must be submitted to be able to get its values, thus you need to write some javascript that will submit the form and pass the action name to the controller (possibly by setting a name of some hidden element to that name).
URLs
If an instance of HTML_QuickForm2 is not provided with an explicit
'action' attribute it tries to guess it from environment using
$_SERVER['PHP_SELF']. This may not be a best guess when all requests are
routed through index.php.
Controller has even more problems since its 'jump' handler has to build an
absolute URL for a redirect (per RFC 2616). It uses various values from
$_SERVER array for this, but your server may be configured in such a way (e.g.
if you use reverse proxy) that an URL a user sees is quite different from one a script can guess.
Thus the only bulletproof solution sometimes will be to explicitly set the
'action' attribute of all HTML_QuickForm2 instances in
Controller:
<?php
foreach ($controller as $page) {
$page->getForm()->setAttribute('action', 'http://example.com/pretty/url/of/my/form');
}
?>Default handler for 'jump' will use this absolute URL for redirects.
Pretty URLs
A common question is whether it is possible to get rid of "ugly" GET
parameters that appear when redirecting from one form page to the other:
http://example.com/wizard.php?_qf_page2_display=true
The simplest solution will be to get rid of 'jump' altogether and use
'display' in its place. However, this will completely break navigation with
browser's "Back" and "Forward" buttons, so don't do that.
It is possible to use an URL like
http://example.com/wizard/page2
instead of the above one, but it'll be more difficult:
- Make sure that the "pretty" URL actually routes to a script containing the Controller.
- Create a custom
'jump'handler that'll redirect to a "pretty" URL instead of an "ugly" one. - Either change HTML_QuickForm2_Controller::getActionName() to be able to get action name from a
"pretty" URL or add a key with "ugly" action name to
$_REQUEST.