View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFDEV:Zend Proposal Zone Template}
{composition-setup}
{zone-data:component-name}
Zend_Form
{zone-data}

{zone-data:proposer-list}
[~matthew]
{zone-data}

{zone-data:revision}
0.9.0 - 19 December 2007: Initial proposal creation
0.9.1 - 03 January 2008: Minor formatting and grouping changes and corrections
0.9.2 - 03 January 2008: Updates to address community feedback
0.9.3 - 04 January 2008: Add grouping to proposal
0.9.4 - 05 January 2008: Added information on validator/filter enhancements, additional view helpers
0.9.5 - 08 January 2008: Added pluginLoader details to class skeletons
0.9.6 - 09 January 2008: Updated to reflect decorator usage
0.9.7 - 10 January 2008: Updated to reflect current API
1.0.0 - 17 January 2008: Updated with link to initial docs
{zone-data}

{zone-data:overview}
*NOTE:* [Initial documentation is now available|ZFDEV:Zend_Form Notes]

Form processing is a routine and common task for web developers that involves many facets: creation of form HTML, creation of server side validation, and escaping form elements for display on-screen or insertion into data storage; in today's web, we also must consider whether or not a user will be able to interact dynamically with a form element using AJAX.

Considering that web forms are the key to dynamic content on websites, usage of forms should be as easy as possible. Form validation and filtering logic should be encapsulated; if possible, auto-generation of HTML would be desirable.

