Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

Zend Framework: Zend_Controller_Action_Helper_MultiPageForm Component Proposal

Proposed Component Name Zend_Controller_Action_Helper_MultiPageForm
Developer Notes http://framework.zend.com/wiki/display/ZFDEV/Zend_Controller_Action_Helper_MultiPageForm
Proposers Jurriën Stutterheim
Matthew Weier O'Phinney (Zend Liaison)
Revision 0.1 - 1 January 2008: Initial revision. (wiki revision: 6)

Table of Contents

1. Overview

Zend_Controller_Action_Helper_MultiPageForm provides support for multi-page forms and Post-Redirect-Get with Zend_Form.

2. References

This proposal heavily based on Simon Mundy's Multiform action helper. Credit to this idea should go to Simon. I merely made it play nice with Zend_Form

3. Component Requirements, Constraints, and Acceptance Criteria

  • This component will provide PRG support.
  • This component will only support Zend_Form.
  • This component will provide multi-page form support.
  • This component could use additional back-ends to store the temporary form data

4. Dependencies on Other Framework Components

  • Zend_Exception
  • Zend_Session
  • Zend_Controller_Action_Helper_Abstract

5. Theory of Operation

The helper makes use of Zend_Form's subform support to provide multi-page forms. Each subform is essentially a form page.
At the end of the ride all the filtered/validated data is collected and made available to the developer to process it.
A session is used to store the data between requests.

Each subform needs a corresponding controller action. The subform and action have the same name.
The action serves two purposes. It allows custom logic for the data handling and it renders the view.

There is some more functionality that could be added to the helper, but I would like to discuss the options of that first.
One of those possibilities could be the branches idea of Mitchell's form proposal.
Another is the ability to allow unique instances of the same form to be open and filled in simultaneously. However, this would heavily increase the helper's complexity. This probably would be an option for a second iteration.

6. Milestones / Tasks

  • Milestone 1: [DONE] Rewrite Simon's helper to specialize it for Zend_Form
  • Milestone 2: Discuss points mentioned above.
  • Milestone 3: Update proposal to reflect comments and results of discussions.
  • Milestone 4: Helper checked in to incubator.
  • Milestone 5: Documentation & unit test.
  • Milestone 6: Helper accepted into core.

7. Class Index

  • Zend_Controller_Action_Helper_MultiPageForm

8. Use Cases

UC-01: Basic usage

