Как стать автором
Обновить

Динамическое добавление групп элементов в формах Zend Framework с использованием ZendX_JQuery

Время на прочтение7 мин
Количество просмотров3.9K
Не секрет, что часто возникает необходимость добавить на форму элементы или группы элементов, количество которых может быть неопределенным или достаточно большим, чтобы указать их явно в конфигурации формы.

Также, не секрет, что существует общий подход к решению данного вопроса — добавление групп элементов через субформы. Логика этого подхода проста — в шаблоне посредством яваскрипта на форму добавляются необходимые группы элементов, в обработчике формы подсчитывается кол-во пришедших групп элементов и, соответственно их количеству, добавляются субформы, затем уже вся форма с субформами валидируется.

Для меня минус этого подхода заключается в том, что практически невозможно вынести конфигурацию формы в отдельное место (в отдельный файл конфигурации) и ее приходится «доконфигурировать» в обработчике формы.

Я предлагаю решить этот вопрос посредством создания отдельного элемента формы, реализующего данную функциональность.

image

Перейдем к практической реализации этого.



Создадим новый ZF проект

% zf create project www.multielement.lo

Инициализируем объект View с поддержкой JQuery в конфигурационном файле application.ini, устанавливаем путь к нашим помощникам, устанавливаем версии и пути к javascript-библиотекам

resources.view[] = ""
resources.view.helperPath.ZendX_JQuery_View_Helper = "ZendX/JQuery/View/Helper"
resources.view.helperPath.My_JQuery_View_Helper = "My/JQuery/View/Helper"
resources.jquery.version = "1.7"


Создадим FormController в /application/controllers/

<?php
class FormController extends Zend_Controller_Action
{
    public function indexAction()
    {
    	$opts = array(
    		'elements' => array(
    			'firstname' => array(
    				'type' => 'Text',
    				'options' => array(
    					'label' => 'Имя'
    				)
    			),
    			'lastname' => array(
    				'type' => 'Text',
    				'options' => array(
    					'label' => 'Фамилия'
    				)
    			),
    			'items' => array(
    				'type' => 'MultiElement',
    				'options' => array(
    					'label' => 'Товары',
    					'required' => true,
    					'elements' => array(
    						'name' => array(
    							'type' => 'Text',
    							'options' => array(
    								'label' => 'Наименование',
    								'required' => true
    							)
    						),
    						'type' => array(
    							'type' => 'Select',
    							'options' => array(
    								'label' => 'Цвет',
    								'required' => true,
    								'multioptions' => array(
    									'green' => 'Зеленый',
    									'red' => 'Красный',
    									'blue' => 'Синий',
    								)
    							)
    						),
    						'price' => array(
    							'type' => 'Text',
    							'options' => array(
    								'label' => 'Стоимость, руб.',
    								'required' => true
    							)
    						),
    					)
    				)
    			),
    			'logons' => array(
    				'type' => 'MultiElement',
    				'options' => array(
    					'label' => 'Явки и пароли',
    					'required' => true,
    					'elements' => array(
    						'login' => array(
    							'type' => 'Text',
    							'options' => array(
    								'label' => 'Логин',
    								'required' => true
    							)
    						),
    						'passw' => array(
    							'type' => 'Text',
    							'options' => array(
    								'label' => 'Пароль',
    								'required' => true
    							)
    						),
    						'type' => array(
    							'type' => 'Select',
    							'options' => array(
    								'label' => 'Социальная сеть',
    								'required' => true,
    								'multioptions' => array(
    									'vk' => 'Вконтакте',
    									'fc' => 'FaceBook',
    									'tw' => 'Twitter',
    								)
    							)
    						),
    					)
    				)
    			),
    			'submit' => array(
    				'type' => 'Submit',
    				'options' => array(
    					'label' => 'Отправить'
    				)
    			),
    		),
    	);
    	$form = new Zend_Form();
    	$form->addPrefixPath('My_JQuery_Form','My/JQuery/Form');
    	$form->setOptions($opts);
    	if($this->getRequest()->isPost()) {
    		if($form->isValid($this->getRequest()->getPost())) {
    			$values = $form->getValues();
    			$this->view->assign('MyFormValues',$values);
    		}
    	}
    	$this->view->assign('MyForm',$form->render());
    }
}


