Иногда очень полезно инкапсулировать в элемент формы какую-то кастомную логику, а так же его отображение и фильтрацию, для повторного ипользования.
На примере Bootstrap3 date time picker я приведу пример, как сделать элемент Element\DateTimePicker с версткой Bootstrap.
Для начала необходимо подключить расширение для bootstrap, которое добавит jquery метод $.datetimepicker() и возможность написания верстки под этот элемент.
Ссылки на проект:
http://eonasdan.github.io/bootstrap-datetimepicker/
https://github.com/Eonasdan/bootstrap-datetimepicker
Я подключил нужные js и css в layout шаблоне на весь модуль application. Код для примера:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//... $this->headLink() ->appendStylesheet($this->basePath() . '/css/bootstrap/bootstrap.min.css') ->appendStylesheet($this->basePath() . '/css/bootstrap/bootstrap-datetimepicker.min.css') //... $this->headScript() ->appendFile($this->basePath() . '/js/bootstrap/bootstrap.min.js') ->appendFile($this->basePath() . '/js/bootstrap/bootstrap-datetimepicker.min.js') |
Далее можно уже пользоваться этим модулем, просто дублируя каждый раз верстку в нужных местах. Сначала я попробовал пользоваться этим элементом так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<div class="panel-body"> <?php $form->setAttribute("class", "form-inline"); $form->prepare(); echo $this->form(null, \TwbBundle\Form\View\Helper\TwbBundleForm::LAYOUT_INLINE)->openTag($form); echo $this->formRow($form->get("pagenum")); ?> <?php echo $this->formLabel($form->get("dealDateFrom")); ?> <div class='input-group date datetimepicker' data-date-format='YYYY-MM-DD'> <?php echo $this->formElement($form->get("dealDateFrom")->setAttributes( [ "class" => "form-control input-group date", "size" => "10" ] )->setValue($session['dealDateFrom'])); ?> <span class="input-group-addon"><span class="glyphicon glyphicon-time"></span></span> </div> <?php echo $this->formLabel($form->get("dealDateTo")); ?> <div class='input-group date datetimepicker' data-date-format='YYYY-MM-DD'> <?php echo $this->formElement($form->get("dealDateTo")->setAttributes([ "class" => "form-control input-group date", "size" => "10" ])->setValue($session['dealDateTo'])); ?> <span class="input-group-addon"><span class="glyphicon glyphicon-time"></span></span> </div> <?php echo $this->formSubmit($form->get("submit")); echo $this->form()->closeTag(); ?> </div> |
Из приведенного выше кода видно, что я пользуюсь Zend модулем TwbBundle. Этот модуль рендерит все стандартные элементы формы сразу в bootstrap верстке.
Далее я делаю два элемента datetimepicker, при этом дублирую всю верстку два раза, а меняется только название и значение элемента. Естесственно так оставлять нельзя.
Так же в конце шаблона я подключаю js.
1 2 3 4 5 6 |
<?php $this->inlineScript() ->prependFile($this->basePath() . '/js/Application/CouponsController.js') ?> |
Внутри
1 2 3 4 5 |
$('.datetimepicker').datetimepicker({ pickTime: false }); |
В данном случае я отключаю выбор времени и формат делаю YYYY-MM-DD, так было нужно по бизнес логике. Но элемен оставляю datetimepicker в виду того, что возможно позже понадобится время и я его использую в других местах.
Учитывая дублирование похожей верстки для элемента надо вынести все это отдельно используя возможности Zend 2.
Для начала создадим класс элемента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace Model\Form\Element; use Zend\Form\Element; /** * Description of DateTimePicker * * @author seyfer */ class DateTimePicker extends Element { } |
Он будет пустой, но он нужен, т.к. работать с ним я буду в классе Form таким образом
1 2 3 4 5 |
$dealDateFrom = new DateTimePicker('dealDateFrom'); $dealDateFrom->setLabel("Дата Сделки от: "); $this->add($dealDateFrom); |
Внутри элемента можно было бы добавить фильтры и валидацию. Мне это было не нужно, но я приведу код для примера. Таким образом можно инкапсулировать в элемент логику валидации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php namespace MyModule\Form\Element; use Zend\Form\Element; use Zend\InputFilter\InputProviderInterface; use MyModule\InputFilter\Bar as BarValidator; class FooElement extends Element implements InputProviderInterface { protected $validator; public function getValidator() { if (null === $this->validator) { $this->validator = new BarValidator; } return $this->validator; } public function getInputSpecification() { return array( 'name' => $this->getName(), 'required' => true, 'validators' => array( $this->getValidator(), ), ); } } |
По умолчанию наш новый элемент будет рендерится как
1 2 3 |
<input type="text"> |
Нам же нужна кастомная bootstrap верстка. Для этого надо расширить стандартный \Form\View\Helper\FormElement и зарегестрировать свой вариант в сервисах. Так же нам надо создать свой View Helper, который будет наследоваться от \Form\View\Helper\AbstractHelper и являться по сути плагином. Связывается это все через конфиг.
1 2 3 4 5 6 7 8 9 10 11 |
public function getViewHelperConfig() { return array( 'invokables' => array( '\Zend\Form\View\Helper\FormElement' => \Model\Form\View\Helper\FormElement::class, 'FormDateTimePicker' => \Model\Form\View\Helper\FormDateTimePicker::class, ), ); } |
Для начала создаем расширение для хелпера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?php namespace Model\Form\View\Helper; use Model\Form\Element; use Zend\Form\View\Helper\FormElement as BaseFormElement; use Zend\Form\ElementInterface; /** * Description of FormElement * * @author seyfer */ class FormElement extends BaseFormElement { public function render(ElementInterface $element) { $renderer = $this->getView(); if (!method_exists($renderer, 'plugin')) { // Bail early if renderer is not pluggable return ''; } if ($element instanceof Element\DateTimePicker) { $helper = $renderer->plugin('FormDateTimePicker'); return $helper($element); } return parent::render($element); } } |
Как видно тут мы делаем проверку по элементу, и если элемент это наш кастомный элемент, то для него регистрируем плагин рендерер. Само определение плагина такое.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<?php namespace Model\Form\View\Helper; use Zend\Form\ElementInterface; use Zend\Form\View\Helper\AbstractHelper; /** * Description of FormDateTimePicker * * @author seyfer */ class FormDateTimePicker extends AbstractHelper { protected $template = 'model/form-element/datetimepicker'; public function __invoke(ElementInterface $element) { $rendered = $this->renderTpl($element); // $rendered = $this->renderInCode($element); return $rendered; } private function renderTpl(ElementInterface $element) { return $this->getView()->render($this->template, array( 'element' => $element )); } private function renderInCode(ElementInterface $element) { $rendered = ''; $rendered.= "<div class='form-group'>"; $rendered.= "<label>" . $element->getLabel() . "</label>"; $rendered.= "<div class='input-group date datetimepicker' data-date-format='YYYY-MM-DD'>"; $rendered.= "<input type='text' name='" . $element->getName() . "' class='form-control input-group date' size='10' value='" . $element->getValue() . "'>"; $rendered.= "<span class='input-group-addon'><span class='glyphicon glyphicon-time'></span></span>"; $rendered.= "</div>"; $rendered.= "</div>"; return $rendered; } } |
Как видно я реализовал рендеринг двумя способами. Сначала я написал HTML прямо внутри класса, чтобы проверить, что оно работает. Но данное решение выглядит не красиво, лучше вынести верстку в шаблон, что и сделано в методе renderTpl(). Сам шаблон находится по указанному пути и вот его содержимое.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<div class='form-group'> <label><?php echo $element->getLabel() ?></label> <div class='input-group date datetimepicker' data-date-format='YYYY-MM-DD'> <input type='text' name='<?php echo $element->getName() ?>' class='form-control input-group date' size='10' value='<?php echo $element->getValue() ?>'> <span class='input-group-addon'><span class='glyphicon glyphicon-time'></span></span> </div> </div> |
Видим знакомую верстку, только инкапсулированную в элемент и рендерер. Теперь, после задания элемента для формы в месте, где рендерится форма все стало гораздо проще.
В форме
1 2 3 4 5 6 7 8 9 |
$dealDateFrom = new DateTimePicker('dealDateFrom'); $dealDateFrom->setLabel("Дата Сделки от: "); $this->add($dealDateFrom); $dealDateTo = new DateTimePicker('dealDateTo'); $dealDateTo->setLabel("Дата Сделки до: "); $this->add($dealDateTo); |
В шаблоне
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<div class="panel-body"> <?php $form->setAttribute("class", "form-inline"); $form->prepare(); echo $this->form(null, \TwbBundle\Form\View\Helper\TwbBundleForm::LAYOUT_INLINE)->openTag($form); ?> <?php echo $this->formRow($form->get("pagenum")->setValue($session['pagenum'])); echo $this->formDateTimePicker($form->get("dealDateFrom")->setValue($session['dealDateFrom'])); echo $this->formDateTimePicker($form->get("dealDateTo")->setValue($session['dealDateTo'])); echo $this->formRow($form->get("status")->setValue($session['status'])); echo $this->formRow($form->get("agency")->setValue($session['agency'])); ?> <?php echo $this->formSubmit($form->get("submit")); echo $this->form()->closeTag(); ?> </div> |
Стоит обратить внимание на вызов formDateTimePicker(). Именно таким образом вызывается наш зарегистрированный рендерер.
Видно, на сколько стал чище и понятнее клиентский код и повысилась возможность повторного использования.
В шаблон рендеринга я не включал вызов jquery ф-ии datetimepicker, т.к. иначе вызов дублируется при каждом отображении formDateTimePicker().