在zend框架中填充子表单时出现问题

时间:2011-03-16 14:20:09

标签: php jquery zend-framework zend-form

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

我有一个主表单,附有3个子表单(向导样式表单)。表单的第3步有5个或更多动态元素。 (例如:服务器机架的属性) 可以使用ajax克隆步骤3中的表格。 (因此,您可以一次添加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框架升级到最新版本,尝试在谷歌和论坛上找到我的问题,但没有成功。

如果你解决了,我会寄给你比利时啤酒:) 一直在寻找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 个答案:

答案 0 :(得分:0)

我正在毛里求斯处理同样的问题:)

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

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,使用新添加的值重新显示新添加的子表单。

这完全适合我。而且我认为我甚至不需要addNewExperience的$ value(它没有在函数中使用)。当你的数组被填充时,Zend很擅长找到值所在的位置。

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

不太清楚发生了什么事,正如你所知,我们在网上做这类事的人并不多。