Создадим скрипт вида в /views/scripts/form/index.phtml

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Пример формы с мультиэлементами</title>
	<?php print $this->JQuery(); ?>
</head>
<body class="ui-widget">
	<h1>Пример формы с мультиэлементами</h1>
	<?php print $this->MyForm; ?>
	<?php if($this->MyFormValues) { ?>
		<pre>
			<?php print_r($this->MyFormValues); ?>
		</pre>
	<?php } ?>
</body>
</html>


image

Как видно, элемент MultiElement содержит секцию опций «elements», которая совместима с Zend_Form_Element.

Принцип работы элемента MultiElement заключается в следующем:

  • Для каждой группы элементов создается форма Zend_Form без декораторов Form и DtDdWrapper
  • Каждому элементу в группе посредством setElementsBelongTo устанавливается имя вида элемент[][элемент_группы]
  • Форма рендерится и кладется в опции элемента MultiElement для дальнейшего использования в помощнике вида
  • Объект формы кладется в приватное свойство для дальнейшего использования его клонированных экземпляров в методах isValid и setValue
  • В помощнике вида отрендеренная форма группы кладется в js-переменную и добавляется в объект View посредством addJavascript
  • В помощнике вида создается кнопка «Добавить» и js для обработки кликов по ней
  • При клике происходит вставка отрендеренной формы из js-переменной с заменой имен элементов на элемент[счетчик][элемент_группы]
  • В помощнике вида также отрисовываются прищедшие значения групп элементов и сами группы


<?php
require_once "Zend/Form/Element/Xhtml.php";
class My_JQuery_Form_Element_MultiElement extends Zend_Form_Element_Xhtml {
	public $helper = "multiElement";
	/**
	 * Массив объектов формы для каждой группы дополнительных элементов
	 * @var array
	 */
	protected $forms = array();
	/**
	 * Объект формы дополнительных элементов
	 * @var Zend_Form
	 */
	protected $form;
	/**
	 * Отрендеренная форма дополнительных элементов
	 * @var string
	 */
	protected $renderform = '';
	/**
	 * Определяем массив опций и дергаем родительский конструктор
	 *
	 * @param mixed $spec
	 * @param array $options
	 */
	public function __construct($spec, $options = null) {
		/**
		 * Выделяем дополнительные элементы в отдельную форму и рендерим ее
		 */
		if(isset($options['elements']) && is_array($options['elements'])) {
			$form = new Zend_Form(array('elements'=>$options['elements']));
			$form -> removeDecorator('Form');
		    $form -> removeDecorator('DtDdWrapper');
			$form -> setElementsBelongTo($spec.'[]');
			$this->renderform = $form->render();
			unset($options['elements']);
			$this->form = $form;
		}
		/**
		 * Инициализируем родительский конструктор
		 */
		parent::__construct($spec, $options);
	}
	
	/**
	 * Валидация элемента
	 *
	 * @param mixed $value
	 * @return boolean
	 */
	public function isValid($value) {
		$this->_messages = array();
        $this->_errors   = array();
		$this->setValue($value);
        $value = $this->getValue();
		if(!is_array($value) && $this->isRequired()) {
			$this->_messages = array('Значение обязательно для заполнения и не может быть пустым');
			return false;
		}
		$result = true;
        if(is_array($value)) {
	        foreach ($value as $key=>$mini_form) {	
	        	if(key_exists($key,$this->forms)) {
	        		$form = $this->forms[$key];
	        		if(!$form->isValid($mini_form)) $result = false;
	        	}
			}
        }
		return $result;
	}
	
	/**
     * Set element value
     *
     * @param  array $value
     * @return Zend_Form_Element
     */
    public function setValue($value) {
    	if(!is_array($value)) return $this;
        $this->_value = $value;
        foreach ($value as $mini_form) {	
	        $form = clone $this->form;
	        $this->forms[] = $form->setDefaults($mini_form);
		}
        return $this;
    }
}


Как вы можете видеть, поддерживается опция required. При необходимости, элемент MultiElement можно расширить, добавив обработку дополнительных опций, фильтров и валидаторов

Код помощника вида