Zend_Form proposes to do the following things:
* Form element objects encapsulate the following:
** Validation chains
** Filter chains
** Rendering
*** Decorator classes for elements and forms
*** Default decorator classes for HTML generation
*** Allow multiple decorators
** Localization hinting (through [Zend_View_Helper_Translate|http://framework.zend.com/wiki/x/CJI])
** Ability to set element state from a config file
* Form object encapsulates all form elements
** Setting current locale in all elements
** single entry point for validating all elements at once
** accessors for retrieving individual form elements
** ability to generate full HTML
*** loops over all form elements and renders them
** methodology for evaluating AJAX-submitted forms
*** allows validating only passed parameters, not entire form
** Ability to create all form elements and set form state from a config file
{zone-data}

{zone-data:references}
* [Simon Mundy's Zend_Form proposal|http://framework.zend.com/wiki/pages/viewpage.action?pageId=3596]
* [Jurriƫn Stutterheim's Zend_Form proposal|http://framework.zend.com/wiki/pages/viewpage.action?pageId=36061]
* [Mitchell Hashimoto's Zend_Form proposal|http://framework.zend.com/wiki/display/ZFPROP/Zend_Form+-+Mitchell+Hashimoto]
* [Ruby on Rails|http://www.rubyonrails.org]
* [Symfony project|http://www.symfony-project.com]
* [Solar_Form|http://solarphp.com/]
* [Django|http://www.djangoproject.com]
* [CGI::Application::Plugin::ValidateRM|http://search.cpan.org/perldoc?CGI%3A%3AApplication%3A%3APlugin%3A%3AValidateRM]
* [AjaxContext action helper|http://framework.zend.com/wiki/x/R6I]
* [JSON view helper|http://framework.zend.com/wiki/x/TaI]
{zone-data}

{zone-data:requirements}
* *Must* be de-coupled from Zend_Controller and Zend_View
** Form setup and validation *must* be possible without Zend_Controller
** *Should* accept data to validate, and not retrieve it automatically from the environment
** View rendering *should* be *optional*, and allow for usage of *either* Zend_View or custom rendering functionality
* *Must* provide separation between validation, filtering, and rendering
** *Must* be flexible enough to allow non-XHTML rendering schemas
** *Must* have a concept of required/non-required elements
** *Must* have methods for:
*** Validation of entire form
*** Validation of partial form
*** Hinting that a request was done via AJAX
* *Must* re-use existing components as much as possible
** Zend_Filter
** Zend_Json
** Zend_Locale
** Zend_Translate
** Zend_Validate
** Zend_View_Helper_*
* *Must* be able to do both automated and manual rendering of form items
** Automated rendering:
*** *Must* be capable of displaying labels, form elements, and error messages
*** *Must* re-use existing form helpers when possible
*** *Must* be capable of localization
*** *Must* be AJAX-friendly
**** *Should* allow validation of single elements or groups of elements, with a single response
***** Response *should* default to JSON serialization in a documented format
***** Response format *should* be configurable via subclassing
**** *Should* define element IDs
**** *Should* allow defining arbitrary HTML accessors for use with JS (on* attributes and/or JS-library specific)
**** *Could* provide a div for error message display
* *Must* be able to order form elements
** Default *should* be order in which elements are added to form object
* *Must* be able to load form and individual form elements from config objects
* *Could* be capable of handling multi-page forms (i.e., saves form state between requests)
** *Should* provide examples of how multi-page forms could be handled even if no implicit handling is provided.
* *Should* allow grouping of form elements for display and/or validation purposes
* *Could* allow lazy-loading of validators and/or filters when needed
* *Should* either implement validator/filter factories, or require their development
* *Could* be extended to allow direct interaction with a model class (such as a Zend_Db_Table)
* *Must* provide examples of common Ajax patterns
** Autocompletion
** Validation of single/multi elements over AJAX
{zone-data}

{zone-data:dependencies}
* Zend_Config (optional)
* Zend_Exception
* Zend_Filter_*
* Zend_Json (optional; only used with AJAX)
* Zend_Locale (optional, through Zend_View_Helper_Translate)
* Zend_Loader_PluginLoader (for use with factories)
* Zend_Translate (optional, through Zend_View_Helper_Translate)
* Zend_Validate_*
* Zend_View (optional - for rendering and localization)
{zone-data}

{zone-data:operation}
h3. Overview
Zend_Form encapsulates any number of Zend_Form_Elements. Each element contains its own validation and filter chains, as well as mechanisms for rendering (be it via view helpers or other mechanisms).

Zend_Form contains metadata about the form itself, including potentially any HTML attributes used to define the form element. It then has methods for validating against all elements or a subset of elements, retrieving errors and error messages (if any), retrieving individual elements, and rendering the form.

Zend_Form_Element will contain metadata about individual elements. Overloading will be provided to allow setting arbitrary metadata, and by default used as HTML element attributes. Additionally, each element will contain its own validator and filter chains, and hinting about how to render itself.

Elements and the form object itself will each have decorators associated with them. By default, elements will use 'ViewHelper', 'Label', 'Errors', and 'HtmlTag' decorators, which will allow using existing Zend_View helpers for rendering the element, label, and errors, and surround the element in an HTML div. Zend_Form_ElementGroup will surround its content using 'ViewHelper' to select the Fieldset view helper, and Zend_Form will use a new Form view helper. Alternate decorators may be specified at any time, and may be nested (first in will be innermost) or chained (with options to prepend or append to content).

Zend_Form and Zend_Form_Element will each have accessors for setting a Zend_Translate object, allowing localization of the form. The various Zend_View_Helper_Form* view helpers will be retrofitted to allow translation, if a translation object is present (most likely by utilizing the proposed Zend_View_Helper_Translate view helper).

Developers will be able to specify Filters and Validators using strings that indicate the full class name and/or the short class name, as well as constructor options. This will enable using Zend_Config to configure a form object. Additionally, validators used with Zend_Form will be passed a second argument to isValid(), $context, which will contain all elements being validated; this will allow validating elements in relation to other submitted values, if required. Finally, all filters and validators will be accessible by name, allowing modification in as well as removal from their respective chains.

Separate proposals for generic AJAX integration with the Zend Framework MVC will be created, including:
* [AjaxContext|http://framework.zend.com/wiki/x/R6I] action helper, for switching view context when XHR requests are detected and based on the response format requested
* [JSON view helper|http://framework.zend.com/wiki/x/TaI], for returning JSON responses
* [Autocompleter action helper|http://framework.zend.com/wiki/x/q6I], for serializing an array to JSON and returning a response

h3. Zend_Form_Element
Functionality includes:
* Zend_Validate validator chain; attach as few or as many validators as needed
** Allow specifying validators as:
*** objects
*** full class names
*** short class names (minus prefix)
* Zend_Filter filter chain; attach as few or as many filters as needed
** Allow specifying filters as:
*** objects
*** full class names
*** short class names (minus prefix)
* Allows marking element as required/optional (allowing skipping validation if empty or not present)
* getValue() retrieves filtered value by default
* getRawValue() for retrieving original provided value
* Accessor for setting label
* Accessors for retrieving errors and messages
* Accessors for setting arbitrary attributes (possibly via overloading)
* Optionally locale aware
** if so, all messages and labels will be localized. Configures Zend_View_Helper_Translate with Zend_Translate object provided to element.
* Method for validating
* Methods for manipulating decorators for rendering an element
* Method for rendering
** Uses any attached decorators
** __toString() will proxy to render()
* Method for loading state via a Zend_Config

h4. Typical workflow:
{code:php}
$username = new Zend_Form_Element_Text('username');
$username->setLabel('User name')
->addValidator(new Zend_Validate_NotEmpty())
->addValidator('EmailAddress))
->addFilter('StringToLower');
if ($username->isValid()) {
echo "Welcome, ", $username, "!"; // "Welcome, foobar@foo.com!"
} else {
$messages = $username->getMessages();
echo "Error in validation:\n ", implode("\n ", $messages);
}
{code}

h4. Standard elements to ship with first iteration:
* Button
* Checkbox
* Hidden
* Image
* Multiselect
* Password
* Radio
* Reset
* Select
* Submit
* Textarea
* Text

h4. Standard decorators to ship with first iteration:
* ViewHelper

h3. Zend_Form
Functionality includes:
* Accessors for adding, removing, and retrieving Zend_Form_Element objects
** Adding elements allows optionally setting order
* Accessors for setting arbitrary attributes (possibly via overloading)
* Iterable; iterates over attached elements
* Accessor for setting locale
* getValues() retrieves filtered element values
* getValue($name) retrieves single filtered element value
* getRawValues() retrieves unfiltered element values
* getRawValue($name) retrieves single unfiltered element value
* Accessors for retrieving errors and messages for all values or single value
* Methods for validating:
** full form (isValid())
** partial form (isValidPartial())
** AJAX versions of each of the above
*** Returns standardized JSON response
* Methods for manipulating decorators for rendering the form
* Method for rendering
** Uses associated decorators
** __toString() proxies to it
* Method for loading state of form and all elements via a Zend_Config
* (optional) Ability to group elements into sections/pages
{zone-data}

{zone-data:milestones}
* Milestone 1: \[DONE\] Create prototype
* Milestone 2: \[DONE\] Create proposal and submit for community review
* Milestone 3: \[DONE\] Finalize form element base code and individual elements, including tests
* Milestone 4: \[DONE\] Finalize form base code, including tests
* Milestone 5: \[DONE\] Test rendering, including JSON responses
* Milestone 6: Documentation, demos, and tutorials
* Milestone 7: Component moved to core

If a milestone is already done, begin the description with "\[DONE\]", like this:
* Milestone #: \[DONE\] Unit tests ...
{zone-data}

{zone-data:class-list}
* Zend_Form classes:
** Zend_Form
** Zend_Form_Decorator_Interface
** Zend_Form_Decorator_Abstract
** Zend_Form_Decorator_Errors
** Zend_Form_Decorator_HtmlTag
** Zend_Form_Decorator_Label
** Zend_Form_Decorator_ViewHelper
** Zend_Form_Decorator_ViewScript
** Zend_Form_Element
** Zend_Form_ElementGroup
** Zend_Form_Element_Exception
** Zend_Form_Element_Autocomplete
** Zend_Form_Element_Button
** Zend_Form_Element_Checkbox
** Zend_Form_Element_Hidden
** Zend_Form_Element_Image
** Zend_Form_Element_Multi
** Zend_Form_Element_Multiselect
** Zend_Form_Element_Password
** Zend_Form_Element_Radio
** Zend_Form_Element_Reset
** Zend_Form_Element_Select
** Zend_Form_Element_Submit
** Zend_Form_Element_Textarea
** Zend_Form_Element_Text
** Zend_Form_Element_Xhtml
** Zend_Form_Exception
* Zend_View helpers:
** Zend_View_Helper_Fieldset
** Zend_View_Helper_Form
** Zend_View_Helper_FormErrors
{zone-data}

{zone-data:use-cases}
{deck:id=Use Cases}
{card:label=UC-01}
h3. Creating form programmatically
{code:php}
$name = new Zend_Form_Element_Text('name');
$name->addValidator('NotEmpty')
->addFilter('StringToLower');
$email = new Zend_Form_Element_Text('email');
$email->addValidator('NotEmpty')
->addValidator('EmailAddress')
->addFilter('StringToLower');
$submit = new Zend_Form_Element_Submit('send', 'Send');
$form = new Zend_Form();
$form->addElement($name)
->addElement($email)
->addElement($submit);
if ($form->isValid($data)) {
// success
} else {
// failure
$errors = $form->getMessages();
// ...
}
{code}
{card}

{card:label=UC-02}
h3. Create custom form class
{code:php}
class MyForm extends Zend_Form
{
public function __construct()
{
$name = new Zend_Form_Element_Text('name');
$name->addValidator('NotEmpty')
->addFilter('StringToLower');
$email = new Zend_Form_Element_Text('email');
$email->addValidator('NotEmpty')
->addValidator('EmailAddress')
->addFilter('StringToLower');
$submit = new Zend_Form_Element_Submit('send', 'Send');

$this->addElement($name)
->addElement($email)
->addElement($submit);
}
}

$form = new MyForm();
if ($form->isValid($data)) {
// success
} else {
// failure
$errors = $form->getMessages();
// ...
}
{code}
{card}

{card:label=UC-03}
h3. Validating partial forms
{code:php}
$data = array('name' => 'value');
if ($form->isValidPartial($data)) {
// success
} else {
// failure
}
{code}
{card}

{card:label=UC-04}
h3. Validating and returning JSON response
{code:php}
if (!$form->processXhr($data)) {
// errors
}
{code}
{card}

{card:label=UC-05}
h3. Render a form
{code:php}
// Manually:
$output = $form->render();

// Via __toString():
$output = (string) $form;
echo $form;

// In a view script:
<?= $this->form ?>
{code}
{card}

{card:label=UC-06}
h3. Render an individual element
{code:php}
// Manually:
$output = $element->render();

// Via __toString():
$output = (string) $element;
echo $element;

// From a form object:
$output = $form->getElement('name')->render();
echo $form->getElement('name');

// In a view script:
<?= $this->form->getElement('name') ?>
{code}
{card}

{card:label=UC-07}
h3. Group elements
Sometimes, it makes sense to group elements:
* to display within separate fieldsets
* to validate separately (for instance, to validate a group of elements via AJAX in order to determine the next set of elements to display in a form)
* to allow for multi-page forms

The addGroup() method allows you to group elements, and simply accepts a Zend_Form_Abstract object. The Zend_Form_ElementGroup class is a special Zend_Form_Abstract implementation that renders the elements within an HTML fieldset, using the legend provided as the fieldset legend.

{code:php}
class MyForm extends Zend_Form
{
public function __construct()
{
$name = new Zend_Form_Element_Text('name');
$name->addValidator('NotEmpty')
->addFilter('StringToLower');
$email = new Zend_Form_Element_Text('email');
$email->addValidator('NotEmpty')
->addValidator('EmailAddress')
->addFilter('StringToLower');
$demog = new Zend_Form_ElementGroup();
$demog->setLegend('Your Information')
->addElement($name)
->addElement($email);

$submit = new Zend_Form_Element_Submit('send', 'Send');

$this->addGroup($demog, 'demog')
->addElement($submit);
}
}

{code}
{card}

{card:label=UC08}
h3. Decorators
You may wish to specify your own decorators for rendering an element. The default decorators are built in the element constructors, using code like the following:
{code:php}
class My_Form_Element
public function __construct($name = null, $options = null)
{
// ...
$this->addDecorator('ViewHelper', array('helper' => 'formText'))
->addDecorator('Label')
->addDecorator('Errors')
->addDecorator('HtmlTag', array('tag' => 'div', 'class' => 'form element'));
}
}
{code}

Let's change this slightly; instead of using a decorator chain such as the above, let's instead use a view script to render the element:
{code:php}
$element = new My_Form_Element;
$element->clearDecorators()
->addDecorator('ViewScript', array('script' => 'my_element.phtml');
{code}

Decorators can either transform the content provided to them, or simply append or prepend; it depends on the decorator. For instance, the 'Label' decorator by default prepends to the content passed to it, while 'Errors' appends to it. You can modify the placement (i.e., append or prepend) by passing the 'placement' option to the decorator:

{code:php}
$element->addDecorator('Label', array('placement' => 'append'))
->addDecorator('Errors', array('placement' => 'prepend'));
{code}

In such cases, a separator is typically placed between the original and added content; by default, most decorators use PHP_EOL for the separator. You can specify a custom separator by passing the 'separator' option to the decorator:

{code:php}
$element->addDecorator('Label', array('separator', '<br />'));
{code}

While many decorators append or prepend, others, such as 'HtmlTag', do not, and instead transform the content (in the case of HtmlTag, it surrounds the content with an HTML tag). As a result, the order in which the decorators are added becomes important.
{card}
{deck}
{zone-data}

{zone-data:skeletons}
{deck:id=Skeletons}
{card:label=Zend_Form}
{code:php}
class Zend_Form implements Iterator, Countable
{
public function setConfig(Zend_Config $config);

// Loaders
public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type)
public function getPluginLoader($type)
public function addPrefixPath($prefix, $path, $type = null)

// Element interaction:
public function setElements(array $elements);
public function addElement($element, $name = null);
public function getElement($name);
public function getElements();
public function removeElement($name);
public function setDefaults(array $defaults);
public function setDefault($name, $value);
public function getValue($name);
public function getValues();
public function getUnfilteredValue($name);
public function getUnfilteredValues();
public function __get($name);

// Element groups:
public function addGroup(Zend_Form_Abstract $group, $id, $order = null);
public function getGroup($id);
public function removeGroup($id);

// Processing
public function populate(array $values); // alias to setDefaults()
public function isValid(array $data);
public function isValidPartial(array $data);
public function processAjax($request);
public function persistData();
public function getErrors($name = null);
public function getMessages($name = null);

// Rendering
public function setView(Zend_View_Interface $view);
public function getView();
public function addDecorator($decorator, $options = array());
public function addDecorators(array $decorator);
public function setDecorators(array $decorator);
public function getDecorator($name);
public function getDecorators();
public function removeDecorator($name);
public function clearDecorators();
public function render(Zend_View_Interface $view = null);
public function __toString();

// Localization:
public function setTranslator(Zend_Translate_Adapter $translator);
public function getTranslator();

// For iteration, countable:
public function current();
public function key();
public function next();
public function rewind();
public function valid();
public function count();
}
{code}
{card}

{card:label=Zend_Form_Element}
{code:php}
class Zend_Form_Element
{
public function __construct($spec)
public function setOptions(array $options)
public function setConfig(Zend_Config $config)

// Localization:
public function setTranslator(Zend_Translate_Adapter $translator)
public function getTranslator()

// Metadata
public function setName($name)
public function getName()
public function setValue($value)
public function getValue()
public function getUnfilteredValue()
public function setLabel($label)
public function getLabel()
public function getType()
public function setAttrib($name, $value)
public function setAttribs(array $attribs)
public function getAttrib($name)
public function getAttribs()
public function __get($key)
public function __set($key, $value)

// Loaders
public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type)
public function getPluginLoader($type)
public function addPrefixPath($prefix, $path, $type = null)

// Validation
public function addValidator($validator, $breakChainOnFailure = false, $options = array())
public function addValidators(array $validators)
public function setValidators(array $validators)
public function getValidator($name)
public function getValidators()
public function removeValidator($name)
public function clearValidators()
public function isValid($value, $context = null)
public function getErrors()
public function getMessages()

// Filtering
public function addFilter($filter)
public function addFilters(array $filters)
public function setFilters(array $filters)
public function getFilter($name)
public function getFilters()
public function removeFilter($name)
public function clearFilters()

// Rendering
public function setView(Zend_View_Interface $view)
public function getView()
public function addDecorator($decorator, $options = array());
public function addDecorators(array $decorator);
public function setDecorators(array $decorator);
public function getDecorator($name);
public function getDecorators();
public function removeDecorator($name);
public function clearDecorators();
public function render(Zend_View_Interface $view = null)
public function __toString()
}
{code}
{card}

{card:label=Zend_Form_ElementGroup}
The Zend_Form_ElementGroup class is an extension of Zend_Form_Abstract, and simply uses Zend_View_Helper_Fieldset to render its contents. Otherwise, it acts like any other Zend_Form_Abstract object, and allows for nested grouping.
{code:php}
class Zend_Form_ElementGroup extends Zend_Form_Abstract
{
}
{code}
{card}
{deck}

{deck:id=SkeletonsIa}
{card:label=Zend_Form_Decorator_Interface}
{code:php}
interface Zend_Form_Decorator_Interface
{
public function __construct($options = null);
public function setElement($element);
public function getElement();
public function setOptions(array $options);
public function setConfig(Zend_Config $config);
public function getOptions();
public function render($content);
}
{code}
{card}

{card:label=Zend_Form_Decorator_Abstract}
{code:php}
abstract class Zend_Form_Decorator_Abstract implements Zend_Form_Decorator_Interface
{
public function __construct($options = null)
public function setOptions(array $options)
public function setConfig(Zend_Config $config)
public function getOptions()
public function setElement($element)
public function getElement()
public function getPlacement()
public function getSeparator()
public function render($content)
}
{code}
{card}

{card:label=Zend_Form_Decorator Concrete Classes}
{code:php}
class Zend_Form_Decorator_Errors extends Zend_Form_Decorator_Abstract
{
public function render($content);
}
{code}

{code:php}
class Zend_Form_Decorator_HtmlTag extends Zend_Form_Decorator_Abstract
{
public function normalizeTag($tag);
public function render($content);
}
{code}

{code:php}
class Zend_Form_Decorator_Label extends Zend_Form_Decorator_Abstract
{
public function render($content);
}
{code}

{code:php}
class Zend_Form_Decorator_ViewHelper extends Zend_Form_Decorator_Abstract
{
public function setHelper($helper);
public function getHelper();
public function render($content);
}
{code}
{card}
{deck}

{deck:id=SkeletonsII}
{card:label=Zend_View_Helper_Form}
{code:php}
class Zend_View_Helper_Form
{
public function setView(Zend_View_Interface $view);
public function form();
}
{code}
{card}

{card:label=Zend_View_Helper_FormErrors}
The FormErrors helper provides a standard HTML rendering of an array of form element errors.
{code:php}
class Zend_View_Helper_FormErrors
{
public function setView(Zend_View_Interface $view);
public function formErrors(array $errors);
}
{code}
{card}

{card:label=Zend_View_Helper_Fieldset}
{code:php}
class Zend_View_Helper_Fieldset
{
public function setView(Zend_View_Interface $view);
public function fieldset($id = null, $legend = null);
}
{code}
{card}

{deck}
{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>