Zend_Framework 装饰器将 Label 和 ViewHelper 包装在 div 内

发布于 2024-12-06 12:51:03 字数 1441 浏览 3 评论 0原文

我对 zend 装饰混乱很陌生,但我有两个重要的问题我无法解决。问题一后面是一些示例,

$decorate = array(
    array('ViewHelper'),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);

...

$name = new Zend_Form_Element_Text('title');
$name->setLabel('Title')
    ->setDescription("No --- way");

$name->setDecorator($decorate);

该示例输出

<li class="element">
    <label for="title" class="required">Title</label> 
    <input type="text" name="title" id="title" value="">
    <p class="hint">No --- way</p>
    <ul class="error">
        <li>Value is required and can't be empty</li>
    </ul>
</li>

问题#1

如何将 labelinput 包裹在 div 标签周围?所以输出如下:

<li class="element">
    <div>
        <label for="title" class="required">Title</label> 
        <input type="text" name="title" id="title" value="">
    </div>
    <p class="hint">No --- way</p>
    <ul class="error">
        <li>Value is required and can't be empty</li>
    </ul>
</li>

问题 #2

$decorate 数组中 element 的顺序是怎样的? 他们毫无意义!

I am new to this, zend decoration malarchy, but i have two significant questions that i cant get my head around. Question one is followed by some example

$decorate = array(
    array('ViewHelper'),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);

...

$name = new Zend_Form_Element_Text('title');
$name->setLabel('Title')
    ->setDescription("No --- way");

$name->setDecorator($decorate);

Which outputs

<li class="element">
    <label for="title" class="required">Title</label> 
    <input type="text" name="title" id="title" value="">
    <p class="hint">No --- way</p>
    <ul class="error">
        <li>Value is required and can't be empty</li>
    </ul>
</li>

The Question #1

How do i wrap the label and the input around a div tag? So the output is as follows:

<li class="element">
    <div>
        <label for="title" class="required">Title</label> 
        <input type="text" name="title" id="title" value="">
    </div>
    <p class="hint">No --- way</p>
    <ul class="error">
        <li>Value is required and can't be empty</li>
    </ul>
</li>

The Question #2

What is up with the order of the elements in the $decorate array? They MAKE NO SENSE!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

单调的奢华 2024-12-13 12:51:03

装饰器模式是一种设计模式,用于向现有类添加功能而不更改现有类。相反,装饰器类将自身包装在另一个类周围,并且通常公开与被装饰类相同的接口。

基本示例:

interface Renderable
{
    public function render();
}

class HelloWorld
    implements Renderable
{
    public function render()
    {
        return 'Hello world!';
    }
}

class BoldDecorator
    implements Renderable
{
    protected $_decoratee;

    public function __construct( Renderable $decoratee )
    {
        $this->_decoratee = $decoratee;
    }

    public function render()
    {
        return '<b>' . $this->_decoratee->render() . '</b>';
    }
}

// wrapping (decorating) HelloWorld in a BoldDecorator
$decorator = new BoldDecorator( new HelloWorld() );
echo $decorator->render();

// will output
<b>Hello world!</b>

现在,您可能会想,因为 Zend_Form_Decorator_* 类是装饰器,并且有一个 render 方法,所以这自动意味着被装饰类的输出' render 方法将始终由装饰器用附加内容包装。但是,通过检查上面的基本示例,我们可以很容易地看出,当然,情况不一定一定如此,正如这个附加(尽管相当无用)示例所示:

class DivDecorator
    implements Renderable
{
    const PREPEND = 'prepend';
    const APPEND  = 'append';
    const WRAP    = 'wrap';

    protected $_placement;

    protected $_decoratee;

    public function __construct( Renderable $decoratee, $placement = self::WRAP )
    {
        $this->_decoratee = $decoratee;
        $this->_placement = $placement;
    }

    public function render()
    {
        $content = $this->_decoratee->render();
        switch( $this->_placement )
        {
            case self::PREPEND:
                $content = '<div></div>' . $content;
                break;
            case self::APPEND:
                $content = $content . '<div></div>';
                break;
            case self::WRAP:
            default:
                $content = '<div>' . $content . '</div>';
        }

        return $content;
    }
}

// wrapping (decorating) HelloWorld in a BoldDecorator and a DivDecorator (with DivDecorator::APPEND)
$decorator = new DivDecorator( new BoldDecorator( new HelloWorld() ), DivDecorator::APPEND );
echo $decorator->render();

