Декораторы Zend_Form
|
Опубликовано: 31.10.2008
|
- От переводчика
- Вступление
- Проектирование Zend_Form
- Основные операции с декораторами
- Настройка вывода с использованием стандартных декораторов
- Стандартные декораторы
- Пример: Верстка таблицы
- Пример: Полный контроль отображения формы с помощью декоратора ViewScript
- Создание собственного декоратора
- Анатомия Декоратора.
- Определение местонахождения собственных декораторов для объектов формы или элемента
- Формы
- Элементы
- Пример: Сгруппированные Чекбоксы
- Другие способы настройки декораторов
- Помощники вида (View Helpers)
- Заключение
Данный текст - перевод статьи , автором которой является . Оригинал статьи был опубликован 5 мая 2008 года. Статья написана по Zend Framework версии 1.5, но сохраняет актуальность и для последующих релизов. Единственная модификация, которой подвергся предмет статьи - добавление в версии Zend Framework 1.6 стандартного декоратора Captcha.
Многие говорят о компоненте Zend_Form как об отличном дополнении к Zend Framework и гибком решении "проблемы форм". Однако, несмотря на гибкость решения, для многих разработчиков оказывается не так просто разобраться с одной из сторон Zend_Form - декораторами. Цель этой статьи - пролить немного света на декораторы, а так же показать некоторые способы создания ваших собственных декораторов и их комбинирования для реализации различных подходов к генерации форм.
Когда компонент Zend_Form находился на стадии проектирования, главной целью было предоставление возможности автоматической генерации верстки необходимой для отображения каждого элемента формы и формы целиком. Обоснованием к такому подходу было то, что вставка значений, атрибутов формы и её элементов, отображение ошибок, в случае неверного ввода данных, - это, чаще всего, скучная, однообразная работа. Какой бы ни была реализация компонента формы, она должна сделать эти действия тривиальными для программиста.
Конечно, главная проблема генерации разметки форм - отсутствие единого стандартного решения. Подходы варьируются от разработчика к разработчику и от проекта к проекту. Так что, решение должно быть достаточно гибким, чтобы охватить в себе как можно больше подходов.
Также одной из целей при планировании было обеспечение независимости Zend_Form от Zend_View. Использовать Zend_View или нет - это решение должно полностью лежать на плечах разработчика. И компонент Zend_Form должен быть достаточно гибким, чтобы такую возможность обеспечивать.
С такими ориентирами, решение нашлось само собой: использование . Декоратор традиционно используется, как способ расширить функциональность класса путем его "обвертывания". Это даёт разработчикам возможность добавлять или модифицировать существующее поведение класса, не меня при этом его API. Декораторы могут "оборачивать" собой другие декораторы, давая, таким образом, возможность создавать многоуровневые расширения. Сам декоратор используется вместо оригинального объекта, так как предоставляет тоже самое API. Он просто обеспечивает новую или дополнительную функциональность оригиналу.
Для примера, рассмотрим так называемый класс «Окна» (Window). Вы можете создать декоратор окна, который будет это окно отображать (WindowDecorator) и дополнительные декораторы для отображения скроллбаров, заголовка окна и так далее. Каждый декоратор отвечает за единственный аспект финального отображения (декоратор скроллбар - добавляет скроллы, декоратор заголовка, соответственно, заголовок). Например, создание объекта может выглядеть вот таким образом:
$window = new WindowScrollbarDecorator(
new WindowTitleDecorator(
new WindowDecorator(
new Window()
)
)
);
И, для вывода итогового «Окна»:
$window->render();
Эта строчка вызывает метод WindowScrollbarDecorator::render(), который, в свою очередь, вызывает метод WindowTitleDecorator::render(), который, затем, вызывает WindowDecorator::render(), который, в заключение, запускает Window::render(). В результате мы получим окно с заголовком и со скроллбарами.
Декораторы оказались идеальным решением для Zend_Form, так как таким образом разработчики могут сами выбирать, что они хотят видеть в конечной верстке формы. Финальная реализация основывается на модифицированном паттерне декоратор - вместо объекта, оборачивается (декорируется) генерируемая верстка, и для этого используются мета данные объекта формы или элемента формы.
Используя декораторы, вы имеете возможность для формы или любого элемента формы добавить какую-нибудь новую верстку до, после или вместо элемента/формы (замена контента также может подразумевать помещение старого контента внутрь нового). Изначально контент - это пустая строка, и, каждый декоратор работает с контентом, который вернул предыдущий декоратор.
Давайте посмотрим как работают декораторы "по умолчанию" для большинства элементов формы. По умолчанию используются следующие декораторы: ViewHelper, Errors, HtmlTag, и, наконец, Label. ViewHelper декоратор по умолчанию заменяет любой переданный контент (например формирует верстку отдельно взятого input'a на основе установленных атрибутов); декоратор ошибок, Errors добавляет контент после каждого элемента (выводит ошибки, если значения формы не прошли проверку после введения); HtmlTag оборачивает контент (по умолчанию он каждый элемент формы "заворачивает" в теги <dd></dd>) и декоратор Label добавляет контент (тег label) перед элементом формы. (Во всех случаях, исключая ViewHelper, подстановка контента (до или после элемента формы) или обертка - может быть изменено по желанию.) Итоговое отображение будет вызвано примерно вот таким образом:
$label->render($htmlTag->render($errors->render($viewHelper->render(''))))
Шаг за шагом, давайте посмотрим как же генерируется верстка:
-
'' -
<input name="foo" id="foo" type="text" value="" />
-
<input name="foo" id="foo" type="text" value="" />
<div class="error"><ul>
<li>...</li>
</ul></div> -
<dd>
<input name="foo" id="foo" type="text" value="" />
<div class="error"><ul>
<li>...</li>
</ul></div>
</dd>
-
<dt><label for="foo" class="optional">Foo</label><dt>
<dd>
<input name="foo" id="foo" type="text" value="" />
<div class="error"><ul>
<li>...</li>
</ul></div>
</dd>
Как вы можете видеть, каждый декоратор делает только одну вещь, а финальный результат это агрегация верстки полученной от каждого декоратора.
Чтобы начать разбираться с настройкой верстки с помощью декораторов, вы должны узнать один из основных моментов: каждый стандартный, зарегистрированный по умолчанию декоратор имеется для каждого типа объекта. Стандартные декораторы в том порядке, в котором они зарегистрированы, для большинства элементов, это:
- ViewHelper
- Errors
- HtmlTag(<dd>)
- Label (с оборачиванием в тег <dt>)
Для объекта формы стандартные декораторы это:
- FormElements (пробегает по списку всех элементов формы, выводит группы, вложенные формы, отвечает за вывод каждого)
- HtmlTag(<dl>)
- Form
Отображение групп элементов и вложенных форм по умолчанию обеспечивается следующими декораторами:
- FormElements
- Fieldset
- DtDdWrapper (оборачивает набор полей (fieldset) в <dd> и добавляет перед ним пустой тег <dt></dt>)
Один из простых способов настроить отображение формы - это добавление либо модификация свойства декораторов. Например, если вы решите, что ваша текстовая метка к элементу формы (Label) должна следовать после элемента, а не до него, вы можете изменить свойство подстановки (placement):
$label = $element->getDecorator('label');
$label->setOption('placement', 'append'); // append – после, prepend (по умолчанию) - до
Существуют также множество других свойств (опций), доступных для большинства декораторов; вам следует изучить мануал, и/или документацию по API, если вы хотите разобрать этот момент более детально.
Другой не сложный способ конфигурации вывода верстки - удаление декоратора. Если вы не хотите чтобы текстовая метка (Label) к элементу формы вообще показывалась – просто удалите соответствующий декоратор:
$element->removeDecorator('label');
К сожалению, на данный момент нет методов для добавления декораторов в определенное место в стеке декораторов, так что если вам нужно, например, поместить новый декоратор в середину стека (имеется в виду каким-то образом поменять очередность вызова декораторов), лучший способ сделать это - сбросить стек (переопределить его заново). Например, если вы хотите добавить Описание к элементу (например текст с детальным описанием назначения поля для ввода текста) вы можете сделать следующее:
$element->setDescription($desc);
$element->setDecorators(array(
'ViewHelper',
'Description',
'Errors',
array('HtmlTag', array('tag' => 'dd')),
array('Label', array('tag' => 'dt')),
));
Несмотря на то, что существуют методы addDecorator() и addDecorators(), в большинстве случаев вам придется использовать именно setDecorators(), за исключением случаев, где вы изначально определяете стек с очень маленьким количеством декораторов.
Во время добавления декоратора вы можете назначить для него «алиас». Это позволит в будущем получать этот декоратор из стека, используя этот алиас. В первую очередь алиасы удобно использовать, если вам надо добавить два или более декораторов одного и того же типа; на самом деле, в таком случае, если вы не определите алиас, последний зарегистрированный декоратор перезапишет все другие объекты декораторов такого же типа! Чтобы установить алиас для декоратора вы передаете в качестве типа декоратора массив с единственной парой ключ/значение, где ключ - это алиас декоратора, а значение - тип декоратора. Например, если вам надо использовать два или более различных HtmlTag декораторов в вашем стеке, вы можете сделать что-то вроде этого:
$element->setDecorators(array(
'ViewHelper',
'Description',
'Errors',
array(array('elementDiv' => 'HtmlTag'), array('tag' => 'div')),
array(array('td' => 'HtmlTag'), array('tag' => 'td')),
array('Label', array('tag' => 'td')),
));
В вышеуказанном примере, элемент формы помещается в HTML тег div, после чего помещается в клетку таблицы (с помощью помещения в теги <td>...</td>). Эти два декоратора имеют алиасы 'elementDiv' и 'td', соответственно.
Теперь, когда мы знаем, как манипулировать стеком декораторов и отдельно взятыми объектами декораторов, мы рассмотрим список доступных стандартных декораторов:
- Callback: запускает определенный PHP код, который возвращает контент
- Description: выводит описание элемента из соответствующего свойства.
- DtDdWrapper: помещает элемент в теги <dd>...</dd> и добавляет пустой тег <dt></dt> перед элементом.
- Errors: выводит не упорядоченный список ошибок для элемента, если они есть.
- Fieldset: выводит контент как набор полей (html тег <fieldset>), используя для легенды (тег <legend>) свойство элемента legend, если оно определено.
- FormElements: перебирает все элементы формы, вложенной формы или группы элементов выводя каждый (элемент также может быть в свою очередь элементом формы, вложенной формы или группы элементов)
- Form: оборачивает контент в тег <form>, используя метаданные объекта как атрибуты
- HtmlTag: помещает элемент в указанный HTML тег, или добавляет тег перед или после элемента (также может быть использован для добавления только открывающего или закрывающего тега)
- Image: оформляет элемент как изображение ( тег <input type="image" ... /> )
- Label: выводит текстовую метку для элемента (по умолчанию она помещается перед элементом)
- ViewHelper: отображает элемент, используя для этого помощник вида (view helper). Помощник вида определяется свойством элемента 'helper', если оно определено, но также может быть задан явно, путем передачи опции 'helpler' в декоратор. По умолчанию заменяет контент элемента на возвращенный хелпером.
- ViewScript: для оформления элемента используется определенный скрипт вида.
Каждый из этих декораторов добавляет контент предусмотренный по умолчанию, если не определено другого поведения.
Одна из часто появляющихся в разработке веб-приложений задач - это вывод формы как таблицы HTML. Как же это может быть реализовано с помощью декораторов?
В демонстрационных целях, для нашего примера допустим, что в нашей таблице отводится только одна строка на один элемент формы. Стандартные элементы будут использовать две колонки в строке, первая - для текстовой метки и вторая - для самого элемента (поля ввода текста, например) и любых возникающих ошибок; кнопки формы будут отображаться во второй колонке без текстовой метки.
Для стандартных элементов, вы должны установить декораторы вот таким образом:
$element->setDecorators(array(
'ViewHelper',
'Errors',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array('Label', array('tag' => 'td')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));
Для кнопок и изображений, мы используем следующий код:
$element->setDecorators(array(
'ViewHelper',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array(array('label' => 'HtmlTag'), array('tag' => 'td', 'placement' => 'prepend')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));
Сама форма будет настраиваться вот таким образом:
$form->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'table')),
'Form',
));
После этого может быть сгенерирована следующая верстка:
<form enctype="application/x-www-form-urlencoded" action="" method="post">
<table>
<tr>
<td><label for="username" class="optional">Логин:</label></td>
<td class="element">
<input type="text" name="username" id="username" value="">
</td>
</tr>
<tr>
<td><label for="firstname" class="optional">Имя:</label></td>
<td class="element">
<input type="text" name="firstname" id="firstname" value="">
</td>
</tr>
<tr>
<td><label for="lastname" class="optional">Фамилия:</label></td>
<td class="element">
<input type="text" name="lastname" id="lastname" value="">
</td>
</tr>
<tr>
<td></td>
<td class="element">
<input type="submit" name="save" id="save" value="Save">
</td>
</tr>
</table>
</form>
Заметка: этот пример не приспособлен для вывода групп элементов и вложенных форм, но он должен давать достаточно пищи для ума, чтобы вы смогли сами выяснить, как это сделать.
Вы можете справедливо заметить, что устанавливать декораторы для всех элементов формы вручную не так чтобы уж очень удобно. И вам не придется так поступать, так как есть различные способы этот процесс упростить.
К примеру, вы можете использовать метод объекта формы setElementDecorator(). Он установит все декораторы для всех зарегистрированных на данный момент элементов формы (не для элементов зарегистрированных после вызова метода):
$form->setElementDecorators(array(
'ViewHelper',
'Errors',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array('Label', array('tag' => 'td')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));
Вы можете сделать это один раз для всех обычных элементов и потом перебирать элементы формы в поисках кнопок и изображений, для установки их собственных декораторов.
Другой легкий способ - расширить класс формы и определить в нем массивы с декораторами, которые применяются к нужным элементам формы:
class My_Form_Registration extends Zend_Form
{
public $elementDecorators = array(
'ViewHelper',
'Errors',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array('Label', array('tag' => 'td')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
);
public $buttonDecorators = array(
'ViewHelper',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array(array('label' => 'HtmlTag'), array('tag' => 'td', 'placement' => 'prepend')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
);
public function init()
{
$this->addElement('text', 'username', array(
'decorators' => $this->elementDecorators,
'label => 'Username:',
);
$this->addElement('text', 'firstname', array(
'decorators' => $this->elementDecorators,
'label => 'First Name:',
);
$this->addElement('text', 'lastname', array(
'decorators' => $this->elementDecorators,
'label => 'Last Name:',
);
$this->addElement('submit', 'save', array(
'decorators' => $this->buttonDecorators,
'label => 'Save',
);
}
public function loadDefaultDecorators()
{
$this->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'table')),
'Form',
));
}
}
Вышеуказанный метод в особенности хорош, так как предотвращает установку декораторов по умолчанию для формы, загружая собственные декораторы во время создания объекта. Также преимуществом является возможность модификации декораторов в любом месте. Если же вы решите сделать свойства elementDecorators и buttonDecorators статическими, вы даже сможете использовать эти декораторы для форм до создания объекта формы, допуская многократное использование с разными стеками декораторов.
Наконец, вы также можете создать ваш собственный элемент формы, расширив стандартный класс, перегрузив метод loadDefaultDecorators(). Это позволяет вам иметь различные элементы (используемые в зависимости от того, какую верстку вы собираетесь генерировать).
А как же быть в случае, когда вам необходима совершенно неструктурированная верстка формы, никак не зависящая от типа элементов? В таком случае декораторы могут быть полезны для отдельно взятых элементов, однако для формы в целом они не имеют особого смысла. Представляю вам ViewScript декоратор.
Этот декоратор оформляет форму, используя указанный скрипт вида, как помощник вида partial, передавая объект формы как переменную вида $form. Это позволяет вам выбирать необходимые метаданные элементов и/или сами элементы и выводить их напрямую в скрипте вида. Также в случае использования этого декоратора вы можете избежать потребности в группах элементов, так как вы можете делать их вручную в скрипте вида.
В качестве примера, рассмотрите следующий скрипт вида:
<h4>Пожалуйста, зарегистрируйтесь!</h4>
<form action="<?= $this->escape($this->form->getAction() ?>"
method="<?= $this->escape($this->form->getMethod() ?>">
<fieldset>
<legend>Демографические данные</legend>
<p>
Пожалуйста, предоставьте нам указанную информацию, чтобы мы могли узнать
больше о вас:
</p>
<?= $this->form->age ?>
<?= $this->form->nationality ?>
<?= $this->form->income ?>
</fieldset>
<fieldset>
<legend>Пользовательская информация</legend>
<p>
Теперь, расскажите, пожалуйста, кто вы и как с вами связаться.
</p>
<?= $this->form->firstname ?>
<?= $this->form->lastname ?>
<?= $this->form->email ?>
<?= $this->form->address1 ?>
<?= $this->form->address2 ?>
<?= $this->form->city ?>
<?= $this->form->state ?>
<?= $this->form->postal ?>
<?= $this->form->phone ?>
</fieldset>
<?= $this->form->submit ?>
</form>
Если продемонстрированная выше форма будет в скрипте вида "demogForm.phtml", вы можете использовать её для вашего объекта формы следующим образом:
$form->setDecorators(array(
array('ViewScript', array('viewScript' => 'demogForm.phtml'))
));
Как вы можете видеть, такой метод отображения формы предоставляет очень гибкую настройку вывода, позволяя модифицировать верстку формы без ограничений, при этом вы также можете использовать преимущества вывода ошибок, текстовых меток для элементов и других декораторов (путем декорирования конкретных элементов).
Не сомневайтесь: обязательно наступит момент, когда функционала стандартных декораторов станет недостаточно. У вас может быть слишком сложная верстка, чтобы сгенерировать её, используя лишь набор стандартных декораторов, вы можете захотеть уменьшить количество вызовов функций, чтобы оптимизировать ваше приложение, вы можете захотеть скомбинировать несколько различных HTML элементов в один Zend_Form элемент и так далее. В этот момент вам следует начать рассматривать возможность создания собственного декоратора.
Все декораторы реализуют интерфейс Zend_Form_Decorator_Interface, который в упрощенном виде выглядит следующим образом:
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 setOption($key, $value);
public function getOption($key);
public function getOptions();
public function removeOption($key);
public function clearOptions();
public function render($content);
}
Для вашего удобства все эти методы присутствуют в Zend_Form_Decorator_Abstract, так что все, что вам нужно сделать для вашего декоратора это реализовать метод render():
class My_Form_Decorator_Foo extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
// ...
return $content
}
}
Декоратор хранит текущий объект (который декорируется) как "element", однако, элемент не обязательно должен быть объектом Zend_Form_Element, но также может являться формой, группой элементов или вложенной формой. В результате вы можете создавать декораторы, используемые с любым из этих типов объектов и их метаданными. Получение текущего элемента, возможно с помощью getElement().
Если в вашем декораторе необходим доступ к объекту вида View, вы можете получить его из текущего элемента:
$view = $this->getElement()->getView();
Если возвращенное значение - null, значит объект вида не был определен в этом элементе.
Ещё два свойства, которые также всегда установлены: separator (разделитель) и placement (позиция подстановки). Вашему методу render() следует использовать эти свойства во время возвращения контента. Если ваш декоратор расширен от Zend_Form_Decorator_Abstract вы можете получать эти свойства с помощью getSeparator() и getPlacement(); иначе проверяйте их наличие с помощью getOption(), так как они обычно передаются как опции. Например:
public function render($content)
{
// ...
$separator = $this->getSeparator();
$placement = $this->getPlacement();
switch ($placment) {
case 'APPEND':
// добавляем новый контент после старого, используя разделитель
return $content . $separator . $newContent;
case 'PREPEND':
// добавляем новый контент перед старым, используя разделитель
return $newContent . $separator . $content;
default:
// заменяем старый контент на новый
return $newContent;
}
}
Теперь, когда у вас есть все средства для создания собственных декораторов, давайте скажем объекту формы, как он может найти новые декораторы.
Создание собственных декораторов это конечно здорово, но пока вы не сообщите вашей форме или вашим элементам где искать их, они бесполезны. Вы обязательно должны обозначить для ваших объектов, где они могут искать ваши собственные декораторы.
Формам вы можете сообщить где искать ваши декораторы с помощью addPrefixPath():
// Использование
$form->addPrefixPath($prefix, $path, 'decorator');
// Пример использования
$form->addPrefixPath('My_Form_Decorator', 'My/Form/Decorator/', 'decorator');
Второй аргумент, $path определяет путь до классов, содержащих указанный префикс. Если этот путь у вас в include_path, тогда вы можете использовать относительный путь; если нет - используйте полный путь, чтобы быть уверенным в том, что загрузчик плагинов сможет найти ваши классы. Третий аргумент это тип классов для загрузчика плагина; в нашем случае нам нужно искать только декораторы, поэтому мы используем строку 'decorator'; однако, вы также можете использовать addPrefixPath() чтобы установить пути для других типов классов в загрузчике плагинов, таких как: элементы, валидаторы и фильтры. Вы также можете устанавливать пути к декораторам для разнообразных групповых объектов Zend_Form, таких как группы элементов, вложенные формы и элементы. Это может быть сделано с помощью одного из следующих методов:
-
addElementPrefixPath($path, $prefix, 'decorator')
-
addDisplayGroupPrefixPath($path, $prefix)
В любом случае, настройка будет применена к любым элементам или группам элементов, которые на данный момент существуют в форме и к тем которые могут быть добавлены в форму позже. Вы можете также передать опцию elementPrefixPath во время конфигурации формы; значение будет использовано для всех элементов созданных в форме. Пара примеров:
// Программно:
$form->addElementPrefixPath('My_Form_Decorator', 'My/Form/Decorator', 'decorator');
$form->addDisplayGroupPrefixPath('My_Form_Decorator', 'My/Form/Decorator');
// во время создания объекта формы
$form = new Zend_Form(array(
'elementPrefixPath' => array(
array(
'prefix' => 'My_Form_Decorator',
'path' => 'My/Form/Decorator/',
'type' => 'decorator'
),
),
));
// Или используя конфигурационный файл INI
form.elementPrefixPath.my.prefix = "My_Form_Decorator"
form.elementPrefixPath.my.path = "My/Form/Decorator"
form.elementPrefixPath.my.type = "decorator"
// Или используя конфигурационный файл XML
<form>
<elementPrefixPath>
<my>
<prefix>My_Form_Decorator</prefix>
<path>My/Form/Decorator</path>
<type>decorator</type>
</my>
</elementPrefixPath>
</form>
Чтобы установить путь для загрузчика плагинов для отдельного элемента вы можете вызвать метод addPrefixPath($prefix, $path, 'decorator') на самом элементе, или передать значение prefixPath во время создания объекта. В основном это может быть использовано, если вы знаете что единичный элементы или единичная группа элементов использует собственный декоратор, либо если вам необходимо чтобы некоторые элементы использовали стандартные декораторы, а некоторые - перегруженные.
Варианты использования почти такие же, как и в случае с Zend_Form
// Использование:
$element->addPrefixPath($prefix, $path, 'decorator');
// Пример
$element->addPrefixPath('My_Form_Decorator', 'My/Form/Decorator', 'decorator');
// Конфигурация во время создания
$element = new Zend_Form_Element('foo', array(
'prefixPath' => array(
array(
'type' => 'decorator',
'path' => 'My/Form/Decorator/',
'prefix' => 'My_Form_Decorator'
),
),
));
// или создание используя объект формы
$form->addElement('text', 'foo', array(
'prefixPath' => array(
array(
'type' => 'decorator',
'path' => 'My/Form/Decorator/',
'prefix' => 'My_Form_Decorator'
),
),
));
// Используя конфигурационный файл INI
form.foo.options.prefixPath.my.type = "decorator"
form.foo.options.prefixPath.my.path = "My/Form/Decorator/"
form.foo.options.prefixPath.my.prefix = "My_Form_Decorator"
// Используя конфигурационный файл XML
<form>
<element>
<options>
<prefixPath>
<my>
<type>decorator</type>
<path>My/Form/Decorator/</path>
<prefix>My_Form_Decorator</prefix>
</my>
</prefixPath>
</options>
</element>
</form>
Теперь вы знаете, как декораторы работают, как написать собственный декоратор и как сообщить вашим объектам, где расположены ваши декораторы. Давайте попробуем применить эти знания на практике.
В качестве практической задачи мы рассмотрим создание набора сгруппированных по категориям чекбоксов. Чекбоксы будут представлены в виде двухуровневого массива, в котором первый уровень будет определять категорию, а второй будет парами значений/текстовых меток. Мы сгруппируем каждую категорию чекбоксов в набор полей (<fieldset></fieldset>), где название категории будет использоваться как легенда (<legend></legend>) и возле каждого чекбокса будет выведена текстовая метка.
Ещё одна вещь, которую мы реализуем - это перевод текстовых меток для каждого чекбокса на необходимый нам язык.
Как хорошие разработчики, мы назовем наш декоратор именем, которое говорит само за себя, например 'CategorizedCheckbox', и используем наш собственный префикс класса 'My_Form_Decorator'.
Чтобы начать нам понадобится зарегистрированный элемент, унаследованный от Zend_Form_Element_Multi; таким образом, мы будем точно знать, что getValue() вернёт массив. Также, мы будем использовать помощники вида Zend_View, так что мы должны удостовериться, что объект вида зарегистрирован в элементе. Наконец, мы должны получить объект трансляции (для перевода текстовых меток) для дальнейшего использования.
class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
$element = $this->getElement();
if (!$element instanceof Zend_Form_Element_Multi) {
return $content;
}
if (null === ($view = $element->getView())) {
return $content;
}
$translator = $element->getTranslator();
}
}
Теперь, когда мы с этим разобрались, нам предстоит немного поработать. В первую очередь, мы инициализируем контент, который будем создавать. Потом мы берем наши текущие значения, чтобы иметь возможность проверить был ли какой-то отдельно взятый чекбокс отмечен. Также нам нужно получить основное имя элемента, чтобы мы смогли использовать его для именования отдельных чекбоксов и для создания идентификаторов наших чекбоксов. Потом мы можем начать перебирать наши значения:
class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
// ...
$html = '';
$values = (array) $element->getValue();
$baseName = $element->getName();
foreach ($element->getMultiOptions() as $category => $values) {
}
}
}
Теперь мы подошли к самой веселой части: генерации верстки. Так как контент помещается в fieldset, то вначале мы сгенерируем верстку списка чекбоксов, а потом поместим её в fieldset. Мы можем сделать это, используя помощники вида. Также здесь мы переведем наши текстовые метки. Чекбоксам нужны имена и идентификаторы, так что сгенерируем и их, перед передачей в помощник вида formCheckbox(). Когда все Чекбоксы категории готовы, мы оборачиваем их в <fieldset> используя переведенное на нужный язык имя категории как <legend>.
class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
// ...
foreach ($element->getMultiOptions() as $category => $values) {
$boxes = '<ul class="checkboxes">';
foreach ($values as $value => $label) {
if ($translator instanceof Zend_Translate) {
$label = $translator->translate($label);
}
$boxName = $baseName . '[' . $value . ']';
$boxId = $basename . '-' . $value;
$attribs = array(
'id' => $boxId,
'checked' => in_array($value, $values),
);
$boxes .= '<li>'
. $view->formCheckbox($boxName, $value, $attribs)
. $view->formLabel($boxName, $label)
. '</li>';
}
$boxes .= '</ul>';
$legend = ($translator instanceof Zend_Translate)
? $translator->translate($category)
: ucfirst($category);
$attribs = array('legend' => $legend);
$html .= $view->fieldset($category, $boxes, $attribs);
}
}
}
Всё, что нам остаётся - это вернуть сгенерированную верстку. При выводе учтем значение опции подстановки.
class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
// ...
$placement = $this->getPlacement();
$separator = $this->getSeparator();
switch ($placement) {
case 'APPEND':
return $content . $separator . $html;
case 'PREPEND':
return $html . $separator . $content;
case null:
default:
return $html;
}
}
}
Вот и всё! Теперь мы можем использовать наш декоратор с элементами формы.
Большинство декораторов имеют много различных опций - вам следует изучить , чтобы определить, какие из них могут вам пригодиться. Многие из них позволяют вам настраивать генерируемую верстку, так что манипулирование значениями этих свойств – это простой способ управлять генерацией верстки.
Вам доступно несколько методов для установки свойств декораторов:
- setOption($key, $value) устанавливает значение одной опции за раз
- setOptions(array $options) устанавливает значение нескольких опций за раз
- Также вы можете передать массив опций или объект конфигурации Zend_Config когда добавляете декоратор к элементу формы:
// Передача опции 'tag' и 'class' во время конструкции декоратора
$element->addDecorator('HtmlTag', array('tag' => 'div', 'class' => 'foo'));
Другой способ модифицировать ваши декораторы и, в особенности, ViewHelper декоратор - это использование перегруженных помощников вида. Переопределение помощников вида вашими собственными были описаны в .
Декораторы - это совсем несложные классы, обеспечивающие, тем не менее, серьезный функционал: генерацию разнообразной верстки на основе ваших элементов. Автор надеется, что с информацией, полученной из этой статьи, вы сможете успешно комбинировать разные декораторы или писать свои собственные и получать, таким образом, необходимую в вашем приложении или веб-сайте разметку форм.
- Спрашивать почему у вас не работает код, для этого есть тема форума закрепленная за статьей.
- Спрашивать как реализовать ту или иную функциональность, для этого необходимо создать свою тему на форуме.
Комментарии для того чтобы: высказать свое аргументированное мнение о статье, указать какие участки вызывают непонимание, что нужно исправить/улучшить, просто сказать спасибо.
Комментарии имеют древовидную структуру.
Если вы хотите ответить на определенный комментарий - нажмите на ссылку "Ответить" возле этого комментария.

