在 zend 框架中填充子表单的问题

Posted

技术标签:

【中文标题】在 zend 框架中填充子表单的问题【英文标题】:Problem in populating a subform in zend framework 【发布时间】:2011-03-16 14:20:08 【问题描述】:

我正在 Zend 框架中构建一个抽象表单。这是通过从数据库中创建动态表单元素(具有键/值值的表)来完成的。 除了表单的 setDefault/populate 函数外,大部分都可以正常工作。让我解释一下。

我有一个附有 3 个子表单的主表单(向导样式表单)。表单的第 3 步有 5 个或更多动态元素。 (例如:服务器机架的属性)。 步骤 3 中的表单可以使用 ajax 进行克隆。 (因此您可以一次添加 4 个服务器机架)。提交表单时,preValidation() 函数将检查所有新字段并将它们添加到子表单中。 这么好这么好。现在问题开始了。

向子表单添加字段时,我使用创建表单元素的工厂方式 =>

$subForm->addElement($presentation_type,'fixedField'. $key.'_'.$formStep, 
array('label' => $label.':',
'required'     => $is_mandatory,
'filter'       => $filters,
'value'        => $value,
'description'  => $unit .' '. $description,
'validators'   => $validators));

这在启动新的未提交表单时工作正常,但在提交表单时无法设置 value 参数,他没有填充 value 参数(同一函数中的其他参数工作正常)。 我将zend框架升级到最新版本,尝试在google和论坛上找到我的问题,但没有成功。

如果你解决了,我会送你一杯比利时啤酒 :) 已经找了3天了。

还尝试使用 setDefault 函数和 populate 函数。

奇怪的是当我执行“echo $subForm->getElement('xxxxxx')->getValue();”时我得到正确的输出。所以看来zend只是不会渲染元素内的值。

控制器代码:

<?php
class TestController extends Zend_Controller_Action 
    protected $_form;
    protected $_namespace = 'TestController';
    protected $_session;

    /**
     * Gets the add/edit form for the current object
     *
     * @access public
     * @return object|void
     * @param boolean $search_form Set to true if you want the search form object to be returned
     */
    public function getForm()
    
        if (null === $this->_form) 
            $action = $this->getRequest()->getActionName();
            $this->_form = new Application_Form_Test();

        
        return $this->_form;
    

    /**
     * Action for the new page
     *
     * @access public
     * @return void
     */
    public function newAction ()


        //Store the parent object in a session, this way we can use the var in the 3th form step
        $this->getSessionNamespace();

        // Either re-display the current page, or grab the "next"
        // (first) sub form
        if (!$form = $this->getCurrentSubForm()) 
            $form = $this->getNextSubForm();
        

        $this->view->form = $this->getForm()->prepareSubForm($form);

    

     /**
     * Action to process the multi step form
     *
     * @access public
     * @return mixed
     */
    public function processAction()

        //No form is set
        if (!$form = $this->getCurrentSubForm()) 
            return $this->_forward('new');
        

        if (!$this->subFormIsValid($form, $this->getRequest()->getPost())) 
            $this->view->form = $this->getForm()->prepareSubForm($form);
            return $this->render('new');
        

        if (!$this->formIsValid()) 
            $form = $this->getNextSubForm();
            $this->view->form = $this->getForm()->prepareSubForm($form);
            return $this->render('new');
        

        // Valid form!
        // Let's save everything
        //......

        // All done, clear the sessions
        Zend_Session::namespaceUnset($this->_namespace);
        //$this->render('index');
        $this->_forward('index');
    

    /**
     * Ajax action that returns the dynamic form field for step3 in the form
     */
    public function newajaxformAction() 

      if(!$this->getRequest()->isXmlHttpRequest()) throw new Zend_Controller_Action_Exception("This isn't a Ajax request !", 404);

      $ajaxContext = $this->_helper->getHelper('AjaxContext');
      $ajaxContext->addActionContext('newfield', 'html')->initContext();

      //Disable view
      $this->_helper->viewRenderer->setNoRender();
      $this->_helper->layout()->disableLayout();

      $id = $this->_getParam('id', null);
      $amount = $this->_getParam('amount', null);

      $fieldsKeys = $_POST['key'];
      $fieldsValues = $_POST['value'];

      //This one adds multiple objects on one page
      $po = new Test_Partial($id,$amount,$fieldsKeys,$fieldsValues);

      echo $po->__toString();
    

    /**
     * Get the session namespace we're using
     *
     * @access public
     * @return Zend_Session_Namespace
     */
    public function getSessionNamespace()
    
        if (null === $this->_session) 
            $this->_session = new Zend_Session_Namespace($this->_namespace);
        

        return $this->_session;
    

    /**
     * Get a list of forms already stored in the session
     *
     * @access public
     * @return array
     */
    public function getStoredForms()
    
        $stored = array();
        foreach ($this->getSessionNamespace() as $key => $value) 
            $stored[] = $key;
        

        return $stored;
    

    /**
     * Get list of all subforms available
     *
     * @access public
     * @return array
     */
    public function getPotentialForms()
    
        return array_keys($this->getForm()->getSubForms());
    

    /**
     * What sub form was submitted?
     *
     * @access public
     * @return false|Zend_Form_SubForm
     */
    public function getCurrentSubForm()
    
        $request = $this->getRequest();
        if (!$request->isPost()) 
            return false;
        

        foreach ($this->getPotentialForms() as $name) 
            if ($data = $request->getPost($name, false)) 
                if (is_array($data)) 
                    return $this->getForm()->getSubForm($name);
                    break;
                
            
        

        return false;
    

    /**
     * Get the next sub form to display
     *
     * @return Zend_Form_SubForm|false
     * @access public
     */
    public function getNextSubForm()
    
        $storedForms    = $this->getStoredForms();
        $potentialForms = $this->getPotentialForms();

        foreach ($potentialForms as $name) 
            if (!in_array($name, $storedForms)) 
                return $this->getForm()->getSubForm($name);
            
        

        return false;
    

    /**
     * Is the sub form valid?
     *
     * @param  Zend_Form_SubForm $subForm
     * @param  array $data
     * @return bool
     */
    public function subFormIsValid(Zend_Form_SubForm $subForm,array $data)
    
        $name = $subForm->getName();

        echo '<br />Submitted data(Send from Controller) = <pre>';
            print_r($data);
        echo '</pre>';

        if ($subForm->isValid($data)) 
            $this->getSessionNamespace()->$name = $subForm->getValues();
            return true;
        

        return false;
    

    /**
     * Is the full form valid?
     *
     * @return bool
     * @access public
     */
    public function formIsValid()
    
        $data = array();
        foreach ($this->getSessionNamespace() as $key => $info) 
            $data[$key] = $info[$key];
        

        return (count($this->getStoredForms()) < count($this->getPotentialForms()))? false : $this->getForm()->isValid($data);
    