<?php
require_once "ZendX/JQuery/View/Helper/UiWidget.php";
class My_JQuery_View_Helper_MultiElement extends ZendX_JQuery_View_Helper_UiWidget {
	/**
	 * Рисуем элемент
	 *
	 * @param string $id Id HTML-элемента
	 * @param string $value Значение элемента
	 * @param array $params Массив конфигурации из секции options
	 * @return string
	 */
	public function multiElement($id, $value = null, array $params = array()) {
		/**
		 * Определяем форму с элементами группы
		 * Добавляем форму в JS
		 */
		$js_var = $id . '_subform';
		if(isset($params['renderform'])) {
			$this->jquery->addJavascript('var ' . $js_var . ' = ' 
												. ZendX_JQuery::encodeJson($params['renderform']) . ';');
		}
		/**
	     * Определяем кнопку удаления группы
	     */
	    $icon_delete = $this->view->formButton($id . '_delete', 'Удалить');;
		/**
	     * Формируем кнопку добавления новой группы и вешаем на нее JS
	     */
		$button_id = $id . '_add';
	    $button = $this->view->formButton($button_id, 'Добавить');
	    $jquery_handler = ZendX_JQuery_View_Helper_JQuery::getJQueryHandler();
	    $js = array();
	    $js[] = sprintf('%s("#%s").next("ul").find("> li").prepend(%s("%s").click(function(){
		    	if(confirm("%s")) %s(this).parent("li").remove();
				return false;
		    }));',
		$jquery_handler, $button_id, $jquery_handler, addslashes($icon_delete), 'Удалить?', $jquery_handler);
	    $js[] = sprintf('%s("#%s").click(function(){
		    	var itr = %s(this).next("ul").find("> li").length-1;
		    	var Tmpl = %s.replace(/name=\"%s\[\]\[/g,"name=\"%s["+itr+"][");
		    	var li = %s(this).next("ul").find("li:last").clone(true).insertBefore(%s(this)
		    					.next("ul").find("li:last")).append(Tmpl).show();
	    	});',
        $jquery_handler, $button_id, $jquery_handler, $js_var, $id, $id, $jquery_handler, $jquery_handler);
	    $this->jquery->addOnLoad(join(PHP_EOL,$js));
	    /**
	     * Отрисовываем переданные группы и шаблон
	     */
	    $xhtml = array();
	    $xhtml[] = '<ul>';
	    $attribs = array();
	    foreach ($params as $k=>$v) if(in_array($k,array('class','style'))) $attribs[$k] = $v;
	    /**
	     * Устанавливаем пришедшие значения
	     */
		foreach ($params['forms'] as $key=>$form) {
	    	$form -> setElementsBelongTo($id . '['.$key.']');
	    	$xhtml[] = '<li' . $this->_htmlAttribs($attribs) . '>' . $form->render() . '</li>';
	    }
	    /**
	     * Отрисовываем "хвост" списка
	     */
		if(isset($attribs['style'])) $attribs['style'] .= ';display:none'; else $attribs['style'] = 'display:none'; 
	    $xhtml[] = '<li' . $this->_htmlAttribs($attribs) . '></li>';
	    $xhtml[] = '</ul>';
		return $button . join(PHP_EOL,$xhtml);
	}
}


Значения формы получаются в аккуратном иерархическом виде

[firstname] => Вася
[lastname] => Иванов
[items] => Array
    (
        [2] => Array
            (
                [name] => Бабочки
                [type] => red
                [price] => 1000
            )
        [3] => Array
            (
                [name] => Бревна
                [type] => blue
                [price] => 2000
            )
    )
[logons] => Array
    (
        [0] => Array
            (
                [login] => username
                [passw] => qwerty
                [type] => vk
            )
    )


К сожалению, в группу элементов нельзя добавить элемент File, поскольку он не поддерживает BelongTo. Я пробовал добавить ее поддержку в декораторе File, но столкнулся с проблемами в Zend_File_Transfer_Adapter, связанными с отсутствием обработки многомерных массивов в переменной $_FILES.

Можно попробовать формировать префикс в setBelongTo без [] и обрабатывать массив файлов отдельно от группы элементов, в которую они входят либо использовать какой-нибудь Ajax File Uploader вместо элемента File.

Скачать полностью рабочий пример можно отсюда
Теги:
Хабы:
Всего голосов 11: ↑10 и ↓1+9
Комментарии4

Публикации