Zend_Form 的高级用法
数组符号(Notation)许多有经验的 web 开发者喜欢在元素名上用数组符号把相关的表单元素组成组。例如,如果有两个地址在表单上,一个邮寄地址和一个帐单地址,你可能会有相同的元素,通过把它们用数组组成组,你可以确保它们能分别被抓取。用下面的表单做个例子: <form> <fieldset> <legend>Shipping Address</legend> <dl> <dt><label for="recipient">Ship to:</label></dt> <dd><input name="recipient" type="text" value="" /></dd> <dt><label for="address">Address:</label></dt> <dd><input name="address" type="text" value="" /></dd> <dt><label for="municipality">City:</label></dt> <dd><input name="municipality" type="text" value="" /></dd> <dt><label for="province">State:</label></dt> <dd><input name="province" type="text" value="" /></dd> <dt><label for="postal">Postal Code:</label></dt> <dd><input name="postal" type="text" value="" /></dd> </dl> </fieldset> <fieldset> <legend>Billing Address</legend> <dl> <dt><label for="payer">Bill To:</label></dt> <dd><input name="payer" type="text" value="" /></dd> <dt><label for="address">Address:</label></dt> <dd><input name="address" type="text" value="" /></dd> <dt><label for="municipality">City:</label></dt> <dd><input name="municipality" type="text" value="" /></dd> <dt><label for="province">State:</label></dt> <dd><input name="province" type="text" value="" /></dd> <dt><label for="postal">Postal Code:</label></dt> <dd><input name="postal" type="text" value="" /></dd> </dl> </fieldset> <dl> <dt><label for="terms">I agree to the Terms of Service</label></dt> <dd><input name="terms" type="checkbox" value="" /></dd> <dt></dt> <dd><input name="save" type="submit" value="Save" /></dd> </dl> </form> 在本例中,帐单地址和邮寄地址包含一些相同的字段,这意味着一个可能会覆盖另一个。我们可以用数组符号来解决这个问题: <form> <fieldset> <legend>Shipping Address</legend> <dl> <dt><label for="shipping-recipient">Ship to:</label></dt> <dd><input name="shipping[recipient]" id="shipping-recipient" type="text" value="" /></dd> <dt><label for="shipping-address">Address:</label></dt> <dd><input name="shipping[address]" id="shipping-address" type="text" value="" /></dd> <dt><label for="shipping-municipality">City:</label></dt> <dd><input name="shipping[municipality]" id="shipping-municipality" type="text" value="" /></dd> <dt><label for="shipping-province">State:</label></dt> <dd><input name="shipping[province]" id="shipping-province" type="text" value="" /></dd> <dt><label for="shipping-postal">Postal Code:</label></dt> <dd><input name="shipping[postal]" id="shipping-postal" type="text" value="" /></dd> </dl> </fieldset> <fieldset> <legend>Billing Address</legend> <dl> <dt><label for="billing-payer">Bill To:</label></dt> <dd><input name="billing[payer]" id="billing-payer" type="text" value="" /></dd> <dt><label for="billing-address">Address:</label></dt> <dd><input name="billing[address]" id="billing-address" type="text" value="" /></dd> <dt><label for="billing-municipality">City:</label></dt> <dd><input name="billing[municipality]" id="billing-municipality" type="text" value="" /></dd> <dt><label for="billing-province">State:</label></dt> <dd><input name="billing[province]" id="billing-province" type="text" value="" /></dd> <dt><label for="billing-postal">Postal Code:</label></dt> <dd><input name="billing[postal]" id="billing-postal" type="text" value="" /></dd> </dl> </fieldset> <dl> <dt><label for="terms">I agree to the Terms of Service</label></dt> <dd><input name="terms" type="checkbox" value="" /></dd> <dt></dt> <dd><input name="save" type="submit" value="Save" /></dd> </dl> </form> 在上例中,我们有了两个单独的地址。在提交的表单,我们有三个元素,'save' 元素表示提交,和两个数组 'shipping' 和 'billing',每个都有键对应它们的元素。
另外,有工具让你有条件地打开数组符号,也可以指定特定的数组给元素和集合所属的(子表单或表单):
另外,在元素一级,你可以用 多页表单
目前, 生成多页表单的关键是利用子表单,但每个页面只显示一个子表单。这让你一次提交一个单个的表单并校验它,直到所有表单都提交了才处理。 Example #1 注册表单示例 让我们用注册表单作为例子,我们的意图是在第一页上读取期望的用户名和密码,还有用户的元数据 -- 用户的名字、姓和地点 -- 最后让他们决定使用哪个邮件列表(如果有的话)。 首先,来生成表单,并在里面定义一些子表单: <?php class My_Form_Registration extends Zend_Form { public function init() { // Create user sub form: username and password $user = new Zend_Form_SubForm(); $user->addElements(array( new Zend_Form_Element_Text('username', array( 'required' => true, 'label' => 'Username:', 'filters' => array('StringTrim', 'StringToLower'), 'validators' => array( 'Alnum', array('Regex', false, array('/^[a-z][a-z0-9]{2,}$/')) ) )), new Zend_Form_Element_Password('password', array( 'required' => true, 'label' => 'Password:', 'filters' => array('StringTrim'), 'validators' => array( 'NotEmpty', array('StringLength', false, array(6)) ) )), )); // Create demographics sub form: given name, family name, and location $demog = new Zend_Form_SubForm(); $demog->addElements(array( new Zend_Form_Element_Text('givenName', array( 'required' => true, 'label' => 'Given (First) Name:', 'filters' => array('StringTrim'), 'validators' => array( array('Regex', false, array('/^[a-z][a-z0-9., \'-]{2,}$/i')) ) )), new Zend_Form_Element_Text('familyName', array( 'required' => true, 'label' => 'Family (Last) Name:', 'filters' => array('StringTrim'), 'validators' => array( array('Regex', false, array('/^[a-z][a-z0-9., \'-]{2,}$/i')) ) )), new Zend_Form_Element_Text('location', array( 'required' => true, 'label' => 'Your Location:', 'filters' => array('StringTrim'), 'validators' => array( array('StringLength', false, array(2)) ) )), )); // Create mailing lists sub form $listOptions = array( 'none' => 'No lists, please', 'fw-general' => 'Zend Framework General List', 'fw-mvc' => 'Zend Framework MVC List', 'fw-auth' => 'Zend Framwork Authentication and ACL List', 'fw-services' => 'Zend Framework Web Services List', ); $lists = new Zend_Form_SubForm(); $lists->addElements(array( new Zend_Form_Element_MultiCheckbox('subscriptions', array( 'label' => 'Which lists would you like to subscribe to?', 'multiOptions' => $listOptions, 'required' => true, 'filters' => array('StringTrim'), 'validators' => array( array('InArray', false, array(array_keys($listOptions))) ) )), )); // Attach sub forms to main form $this->addSubForms(array( 'user' => $user, 'demog' => $demog, 'lists' => $lists )); } } 注意还没有提交按钮,而起我们对子表单的装饰器也没有做任何事情 -- 意思是缺省地他们作为字段(fieldsets)显示。我们将能够 override 这些因为我们显示每个独立的子表单,并加入提交按钮这样我们就可以处理它们了 -- 也要求有动作和方法(注:这里的方法是 'post' 或 'get')属性。来给我们的类添砖加瓦让它提供那些信息: class My_Form_Registration extends Zend_Form { // ... /** * 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->setSubFormDecorators($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 setSubFormDecorators(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, ) )); 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('/registration/process') ->setMethod('post'); return $this; } }
接着,我们也需要为动作控制器添加一些辅助东西,并有若干考虑。首先,我们需要确保在请求之间保持表单数据,这样可以确定何时退出。第二,我们需要一些逻辑来确定表单的哪部分已经提交,哪个子表单需要基于这些信息来显示。我们使用 让我们来创建控制器,并添加用于获取表单实例的方法: <?php class RegistrationController extends Zend_Controller_Action { protected $_form; public function getForm() { if (null === $this->_form) { require_once 'My/Form/Registration.php'; $this->_form = new My_Form_Registration(); } return $this->_form; } } 现在,添加一些函数来确定显示哪个表单。基本上,直到整个表单被认为有效,我们才需要继续显示表单片段。另外,我们可能想确保它们是按一定的顺序:用户、demog 和 列表。我们在可以通过检查出现在每个子表单上的特定键的会话命名空间确定哪个数据被提交。 class RegistrationController extends Zend_Controller_Action { // ... protected $_namespace = 'RegistrationController'; protected $_session; /** * Get the session namespace we're using * * @return Zend_Session_Namespace */ public function getSessionNamespace() { if (null === $this->_session) { require_once 'Zend/Session/Namespace.php'; $this->_session = new Zend_Session_Namespace($this->_namespace); } return $this->_session; } /** * Get a list of forms already stored in the session * * @return array */ public function getStoredForms() { $stored = array(); foreach ($this->getSessionNamespace() as $key => $value) { $stored[] = $key; } return $stored; } /** * Get list of all subforms available * * @return array */ public function getPotentialForms() { return array_keys($this->getForm()->getSubForms()); } /** * What sub form was submitted? * * @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 */ 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; } }
上述方法让我们使用符合如 "
现在,让我们看一下如何处理和显示不同的子表单。我们可以使用 当我们收到表单提交,可以校验子表单并接着检查是否整个表单有效。为完成这些任务,我们需要另外的方法确保提交的数据添加到会话和什么时候校验整个表单,我们依靠从会话来的所有片段(segments)来校验: <?php class My_Form_Registration extends Zend_Form { // ... /** * 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(); if ($subForm->isValid($data)) { $this->getSessionNamespace()->$name = $subForm->getValues(); return true; } return false; } /** * Is the full form valid? * * @return bool */ public function formIsValid() { $data = array(); foreach ($this->getSessionNamespace() as $key => $info) { $data[$key] = $info; } return $this->getForm()->isValid($data); } } 八字已经画了一撇,让我们来为控制器构造一个动作。我们需要为表单做一个 landing 页面,接着'process' 动作来处理表单。 <?php class RegistrationController extends Zend_Controller_Action { // ... public function indexAction() { // 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); } public function processAction() { if (!$form = $this->getCurrentSubForm()) { return $this->_forward('index'); } if (!$this->subFormIsValid($form, $this->getRequest()->getPost())) { $this->view->form = $this->getForm()->prepareSubForm($form); return $this->render('index'); } if (!$this->formIsValid()) { $form = $this->getNextSubForm(); $this->view->form = $this->getForm()->prepareSubForm($form); return $this->render('index'); } // Valid form! // Render information in a verification page $this->view->info = $this->getSessionNamespace(); $this->render('verification'); } } 正如你注意到的,处理表单的代码相当简单。我们检查是否有一个子表单提交,如果没有,就回到 landing 页面。如果我们确实有一个子表单,就尝试校验它,如果失败,重新显示。如果子表单有效,那么我们就检查表单是否有效,它将指示我们是否完成,如果没有,我们就显示下一个表单片段。最后,我们显示一个确认过的带有会话内容的页面。 The view scripts are very simple: <? // registration/index.phtml ?> <h2>Registration</h2> <?= $this->form ?> <? // registration/verification.phtml ?> <h2>Thank you for registering!</h2> <p> 这里是你所提供的信息: </p> <? // Have to do this construct due to how items are stored in session namespaces foreach ($this->info as $info): foreach ($info as $form => $data): ?> <h4><?= ucfirst($form) ?>:</h4> <dl> <? foreach ($data as $key => $value): ?> <dt><?= ucfirst($key) ?></dt> <? if (is_array($value)): foreach ($value as $label => $val): ?> <dd><?= $val ?></dd> <? endforeach; else: ?> <dd><?= $this->escape($value) ?></dd> <? endif; endforeach; ?> </dl> <? endforeach; endforeach ?> Zend Framework 的下次发行将通过抽象会话和顺序逻辑来提供制作多页面表单的组件。在这期间,上述例子对如何为你的站点生成多页面是个合理的指南。
|