// will output
<b>Hello world!</b><div></div>

事实上,这基本上就是很多 < code>Zend_Form_Decorator_* 装饰器可以工作,如果它们具有这种放置功能是有意义的。

对于有意义的装饰器,您可以使用 setOption( 'placement', 'append' ) 来控制放置,例如,或者通过传递选项 'placement' =>;例如,'append' 到选项数组。

例如,对于 Zend_Form_Decorator_PrepareElements,此放置选项是无用的,因此会被忽略,因为它准备供 ViewScript 装饰器使用的表单元素,使其成为不使用 ViewScript 的装饰器之一。不要触及装饰元素的渲染内容。

根据各个装饰器的默认功能,装饰类的内容被包装、附加、前置、丢弃对装饰类进行完全不同的操作,而不直接向内容添加某些内容,然后将内容传递给下一个装饰器。考虑这个简单的例子:

class ErrorClassDecorator
    implements Renderable
{
    protected $_decoratee;

    public function __construct( Renderable $decoratee )
    {
        $this->_decoratee = $decoratee;
    }

    public function render()
    {
        // imagine the following two fictional methods
        if( $this->_decoratee->hasErrors() )
        {
            $this->_decoratee->setAttribute( 'class', 'errors' );
        }

        // we didn't touch the rendered content, we just set the css class to 'errors' above
        return $this->_decoratee->render();
    }
}

// wrapping (decorating) HelloWorld in a BoldDecorator and an ErrorClassDecorator
$decorator = new ErrorClassDecorator( new BoldDecorator( new HelloWorld() ) );
echo $decorator->render();

// might output something like
<b class="errors">Hello world!</b>

现在,当您为 Zend_Form_Element_* 元素设置装饰器时,它们将按照添加的顺序被包装并最终执行。因此,按照您的示例:

$decorate = array(
    array('ViewHelper'),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);

...基本上发生的情况如下(为简洁起见,实际的类名被截断):

$decorator = new HtmlTag( new Label( new Errors( new Description( new ViewHelper() ) ) ) );
echo $decorator->render();

因此,在检查示例输出时,我们应该能够提取各个装饰器的默认放置行为:

// ViewHelper->render()
<input type="text" name="title" id="title" value="">

// Description->render()
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p> // placement: append

// Errors->render()
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error"> // placement: append
    <li>Value is required and cant be empty</li>
</ul>

// Label->render()
<label for="title" class="required">Title</label> // placement: prepend
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error">
    <li>Value is required and cant be empty</li>
</ul>

// HtmlTag->render()
<li class="element"> // placement: wrap
    <label for="title" class="required">Title</label>
    <input type="text" name="title" id="title" value="">
    <p class="hint">No --- way</p>
    <ul class="error">
        <li>Value is required and cant be empty</li>
    </ul>
</li>

以及你知道吗;这实际上是所有相应装饰器的默认位置。

但现在最困难的部分来了,我们需要做什么才能得到您想要的结果?为了包装 labelinput 我们不能简单地这样做:

$decorate = array(
    array('ViewHelper'),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array('HtmlTag', array('tag' => 'div')), // default placement: wrap
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);

...因为这将包装所有前面的内容(ViewHelperDescriptionErrorsLabel)与 div,对吗?甚至不...添加的装饰器将被下一个装饰器替换,因为如果装饰器属于同一类,则装饰器将被后面的装饰器替换。相反,您必须给它一个唯一的键:

$decorate = array(
    array('ViewHelper'),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array(array('divWrapper' => 'HtmlTag'), array('tag' => 'div')), // we'll call it divWrapper
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);

现在,我们仍然面临 divWrapper 将包装所有前面的内容(ViewHelperDescription错误标签)。所以我们需要在这里发挥创意。有很多方法可以实现我们想要的。我将举一个例子,这可能是最简单的:

$decorate = array(
    array('ViewHelper'),
    array('Label', array('tag'=>'div', 'separator'=>' ')), // default placement: prepend
    array(array('divWrapper' => 'HtmlTag'), array('tag' => 'div')), // default placement: wrap
    array('Description'), // default placement: append
    array('Errors', array('class'=>'error')), // default placement: append
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')), // default placement: wrap
);

有关 Zend_Form 装饰器的更多说明,我建议阅读 Zend Framework 的首席开发人员 Matthew Weier O'Phinney 的 有关 Zend Form 装饰器的文章