?>


Form Code:

<?php
class Application_Form_Test extends Zend_Form 

    public function init() 

        //Set some filters for are fields
        $this->setElementFilters(array('StringTrim'));

        //Lets make some subforms = > each subform will be on a different page
        //Step 1
        $step1 = new Zend_Form_SubForm();

        $step1->addElement('select', 'test', array(     'label' => 'Save in:',
                                                    'multiOptions' => array('choose'=>'Choose one ...','a'=>'a','b'=>'b'),
                                                    'required'     => false,
                                                    'ignore' => true,
                                                    'value'        => array('choose'),
                                                    'validators'   => array(array('InArray',false,array(array_keys(array('choose'=>'Choose one ...','a'=>'a','b'=>'b')))))));



        // Step 3
        $step2 = new Zend_Form_SubForm();

        // Add a remove and add button for the dynamic forms
        $step2->addElement('text', 'addFormAmount', array('label' => '',
                                                        'required' => false,
                                                        'ignore'=> true,
                                                        'value'        => 1,
                                                        'description'  => 'objects.',
                                                        'order' => 99992
        ));

        $step2->addElement('button', 'addForm', array('label' => 'Add',
                                                        'order' => 99991
        ));

        $step2->getElement('addForm')->setAttrib('onClick', 'ajaxAddForm();');

        // Add a hidden id field, this way we can use the id in javascript to count the numner of fields
        $step2->addElement('hidden', 'id', array('value' => 1));

            $this->addAbstractField($step2,'',1,'test value');


        //test, let's put our prevalidation at the end of the form object
        $this->preValidation($step2,$_POST);

        // Finally attach sub forms to main form
        $this->addSubForms(array(
            'step1' => $step1,
            'step2' => $step2
        ));
    


    /**
     * Create a sluggable string for forms or any other uri related string
     *
     * @return mixed
     * @access public
     * @param mixed $array
     */
    protected function getSlug($string)
         $slug = trim($string); // trim the string
         $slug= preg_replace('/[^a-zA-Z0-9 -]/','',$slug ); // only take alphanumerical characters, but keep the spaces and dashes too…
         $slug= str_replace(' ','-', $slug); // replace spaces by dashes
         $slug= strtolower($slug); // make it lowercase
         return $slug;
    

    /**
     * Prepare a sub form for display
     *
     * @param  string|Zend_Form_SubForm $spec
     * @return Zend_Form_SubForm
     */
    public function prepareSubForm($spec)
    
        if (is_string($spec)) 
            $subForm = $this->$spec;
         elseif ($spec instanceof Zend_Form_SubForm) 
            $subForm = $spec;
         else 
            throw new Exception('Invalid argument passed to ' . __FUNCTION__ . '()');
        
        $this->setSubFormDefaultDecorators($subForm)
             ->addSubmitButton($subForm)
             ->addSubFormActions($subForm);

        return $subForm;
    

    /**
     * Add form decorators to an individual sub form
     *
     * @param  Zend_Form_SubForm $subForm
     * @return My_Form_Registration
     */
    public function setSubFormDefaultDecorators(Zend_Form_SubForm $subForm)
    
        $subForm->setDecorators(array(
            'FormElements',
            array('HtmlTag', array('tag' => 'dl','class' => 'zend_form')),'Form',));
        return $this;
    

    /**
     * Add a submit button to an individual sub form
     *
     * @param  Zend_Form_SubForm $subForm
     * @return My_Form_Registration
     */
    public function addSubmitButton(Zend_Form_SubForm $subForm)
    
        $subForm->addElement(new Zend_Form_Element_Submit(
            'save',
            array(
                'label'    => 'Save and continue',
                'required' => false,
                'ignore'   => true,
                'order' => 99999
            )));

        $subForm->getElement('save')->setAttrib('onClick', 'ajaxController(); $("#processing_alert").css("display", "block");');

        return $this;
    

    /**
     * Add action and method to sub form
     *
     * @param  Zend_Form_SubForm $subForm
     * @return My_Form_Registration
     */
    public function addSubFormActions(Zend_Form_SubForm $subForm)
    
        $subForm->setAction('/test/process')
                ->setMethod('post')
                ->setEnctype(Zend_Form::ENCTYPE_MULTIPART);
        return $this;
    

