In this recipe, we will separate our lengthy registration form into multiple pages.
Why do we need multipage forms? Because we don't want our visitors to scroll too much and want to enable them to fill out forms as quickly as possible. Multipage forms look much shorter than a single form and fit better without much change in design; most importantly, we can group the form fields in logical sections.
We'll separate our existing user registration form created in the Creating basic forms recipe, to multipage forms. The sections will be for personal information, address details, and contact information.
All code related to the form is written in a file named
_form
underprotected/views/user
.We are dividing the input fields into three sections, so create three separate files in the same folder with the names
_page1
,_page2
, and_page3
. Separate the code's respective files. Some sample lines are as follows:<?php $form=$this->beginWidget('CActiveForm', array( 'id'=>'user-form', 'enableAjaxValidation'=>false, 'stateful'=>true, )); ?> <div class="row"> <?php echo $form->labelEx($model,'first_name'); ?> <?php echo $form->textField($model,'first_name', array( 'size'=>50, 'maxlength'=>50 )); ?> <?php echo $form->error($model,'first_name'); ?> </div> ..... ..... <div class="row buttons"> <?php echo CHtml::submitButton('Next', array( 'name'=>'page2' )); ?> </div> <?php $this->endWidget(); ?> .... <div class="row buttons"> <?php echo CHtml::submitButton('back', array( 'name'=>'page1' )); ?> <?php echo CHtml::submitButton('Next', array( 'name'=>'page3' )); ?> </div> <div class="row buttons"> <?php echo CHtml::submitButton('Back', array( 'name'=>'page2' )); ?> <?php echo CHtml::submitButton('submit', array( 'name'=>'submit' )); ?> </div>
Now, in the
User
controller, change the code foractionCreate
as follows:public function actionCreate() { if(isset($_POST['page1'])) { $model = new User('page1'); $this->checkPageState($model, $_POST['User']); $view = '_page1'; } elseif(isset($_POST['page2'])) { $model = new User('page1'); $this->checkPageState($model, $_POST['User']); if($model->validate()) { $view = '_page2'; $model->scenario = 'page2'; } else { $view = '_page1'; } } .... $this->render($view, array('model'=>$model)); }
And add a function,
checkPageState()
, as follows:private function checkPageState(&$model, $data) { $model->attributes = $this->getPageState('page',array()); $model->attributes = $data; $this->setPageState('page', $model->attributes); }
Lastly, create scenarios in the model
User
to validate each page of the form separately. Add three arrays specifying all the required fields per page, as follows:return array( array('first_name, last_name, gender, dob', 'required', 'on'=>'page1' ), array('address_1, city, state, country', 'required', 'on'=>'page2' ), array('phone_number_1, email_1', 'required', 'on'=>'page3' ),
We have separated all our input fields into three forms. Each page contains an entire standalone form that accepts the input from the user, validates it from the server, and stores the data till we finally submit this form. The parameter stateful
passed to the CactiveForm
widget specifies the form needed to maintain the state across the pages. To do this, Yii creates a hidden field in each form with the name YII_PAGE_STATE
, as shown in the following screenshot:
All the data submitted on the first page is stored in this hidden field and passed to the server with the second page.
To read the data from this field we have used the method getPageState()
, and to write we have used setPageState()
. We have added a private method checkPageState()
to the User
controller, which reads the page state, if any, and assigns it to $model->attributes
, then assigns data from the current form using $model->attributes = $_POST['User']
, and finally overwrites the page state with freshly combined data.
When we click on Next on _page1
, we set the POST
variable page2
, which in turn executes the second block in the if-else
ladder in actionCreate
. In this recipe, we create an instance of the model User
with scenario
set to _page1
(as we need to validate the data received from _page1
). With a call to checkPageState()
, we check the current page state and add any new data from _page1
to the page state.
Then we check if the data filled is valid using $model->validate()
. If the model passes the validation we set, apply view
to _page2
and set $model->scenario
to _page2
, to mark the required fields on _page2
. If the validation fails, we set the view to _page1
with the validation errors set in the model.
At the end of the action, we render the selected view with the current state of the model. If any validation errors are set, they are listed on the same page; else, the next page will be rendered. The same steps are repeated for _page2
as well.
When the submit button is clicked on on _page3
, we retrieve the previous data from the page state using getPageState()
. Here we are not using checkPageState()
as now we do not need to store any data to the page state. We simply assign the data from _page3
to the model, and if the model validates we save all the data to the database with $model->save()
. After saving, we are redirected to actionView()
, where data from all three forms is listed as shown in the following screenshot: