Zend Framework 1.10.1 理解和使用 Zend 表单装饰器之五:创建和呈现复合元素

在上一节中,我们有一个展示一个出生日期元素的例子:

<div class="element">
    <?php echo $form->dateOfBirth->renderLabel(0 ?>
    <?php echo $this->formText('dateOfBirth[day]', '', array('size' => 2, 'maxlength' =>2)) ?>
    /
    <?php echo $this->formText('dateOfBirth[month]', '', array('size' => 2, 'maxlength' =>2)) ?>
    /
    <?php echo $this->formText('dateOfBirth[year]', '', array('size' => 4, 'maxlength' => 4)) ?>
</div>

你如何把这个元素当作一个 Zend_Form_Element 来展现?你如何写一个装饰器来呈现它?

元素
元素是如何工作的,这个问题包括:

你如何设置和检索值?
你如何验证值?
不管其它的,你如何允许分离的表单 input 接受这三个片断(日,月,年)?

头两个问题围绕表单元素它自己:setValue() 和 getValue() 是如何工作的?事实上由这个问题可以暗示到关于装饰器的一个问题:你如何检索从元素得来的分离的数据片断以及/或者设置它们?

解决的办法是重写你的元素的 setValue() 方法来提供一些定制的逻辑。在这个特定的例子,我们的元素应该有三个分离的行为:

如果提供一个整型的时间戳,它应该被用作确定和储存日,月和年。
如果提供一个文字字符串,它应该能被转换成一个时间戳,然后这个值会用作确定和储存日,月和年。
如果提供一个包括日,月和年为键值的数组,那些值将会被储存。

内部的,日,月和年将被分别储存。当元素的值被检索到的时候,它将以一种规范化的字符串格式储存。我们重写 getValue() 也把分离的数据片断组合进一个最终的字符串。

我们的类将看起来是这样的:

class My_Form_Element_Date extends Zend_Form_Element_Xhtml
{
    protected $_dateFormat = '%year%-%month%-%day%';
    protected $_day;
    protected $_month;
    protected $_year;

    public function setDay($value)
    {
        $this->_day = (int) $value;
        return $this;
    }

    public function getDay()
    {
        return $this->_day;
    }

    public function setMonth($value)
    {
        $this->_month = (int) $value;
        return $this;
    }

    public function getMonth()
    {
        return $this->_month;
    }

    public function setYear($value)
    {
        $this->_year = (int) $value;
        return $this;
    }

    public function getYear()
    {
        return $this->_year;
    }

    public function setValue($value)
    {
        if (is_int($value)) {
            $this->setDay(date('d', $value))
                 ->setMonth(date('m', $value))
                 ->setYear(date('Y', $value));
        } elseif (is_string($value)) {
            $date = strtotime($value);
            $this->setDay(date('d', $date))
                 ->setMonth(date('m', $date))
                 ->setYear(date('Y', $date));
        } elseif (is_array($value)
            && (isset($value['day'])
                && isset($value['month'])
                && isset($value['year'])
            )
        ) {
            $this->setDay($value['day'])
                 ->setMonth($value['month'])
                 ->setYear($value['year']);
        } else {
            throw new Exception('Invalid date value provided');
        }
   
        return $this;
    }

    public function getValue()
    {
        return str_replace(
            array('%year%', '%month%', '%day%'),
            array($this->getYear(), $this->getMonth(), $this->getDay()),
            $this->_dateFormat
        );
    }
}

这个类提供了一些不错的扩展性--我们可以从我们的数据库中设置默认的值,同时确信值将会被保存而且正确的表现。另外,我们允许通过表单 input 用一个数组来传递值。最后,对于每一个日期片断,我们有了分离的访问器(accessors),我们现在可以把这些访问器应用在一个装饰器中,来创建一个合成元素。

装饰器
再看一下上一节提到的例子,让我们假设我们想让用户分别输入年,月,日。PHP 很幸运的允许我们在创建元素的时候使用数组表示法,所以它仍有可能采集这三个实体合成一个单一的值 -- 同时我们已经创建了一个 Zend_Form 元素可以处理这样一个数组。

装饰器相对简单:它会从元素抓取日,月和年,把每一个传递给一个分离视图帮助器来呈现个人的表单 input,然后这些会被合并,形成最后的标记(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 in 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'        => 2,
            'maxlength'    => 2,
        );

        $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;
        }
    }
}

我们现在得对我们的表单元素做一个小调整,告诉它我们想默认地使用上面的装饰器。这需要两个步骤。首先,我们需要把装饰器的路径通知这个元素。我们可以在构造函数中完成:

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);
    }

    // ...
}

注意这些是在构造函数中完成,而不是在 init()。这有两个原因。首先,它允许以后在 init 扩展元素添加逻辑,而不必担心调用 parent::init()。其次,它允许通过设置(configuration)或者在一个 init 方法内传递额外的插件,然后允许用我自己的替代者来重写默认的日期装饰器。

下一步,我们需要使用我们新的日期装饰器来重写 loadDefaultDecorators() 方法:

class My_Form_Element_Date extends Zend_Form_Element_Xhtml
{
    // ...

    public function loadDefaultDecorators()
    {
        if ($this->loadDefualtDecoratorsIsDisabled()) {
            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'));
        }
    }

    // ...
}

最后的输出是什么样子的?让我们想一下下面的元素:

$d = new My_Form_Element_Date('dateOfBirth');
$d->setLabel('Date of Birth: ')
  ->setView(new Zend_View());

// There are equivalent:
$d->setValue('20 April 2009');
$d->setValue(array('year' => '2009', 'month' => '04', 'day' => '20'));

如果你之后输出这个元素,你会得到下面的标记(markup)(对空格已经做过修改以便更好的阅读):

<dt id="dateOfBirth-label"><labe 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>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值