Revisiting the example from the last section, let's assume that we want users to input
each of the year, month, and day separately. PHP fortunately allows
us to use array notation when creating elements, so it's still possible to capture these
three entities into a single value -- and we've now created a
Zend_Form element that can handle such an array value.
The decorator is relatively simple: it will grab the day, month, and year from the element, and pass each to a discrete view helper to render individual form inputs; these will then be aggregated to form the final markup.
class My_Form_Decorator_Date extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
$element = $this->getElement();
if (!$element instanceof My_Form_Element_Date) {
// only want to render Date elements
return $content;
}
$view = $element->getView();
if (!$view instanceof Zend_View_Interface) {
// using view helpers, so do nothing if no view present
return $content;
}
$day = $element->getDay();
$month = $element->getMonth();
$year = $element->getYear();
$name = $element->getFullyQualifiedName();
$params = array(
'size' => 2,
'maxlength' => 2,
);
$yearParams = array(
'size' => 4,
'maxlength' => 4,
);
$markup = $view->formText($name . '[day]', $day, $params)
. ' / ' . $view->formText($name . '[month]', $month, $params)
. ' / ' . $view->formText($name . '[year]', $year, $yearParams);
switch ($this->getPlacement()) {
case self::PREPEND:
return $markup . $this->getSeparator() . $content;
case self::APPEND:
default:
return $content . $this->getSeparator() . $markup;
}
}
}
We now have to do a minor tweak to our form element, and tell it that we want to use the above decorator as a default. That takes two steps. First, we need to inform the element of the decorator path. We can do that in the constructor:
class My_Form_Element_Date extends Zend_Form_Element_Xhtml
{
// ...
public function __construct($spec, $options = null)
{
$this->addPrefixPath(
'My_Form_Decorator',
'My/Form/Decorator',
'decorator'
);
parent::__construct($spec, $options);
}
// ...
}
Note that this is being done in the constructor and not in init().
This is for two reasons. First, it allows extending the element later to add logic in
init without needing to worry about calling
parent::init(). Second, it allows passing additional plugin paths
via configuration or within an init method that will then allow
overriding the default Date decorator with my own replacement.
Next, we need to override the loadDefaultDecorators() method to use
our new Date decorator:
class My_Form_Element_Date extends Zend_Form_Element_Xhtml
{
// ...
public function loadDefaultDecorators()
{
if ($this->loadDefaultDecoratorsIsDisabled()) {
return;
}
$decorators = $this->getDecorators();
if (empty($decorators)) {
$this->addDecorator('Date')
->addDecorator('Errors')
->addDecorator('Description', array(
'tag' => 'p',
'class' => 'description'
))
->addDecorator('HtmlTag', array(
'tag' => 'dd',
'id' => $this->getName() . '-element'
))
->addDecorator('Label', array('tag' => 'dt'));
}
}
// ...
}
What does the final output look like? Let's consider the following element:
$d = new My_Form_Element_Date('dateOfBirth');
$d->setLabel('Date of Birth: ')
->setView(new Zend_View());
// These are equivalent:
$d->setValue('20 April 2009');
$d->setValue(array('year' => '2009', 'month' => '04', 'day' => '20'));
If you then echo this element, you get the following markup (with some slight whitespace modifications for readability):
<dt id="dateOfBirth-label"><label for="dateOfBirth" class="optional">
Date of Birth:
</label></dt>
<dd id="dateOfBirth-element">
<input type="text" name="dateOfBirth[day]" id="dateOfBirth-day"
value="20" size="2" maxlength="2"> /
<input type="text" name="dateOfBirth[month]" id="dateOfBirth-month"
value="4" size="2" maxlength="2"> /
<input type="text" name="dateOfBirth[year]" id="dateOfBirth-year"
value="2009" size="4" maxlength="4">
</dd>