The decorator pattern is a design pattern for adding functionality to existing classes without altering those existing classes. In stead, a decorator class wraps itself around another class, and generally exposes the same interface as the decorated class.

Basic example:

interface Renderable
{
    public function render();
}

class HelloWorld
    implements Renderable
{
    public function render()
    {
        return 'Hello world!';
    }
}

class BoldDecorator
    implements Renderable
{
    protected $_decoratee;

    public function __construct( Renderable $decoratee )
    {
        $this->_decoratee = $decoratee;
    }

    public function render()
    {
        return '<b>' . $this->_decoratee->render() . '</b>';
    }
}

// wrapping (decorating) HelloWorld in a BoldDecorator
$decorator = new BoldDecorator( new HelloWorld() );
echo $decorator->render();

// will output
<b>Hello world!</b>

Now, you might be tempted to think that because the Zend_Form_Decorator_* classes are decorators, and have a render method, this automatically means the output of the decorated class' render method will always be wrapped with additional content by the decorator. But on inspection of our basic example above, we can easily see this doesn't necessarily have to be the case at all of course, as illustrated by this additional (albeit fairly useless) example:

class DivDecorator
    implements Renderable
{
    const PREPEND = 'prepend';
    const APPEND  = 'append';
    const WRAP    = 'wrap';

    protected $_placement;

    protected $_decoratee;

    public function __construct( Renderable $decoratee, $placement = self::WRAP )
    {
        $this->_decoratee = $decoratee;
        $this->_placement = $placement;
    }

    public function render()
    {
        $content = $this->_decoratee->render();
        switch( $this->_placement )
        {
            case self::PREPEND:
                $content = '<div></div>' . $content;
                break;
            case self::APPEND:
                $content = $content . '<div></div>';
                break;
            case self::WRAP:
            default:
                $content = '<div>' . $content . '</div>';
        }

        return $content;
    }
}

// wrapping (decorating) HelloWorld in a BoldDecorator and a DivDecorator (with DivDecorator::APPEND)
$decorator = new DivDecorator( new BoldDecorator( new HelloWorld() ), DivDecorator::APPEND );
echo $decorator->render();

// will output
<b>Hello world!</b><div></div>

This is in fact basically how a lot of Zend_Form_Decorator_* decorators work, if it makes sense for them to have this placement functionality.

For decorators where it makes sense, you can control the placement with the setOption( 'placement', 'append' ) for instance, or by passing the option 'placement' => 'append' to the options array, for instance.

For Zend_Form_Decorator_PrepareElements, for instance, this placement option is useless and therefor ignored, as it prepares form elements to be used by a ViewScript decorator, making it one of the decorators that doesn't touch the rendered content of the decorated element.

Depending on the default functionality of the individual decorators, either the content of the decorated class is wrapped, appended, prepended, discarded or something completely different is done to the decorated class, without adding something directly to the content, before passing along the content to the next decorator. Consider this simple example:

class ErrorClassDecorator
    implements Renderable
{
    protected $_decoratee;

    public function __construct( Renderable $decoratee )
    {
        $this->_decoratee = $decoratee;
    }

    public function render()
    {
        // imagine the following two fictional methods
        if( $this->_decoratee->hasErrors() )
        {
            $this->_decoratee->setAttribute( 'class', 'errors' );
        }

        // we didn't touch the rendered content, we just set the css class to 'errors' above
        return $this->_decoratee->render();
    }
}

// wrapping (decorating) HelloWorld in a BoldDecorator and an ErrorClassDecorator
$decorator = new ErrorClassDecorator( new BoldDecorator( new HelloWorld() ) );
echo $decorator->render();

// might output something like
<b class="errors">Hello world!</b>

Now, when you set the decorators for a Zend_Form_Element_* element, they will be wrapped, and consequently executed, in the order in which they are added. So, going by your example:

$decorate = array(
    array('ViewHelper'),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);

... basically what happens is the following (actual class names truncated for brevity):

$decorator = new HtmlTag( new Label( new Errors( new Description( new ViewHelper() ) ) ) );
echo $decorator->render();

So, on examining your example output, we should be able to distill the default placement behaviour of the individual decorators:

// ViewHelper->render()
<input type="text" name="title" id="title" value="">

// Description->render()
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p> // placement: append

// Errors->render()
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error"> // placement: append
    <li>Value is required and cant be empty</li>
</ul>

// Label->render()
<label for="title" class="required">Title</label> // placement: prepend
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error">
    <li>Value is required and cant be empty</li>