/**
 * After post, pre validation hook
 *
 * Finds all fields where name includes 'newField' and uses addNewField to add
 * them to the form object
 *
 * @param array $data $_GET or $_POST
 */
public function preValidation(Zend_Form_SubForm $subForm,array $data) 
  // array_filter callback
  function findFields($field) 
    // return field names that include 'newField'
    if (strpos($field, 'newField') !== false) 
      return $field;
    
  

  // Search $data for dynamically added fields using findFields callback
  $newFields = array_filter(array_keys($data), 'findFields');

  foreach ($newFields as $fieldName) 
    // strip the id number off of the field name and use it to set new order
    $ex1 = explode('newField', $fieldName);
    $ex2 = explode('_',$ex1[1]);
    $key = $ex2[0];
    $order = $ex2[1];

    $this->addAbstractField($subForm,$key, $order,$data[$fieldName]);
    //echo 'order :'.$order." and key is " .$key."<br />"; test ok
  


/**
 * Adds new fields to form dynamically
 *
 * @param string $name
 * @param string $value
 * @param int    $order
 * @param object $subForm
 *
 */
public function addAbstractField(Zend_Form_SubForm $subForm,  $key, $formStep=null,$value)

                $subForm->addElement('text','fixedField'. $key.'_'.$formStep, array('label' => 'Test label:',
                                                                                            'required'     => 'true',
                                                                                            'value'        => $value,
                                                                                            'description'  => 'test description'));

            echo '<br />Added element to subform (Send from Form method) key = "fixedField'. $key.'_'.$formStep .'" and value "'.$value.'"<br />';
            return $this;
    

?>


Form Partial code:

<?php
class Test_Partial 

    protected $id;
    public function __construct($id,$amount=1,$fieldsKeys=array(),$fieldsValues=array())
    
        $this->id = $id;
        $this->amount = is_int( (int)$amount) ? $amount: 1 ;
        $this->fields = array();

        //Lets combine both arrays into one
        foreach ($fieldsKeys as $key => $value)
            $ex = explode('fixedField',$value);
            $ex2 = explode('_',$ex[1]);
            $this->fields[$ex2[0]] = $fieldsValues[$key];
        
    

    public function get() 
        $result_array = array();
        $amount_counter = 1;

        while ($amount_counter <= $this->amount) 

            $result_array[] = new Zend_Form_Element_Text('newField'. $keyvalue['id'].'_'.($this->id+$amount_counter), array(    'label' => 'test:',
                                                                                                                'required'     => true,
                                                                                                                'value'      => 'this data will be lost'));


        $tikk = new Zend_Form_Element_Button('removeForm'.($this->id+$amount_counter), array('label' => 'Remove'));
            $tikk->setAttrib('onClick', 'ajaxRemoveForm('.($this->id+$amount_counter).')');

            $result_array[] = $tikk;
            ++ $amount_counter;
        

        return $result_array;
    

    public function __toString()
    
        return implode('', $this->get());
    

    /**
     * Create a sluggable string for forms or any other uri related string
     *
     * @return mixed
     * @access public
     * @param mixed $array
     */
    protected function getSlug($string)
         $slug = trim($string); // trim the string
         $slug= preg_replace('/[^a-zA-Z0-9 -]/','',$slug ); // only take alphanumerical characters, but keep the spaces and dashes too…
         $slug= str_replace(' ','-', $slug); // replace spaces by dashes
         $slug= strtolower($slug); // make it lowercase
         return $slug;
    

