📜 ⬆️ ⬇️

Dynamically adding groups of elements in Zend Framework forms using ZendX_JQuery

It is no secret that it is often necessary to add elements or groups of elements to a form, the number of which may be uncertain or large enough to indicate them explicitly in the configuration of the form.

Also, it is no secret that there is a general approach to solving this issue - adding groups of elements through subforms. The logic of this approach is simple - in the template, using the javascript, the necessary groups of elements are added to the form, the form handler calculates the number of incoming groups of elements and, according to their number, subforms are added, then the entire form with subforms is validated.

For me, the minus of this approach is that it is almost impossible to take out the configuration of the form in a separate place (in a separate configuration file) and it has to be “configured” in the form handler.
')
I propose to solve this issue by creating a separate form element that implements this functionality.

image

Let us turn to the practical implementation of this.



Create a new ZF project

% zf create project www.multielement.lo

We initialize the View object with jQuery support in the application.ini configuration file, set the path to our helpers, set the versions and paths to the javascript libraries

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"


Create a FormController in / 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()); } } 


Create a view script in /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

As you can see, the MultiElement element contains the “elements” options section, which is compatible with Zend_Form_Element.

The principle of operation of the MultiElement element is as follows:



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


As you can see, the required option is supported. If necessary, the MultiElement element can be expanded by adding processing of additional options, filters and validators.

View helper code

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


Form values ​​are obtained in a neat hierarchical form.

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


Unfortunately, you cannot add a File element to a group of elements because it does not support BelongTo. I tried to add its support in the File Decorator, but I ran into problems in the Zend_File_Transfer_Adapter due to the lack of processing of multidimensional arrays in the $ _FILES variable.

You can try to form the prefix in setBelongTo without [] and process the array of files separately from the group of elements into which they belong, or use some Ajax File Uploader instead of the File element.

Download a fully working example from here.

Source: https://habr.com/ru/post/140178/


All Articles