</ul>

// HtmlTag->render()
<li class="element"> // placement: wrap
    <label for="title" class="required">Title</label>
    <input type="text" name="title" id="title" value="">
    <p class="hint">No --- way</p>
    <ul class="error">
        <li>Value is required and cant be empty</li>
    </ul>
</li>

And what do you know; this actually is the default placement of all respective decorators.

But now comes the hard part, what do we need to do to get the result you are looking for? In order to wrap the label and input we can't simply do this:

$decorate = array(
    array('ViewHelper'),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array('HtmlTag', array('tag' => 'div')), // default placement: wrap
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);

... as this will wrap all preceding content (ViewHelper, Description, Errors and Label) with a div, right? Not even... the added decorator will be replaced by the next one, as decorators are replaced by a following decorator if it is of the same class. In stead you would have to give it a unique key:

$decorate = array(
    array('ViewHelper'),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array(array('divWrapper' => 'HtmlTag'), array('tag' => 'div')), // we'll call it divWrapper
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);

Now, we're still faced with the problem that divWrapper will wrap all preceding content (ViewHelper, Description, Errors and Label). So we need to be creative here. There's numerous ways to achieve what we want. I'll give one example, that probably is the easiest:

$decorate = array(
    array('ViewHelper'),
    array('Label', array('tag'=>'div', 'separator'=>' ')), // default placement: prepend
    array(array('divWrapper' => 'HtmlTag'), array('tag' => 'div')), // default placement: wrap
    array('Description'), // default placement: append
    array('Errors', array('class'=>'error')), // default placement: append
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')), // default placement: wrap
);

For more explanation about Zend_Form decorators I'd recommend reading Zend Framework's lead developer Matthew Weier O'Phinney's article about Zend Form Decorators

温柔女人霸气范 2024-12-13 12:51:03

问题 #1

更改装饰器顺序并以这种方式添加 HtmlTag 帮助器:

$decorate = array(
    array('ViewHelper'),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array('HtmlTag', array('tag' => 'div')),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('HtmlTag', array('tag' => 'li', 'class'=>'element'))
);

问题 #2

装饰器是一条链,每个装饰器的输出都会传递到下一个装饰器的输入一,为了被它“装饰”。

默认情况下,它们附加内容(描述、错误)、前置内容(标签..)和/或包裹一些内容(HtmlTag)。但这些都是默认行为,您可以更改其中的大多数行为:

array('HtmlTag', array('tag' => 'span', placement=>'APPEND'));
//this would append <span></span> to the output of the previous decorator instead of wrapping it inside the <span>

让我们更仔细地了解链中发生的情况:

  1. ViewHelper 使用声明的默认 viewHelper 呈现您的表单元素在表单元素的类中。

  2. Label 将标签添加到先前的输出

  3. HtmlTag 包装

    around

  4. Description 周围元素描述

  5. Errors 附加错误消息(如果有)

  6. HtmlTag 将所有这些包装在

编辑

我在没有测试任何内容的情况下写了这个答案,因此可能会有一些不准确的地方。亲爱的读者,如果您看到一些,请发表评论,我会更新。

Question #1

Change the decorators order and add an HtmlTag helper this way :

$decorate = array(
    array('ViewHelper'),
    array('Label', array('tag'=>'div', 'separator'=>' ')),
    array('HtmlTag', array('tag' => 'div')),
    array('Description'),
    array('Errors', array('class'=>'error')),
    array('HtmlTag', array('tag' => 'li', 'class'=>'element'))
);

Question #2

Decorators are a chain, the output of each one is passed to the input of the next one, in order to be 'decorated' by it.

By default, they append content (description, errors), prepend content (label..) and or wrap something around (HtmlTag). But these are default behaviors, and you can change it for most of them :

array('HtmlTag', array('tag' => 'span', placement=>'APPEND'));
//this would append <span></span> to the output of the previous decorator instead of wrapping it inside the <span>

Let's have a more closer look to what happens in your chain :

  1. ViewHelper renders your form element using it's default viewHelper, declared in the form element's class.

  2. Label prepends the label to the previous output

  3. HtmlTag wraps a <div> around

  4. Description appends the elements description

  5. Errors appends error messages, if any

  6. HtmlTag wraps all this in an <li>

EDIT

I wrote this answer without testing anything, so there might be some little inaccuracies here and there. Dear reader, if you see some just drop a comment and i'll update.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文