?>

View:

<script type="text/javascript">
function getLastSubId()
    var maxc = 0;
    // Remove all elements with a certain sub id
    $('*').each(function() 
        num = parseInt(this.id.split("_")[1],10);
        if(num > maxc)
        
           maxc = num;

        
    );
    return maxc;


// Retrieve new element's html from action controller
function ajaxAddForm(amount) 
    // Diplay processing msg
    $("#processing_alert").css("display", "block");

    // Get value of id - integer appended to dynamic form field names and ids
    var id = $("#step2-id").val();

    if(typeof amount == 'undefined')
        var amount = $("#step2-addFormAmount").val();
    

    var fields = '';

    // Collect all field keys and values and include them in the ajax request.
    $('*[id*=step2-fixedField]:visible').each(function() 
          var key = $(this).attr('id');
          var value = $(this).attr('value');
          fields  += '&key[]='+key+'&value[]='+value;
    );

    $.ajax(
    
      type: "POST",
      url: "<?php echo $this->url(array('action' => 'newAjaxForm')); ?>",
      data: "id=" + id + "&amount=" + amount + fields,
      async: false,
      success: function(newForm) 

        // Insert new element before the Add button
        var counter = parseInt(id) + parseInt(amount);

        $("#addForm-label").before(newForm);
        $("#step2-id").val(counter);
      
    
  );
    // Disable processing msg
    $("#processing_alert").css("display", "none");


function ajaxRemoveForm(id) 
    // Diplay processing msg
    $("#processing_alert").css("display", "block");

    // Remove the "remove" button that we just pressed + label
    $("#removeForm"+id+"-label").remove();
    $("#removeForm"+id+"-element").remove();

    // Remove all elements with a certain sub id
    $('*[id*=_'+id+'-]:visible').each(function() 
        $(this).remove();
    );

    // Disable processing msg
    $("#processing_alert").css("display", "none");

</script>
<?php echo $this->form; ?>

【问题讨论】:

【参考方案1】:

我正在毛里求斯解决同样的问题 :)

我猜你的 ajax 生成的额外字段是基于 Jeremy Kendall 的教程:

http://www.jeremykendall.net/2009/01/19/dynamically-adding-elements-to-zend-form/

我花了很长时间才让它工作。

我已经这样修改了他的教程:

在我的主表单定义中,我得到了函数 preValidation 和 addNewExperience(我在网上处理一份简历)。

public function preValidation(array $data) 

        // array_filter callback
        function findSubForms($subForms) 
            // return field names that include 'newName'
            if (strpos($subForms, 'experience') !== false) 
                return $subForms;
            
        

        // Search $data for dynamically added fields using findFields callback
        $newSubForms = array_filter(array_keys($data), 'findSubForms');

        foreach ($newSubForms as $subForm) 
            // strip the id number off of the field name and use it to set new order

            $order = ltrim($subForm, 'experience') + 3;
            $this->addNewExperience($subForm, $data[$subForm], $order);

        
    



public function addNewExperience($name, $value, $order) 

        $mysubForm = new Application_Form_Experience();
        $this->addSubForm($mysubForm, $name, $order);
    

所以,

要么表单通过验证,在这种情况下,它将进入模型并插入数据库,或者验证失败并且表单必须以键入的值显示。

在这种情况下,启动 preValidation,新添加的 subForms 将重新显示新添加的值。

它完全适合我。而且我认为我什至不需要 addNewExperience 中的 $value (它没有在函数中使用)。 Zend 也擅长在填充数组时查找值所属的位置。

我正在努力将表单格式化为表格:/ 结构有点脆弱。如果我稍微触摸装饰器,表单就会停止正确填充。

不太清楚发生了什么,正如您所发现的,我们中没有多少人在网上做这种事情。

【讨论】:

以上是关于在 zend 框架中填充子表单的问题的主要内容,如果未能解决你的问题,请参考以下文章

zend 框架子表单isValidPartial?

Zend 框架 - 定制表单验证

如何使用 zend from 在 zend 框架 2 中添加自定义属性

Zend 框架中的表单级别验证

Zend 框架 2 - 在布局中添加子视图?

设置 zend 框架动作控制器