开放的编程资料库

当前位置:我爱分享网 > PHP教程 > 正文

创建复合元素

在我上一篇关于装饰器的文章中,我有一个显示“出生日期”元素的示例:

<div class=\"element\">
    <?php echo $form->dateOfBirth->renderLabel() ?>
    <?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以及如何编写装饰器来封装此逻辑的问题。幸运的是,我已经计划好在这篇文章中解决这些问题!

元素

关于元素如何工作的问题包括:

  • 您将如何设置和检索该值?
  • 您将如何验证该值?
  • 无论如何,您将如何允许三段式的离散形式输入(日、月、年)?

前两个问题围绕表单元素本身:setValue()getValue()是如何工作的?关于装饰器的问题实际上还隐含了另一个问题:如何从元素中检索离散的日期段和/或设置它们?

解决方案是覆盖元素的setValue()方法以提供一些自定义逻辑。在这种特殊情况下,我们的元素应该具有三个独立的行为:

  • 如果提供了一个整数时间戳,它应该被用来确定和存储日、月、年
  • 如果提供了一个文本字符串,它应该被转换为一个时间戳,并且然后用于确定和存储日、月和年的值
  • 如果提供了包含日期、月和年的键的数组,则应存储这些值

在内部,日、月、年将被离散存储。当元素的值被检索时,它将以规范化的字符串格式完成。我们将覆盖getValue()并将离散的日期段组装成最终字符串。

类应该是这样的:

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

这个类提供了一些很好的灵活性——我们可以从我们的数据库中设置默认值,并确保该值将被正确存储和表示。此外,我们可以允许从通过表单输入传递的数组中设置值。最后,我们为每个日期段提供了独立的访问器,我们现在可以在装饰器中使用它们来创建复合元素。

装饰器

回顾上一篇文章中的示例,假设我们希望用户分别输入年月日。幸运的是,PHP允许我们在创建元素时使用数组表示法,因此仍然可以将这三个实体捕获到一个值中—我们现在已经创建了一个可以处理这样一个数组值的Zend_Form元素。

装饰器相对简单:它将从元素中获取日、月和年,并将每个传递给一个离散的视图助手以呈现单个表单输入;然后这些将被聚合以形成最终标记。

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

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

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()。其次,它允许我通过配置或在init方法中传递额外的插件路径,然后允许我用我自己的替换覆盖默认的Date装饰器。

接下来,我们需要覆盖loadDefaultDecorators()方法来使用我们的newDate装饰器:

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

    // ...
}

最终输出是什么样的?让我们考虑以下元素:

$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'));

如果您随后回显此元素,您将获得以下标记(为了便于阅读,对空白进行了一些轻微修改):

<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>

结论

我们现在有一个元素可以呈现多个相关的表单输入字段,然后将聚合字段作为单个实体处理—dateOfBirth元素将作为数组传递给该元素,并且该元素然后,正如我们之前提到的,将创建适当的日期段并返回一个我们可以用于大多数后端的值。

此外,我们可以对元素使用不同的装饰器。如果我们想使用DojoDateTextBoxdijit装饰器——它接受并返回字符串值——我们可以,无需修改元素本身。

最后,您将获得一个统一的元素API,您可以使用它来描述表示复合值的元素。

本系列其他文章:

  • 最简单的Zend_Form装饰器
  • 由内而外:如何对装饰器进行分层
  • 单独呈现Zend_Form装饰器
未经允许不得转载:我爱分享网 » 创建复合元素

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