在 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 框架中填充子表单的问题的主要内容,如果未能解决你的问题,请参考以下文章