9. Class Skeletons

]]></ac:plain-text-body></ac:macro>

]]></ac:plain-text-body></ac:macro>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. May 07, 2008

    <p>One thing that's got me puzzled is how you would use subgroups within each form, rather than one subgroup-per-action. For example, I may have one page with a group of checkboxes that are contained within one subgroup (by necessity - the display group does something else with them...) - how then do I represent this?</p>

    <p>The original thinking for the Multiform helper was to provide less dependance on a particular component and try to design more for an interface. Granted, it was before Zend_Form was mature, so the method names were more closely matched to PEAR's HTML_Form. But it allowed a lot more flexibility in implementation.</p>

    <p>If it helps by example, here is a current multipage form implementation I'm using together with Zend_Form and my own multipage form helper. It's for an accommodation booking system:-</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[<?php

    class Booking_AddController extends Zend_Controller_Action
    {
    public function init()

    Unknown macro: { $multiform = $this->_helper->getHelper('Multiform'); $multiform->setActions(array( 'personal', 'accommodation', 'review' ); }

    public function indexAction()

    Unknown macro: { $multiform = $this->_helper->multiform; $multiform->clear(); $this->_helper->redirector->gotoRoute(array( 'action' => 'personal' )); }

    public function personalAction()
    {
    $multiform = $this->_helper->multiform;

    // Implemented the SPL observer interface to my form instances - it triggers update()
    // when a successful form update
    $form = new App_Form_BookingPersonal();
    $form->attach(new App_Registrar_BookingPersonal($this));
    $form->populate($multiform->getActionValues());

    if ($this->getRequest()->isPost())

    Unknown macro: { $valid = $form->isValid($this->getRequest()->getPost()); $values = $form->getValues(); $multiform->update($values, $valid); }

    $this->view->form = $form;
    }

    public function accommodationAction()
    {
    $multiform = $this->_helper->multiform;

    // Implemented the SPL observer interface to my form instances - it triggers update()
    // when a successful form update
    $form = new App_Form_BookingAccommodation();
    $form->attach(new App_Registrar_BookingAccommodation($this));
    $form->populate($multiform->getActionValues());

    if ($this->getRequest()->isPost())

    $this->view->form = $form;
    }

    public function reviewAction()
    {
    $multiform = $this->_helper->multiform;

    // Implemented the SPL observer interface to my form instances - it triggers update()
    // when a successful form update
    $form = new App_Form_BookingReview();
    $form->attach(new App_Registrar_BookingReview($this));
    $form->populate($multiform->getActionValues());

    if ($this->getRequest()->isPost())

    Unknown macro: { $valid = $form->isValid($this->getRequest()->getPost()); $values = $form->getValues(); $multiform->update($values, $valid); }

    $this->view->form = $form;
    }

    public function submitAction()

    Unknown macro: { // Do stuff... Ends the multipage session $this->_helper->redirector->gotoRoute(array( 'action' => 'thankyou' )); }

    public function cancelAction()

    Unknown macro: { $multiform = $this->_helper->multiform; $multiform->clear(); // Perform your cleanup actions.... // Redirect away from this controller $this->_helper->redirector->gotoRoute(array( 'controller' => 'index' ), null, true); }

    public function thankyouAction()

    Unknown macro: { $multiform = $this->_helper->multiform; $multiform->clear(); }

    }]]></ac:plain-text-body></ac:macro>

    <p>I believe this strategy works well - it only ever performs validation if $_POST data exists, and is smart enough to know if the 'back' or 'cancel' actions are triggered then it should save the form data but ignore the validation errors.</p>

    <p>Ultimately, it prevents the action helper from being <em>too</em> helpful - as a developer you are free to code your actions precisely the way you need to and arrange the 'update' method as you see fit. Unless I'm mistaken it looks like the proposal here performs its checking during the preDispatch loop?</p>

    <p>ANyway, that's my 2c for now - look forward to seeing how this progresses.</p>

    1. May 21, 2008

      <p>Can we have your action helper code ?</p>

  2. Jun 02, 2008

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Comments</ac:parameter><ac:rich-text-body>
    <p>The Zend Framework team approves this proposal for immediate development in the<br />
    standard incubator, with the following suggestions:</p>

    <ul>
    <li><strong>Allow custom mappings.</strong> Currently, the proposal states that each sub form should map to an action of the same name. It would be more flexible to allow explicit mappings - which would allow the developer to handle all sub forms in a single action, or to provide custom URL schemas.</li>
    <li><strong>Provide an AJAX use case,</strong> if feasible. One potential use case for multi-page forms is to use AJAX to submit one page, and then return the next form as the XHR response if valid. A use case showing this in the manual would be a nice addition, though not necessary.</li>
    </ul>
    </ac:rich-text-body></ac:macro>

  3. Mar 18, 2009

    <p>This proposal has not been updated in the past 6 months. Archiving for now.</p>

    1. Mar 18, 2009

      <p>The component itself has been approved for the standard incubator, where it has been sitting for quite a while now. Wouldn't it make more sense to keep this in the standard incubator wiki section?</p>

  4. Apr 01, 2009

    <p>I like the idea of this helper.</p>

    <p>My own comments relate to how it handles PRG in a multiform scenario.</p>

    <p>I would like to suggest that the helper allows you to supply a naming convention to permit a PRG.</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $multiform->setActions(
    array(
    'first',
    'second',
    'third',
    )
    );
    $multiform->setPRGActionSuffix('-do');
    ]]></ac:plain-text-body></ac:macro>

    <p>This way you could have the action methods: firstAction(), secondAction() thirdAction() etc. which would display the forms as normal, and firstDoAction(), etc. which would (in a PRG scenario) <strong>always</strong> redirect after it has processed the form.</p>

    <p>By adopting this technique, the back button would never break, even after a multiple failure on a single stage. firstDoAction() would redirect to either firstAction() on failure or secondAction() on success.</p>

    <p>An alternative to a setPRGActionSuffix() method would be to make setActions() accept an associative array where the keys are the GET actions and the values are the POST actions.</p>

  5. Oct 02, 2009

    <p>I recently trying to get this running and found a small bug in the latest Incubator version:</p>

    <p>Was:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    if (!$this->_activeFormName) {
                $formName = $this->getRequest()>getParams($this>_formRoutePart);
    ]]></ac:plain-text-body></ac:macro>
    <p>Fixed:</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    if (!$this->_activeFormName) {
                $formName = $this->getRequest()>getParams($this>_formNameRoutePart);
    ]]></ac:plain-text-body></ac:macro>
    <p>However, I'm not getting this running. After setting up my form using the basic usecase, I get the following warning:</p>

    <p>Additionally, I'm unable to set the action for the form, thus it is empty in the resulting code.</p>

    1. Nov 08, 2009

      <p>It seems that in the latest Incubator still persists this bug :</p>

      <p>Bugged</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      if (!$this->_activeFormName) {
      $formName = $this->getRequest()>getParams($this>_formNameRoutePart);
      ]]></ac:plain-text-body></ac:macro>

      <p>Fixed </p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      if (!$this->_activeFormName) {
      $formName = $this->getRequest()>getParam($this>_formNameRoutePart);
      ]]></ac:plain-text-body></ac:macro>

  6. Nov 08, 2009

    <p>Fixing the small tipo bug above I've been able to run the order form.</p>

    <p>What occurs is that I don't know how to show multiform navigation. <br />
    Does it exits a view helper ? Do I must create buttons in each subform ?</p>

    <p>Posting a complete working example would be nice.</p>

    <p>Thank you</p>

  7. Nov 17, 2009

    <p>Isn't it bad/slow to initialize all subforms on each action?</p>