Search code examples
phpyiiyii1.x

Yii multi page form wizard best practice


I am trying to build a multi-page form with Yii, but am quite new to PHP and Yii and am wondering what the best practice is for writing a multi page form. So far, what I am planning to do is to add a hidden field named 'step' which contains the current step the user is on in the form (the form is broken into 3 steps/pages). So with that in mind, this is how I plan to handle the user clicking on previous/next buttons in the Controller:

public function actionCreate()
 {
  $userModel = new User;

  $data['activityModel'] = $activityModel; 
  $data['userModel'] = $userModel; 

  if (!empty($_POST['step']))
  {
   switch $_POST['step']:
    case '1':
     $this->render('create_step1', $data);
     break;

    case '2':
     $this->render('create_step2', $data);
     break;

  }else
  {
   $this->render('create_step1', $data);
  }
 }

Does this approach make sense? Or am I way off base and there is a much better and more optimized way of doing this in Yii/PHP?

Thanks!


Solution

  • There are a couple of ways to approach this. I see you posted in the Yii forum so I assume you've searched around there too but in case you haven't:

    What I have done is (just for a simple 2-step ActiveRecord form) taken a single action and divided it up into conditional blocks based on the button name, which Yii POSTs on a form submit (note: doesn't work with ajax submits). Then, depending on which button was hit I render the correct form and set the correct scenario on my model for validation purposes.

    A hidden "step" field like you have could serve the same purpose as the checking the submitButton name. I would perhaps save the "step" into the form state instead of adding a hidden field though, but either would be fine.

    Some people use the stateful activeForm attribute to save the data from a single step in the wizard, or you can use the session, or even save to a temp database table. In my completely untested example below I am using a the stateful form functionality.

    Here is an example of what I basically did for an ActiveRecord form. This goes in the "actionCreate":

    <?php if (isset($_POST['cancel'])) {
      $this->redirect(array('home'));
    } elseif (isset($_POST['step2'])) {
      $this->setPageState('step1',$_POST['Model']); // save step1 into form state
      $model=new Model('step1');
      $model->attributes = $_POST['Model'];
      if($model->validate())
        $this->render('form2',array('model'=>$model));
      else {
        $this->render('form1',array('model'=>$model));
      }
    } elseif (isset($_POST['finish'])) {
      $model=new Model('finish');
      $model->attributes = $this->getPageState('step1',array()); //get the info from step 1
      $model->attributes = $_POST['Model']; // then the info from step2
      if ($model->save())
        $this->redirect(array('home'));
      else {
        $this->render('form2',array('model'=>$model));
    } else { // this is the default, first time (step1)
      $model=new Model('new');
      $this->render('form1',array('model'=>$model));
    } ?>
    

    The forms would look something like this:

    Form1:

    <?php $form=$this->beginWidget('CActiveForm', array(
        'enableAjaxValidation'=>false,
        'id'=>'model-form',
        'stateful'=>true,
    ));
    <!-- form1 fields go here -->
    echo CHtml::submitButton("Cancel",array('name'=>'cancel');
    echo CHtml::submitButton("On to Step 2 >",array('name'=>'step2');
    $this->endWidget(); ?>
    

    Form 2:

    <?php $form=$this->beginWidget('CActiveForm', array(
        'enableAjaxValidation'=>false,
        'id'=>'model-form',
        'stateful'=>true,
    ));
    <!-- form2 fields go here -->
    echo CHtml::submitButton("Back to Step 1",array('name'=>'step1');
    echo CHtml::submitButton("Finish",array('name'=>'finish');
    $this->endWidget(); ?>
    

    I hope that is helpful!