Yii多页表单向导最佳实践

时间:2022-05-19 20:07:23

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:

我正在尝试用Yii构建一个多页面表单,但是对于PHP和Yii来说,这是一个全新的概念,我想知道什么是编写多页面表单的最佳实践。到目前为止,我计划做的是添加一个名为“step”的隐藏字段,其中包含用户在表单中的当前步骤(表单被分成3个步骤/页面)。考虑到这一点,这就是我计划如何处理用户点击控制器上一个/下一个按钮:

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?

这种方法有意义吗?还是说我偏离了基础,有更好更优化的方法在Yii/PHP中实现?

Thanks!

谢谢!

2 个解决方案

#1


35  

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:

有几种方法可以解决这个问题。我看到你在Yii论坛上发帖,所以我猜你也在那里搜索过,但如果你没有:

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.

我所做的是(仅针对一个简单的两步ActiveRecord表单)采取了一个操作,并基于按钮名将其划分为条件块,Yii将其发布到表单提交(注意:不使用ajax提交)。然后,根据按下的按钮,我呈现正确的表单,并在模型上设置正确的场景,以便进行验证。

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.

像您这样的隐藏“步骤”字段可以用于与检查submitButton名称相同的目的。我可能会将“步骤”保存到表单状态,而不是添加一个隐藏字段,但任何一个都可以。

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.

有些人使用有状态activeForm属性从向导中的单个步骤保存数据,或者您可以使用会话,甚至保存到临时数据库表。在下面的完全未测试的示例中,我使用有状态表单功能。

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

下面是我为ActiveRecord表单所做的一个例子。这是在“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:

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:

表格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!

我希望这能有所帮助!

#2


4  

Yii provides a feature called page states to implement things like a multi step / multi page form wizard.

Yii提供了一个名为page state的特性来实现多步骤/多页面表单向导。

Lets have a look at the Yii docs first:

让我们先看看Yii文档:

A page state is a variable that is persistent across POST requests of the same page. In order to use persistent page states, the form(s) must be stateful which are generated using {@link CHtml::statefulForm}.

页面状态是一个变量,它在同一页面的POST请求中持续存在。为了使用持久页状态,表单必须是有状态的,它是使用{@link CHtml: statefulForm}生成的。

So the forms of every step / page need to be stateful forms. To render a stateful form you just need to set the CActiveForm::stateful property to true when you start the ActiveForm-widget. Within your controller you can get and set your page states with CController::getPageState() or CController::setPageState().

因此,每一个步骤/页面的形式都需要有状态的形式。要呈现有状态表单,只需在启动ActiveForm-widget时将CActiveForm::stateful属性设置为true。在您的控制器中,您可以使用CController::getPageState()或CController::setPageState()来获取和设置页面状态。

So these are the basics that work quite well if the implementation of your multi page form wizard is made in the traditional style without AJAX requests.

因此,如果多页面表单向导的实现采用传统样式,而不需要AJAX请求,那么这些基本功能就能很好地工作。

If however you want to use AJAX calls to submit step data and display the next step, Yii's page states are not usable.

如果您想使用AJAX调用提交步骤数据并显示下一步,Yii的页面状态是不可用的。

Why? All the page states are transported through HTTP-POST within a hidden input field. The input field gets filled by Yii while the so called output processing. The output processing starts after the rendering and will replace parts of the output. So Yii's page states feature requires the output processing. AJAX responses on the other hand may become corrupted by it because the output processing may also add <link> or <script> tags at the beginning of the output to load required JS and CSS files.

为什么?所有页面状态都通过HTTP-POST在一个隐藏的输入字段中传输。输入字段由Yii填充,而所谓的输出处理。输出处理在呈现之后开始,并将替换输出的部分。因此Yii的页面状态特性需要输出处理。另一方面,AJAX响应可能会被它破坏,因为输出处理还可能在输出开始时添加

In the end I implemented my own version of stateful forms. I am able to get my stateful data with the static function call ActiveFormWidget::getRequestMultiStepData() every time I need it.

最后,我实现了自己的有状态表单。我可以使用静态函数调用ActiveFormWidget::getRequestMultiStepData()获取有状态数据。

Notice: There is one disadvantage in my implementation: all stateful data needs to be collected before the form widget will be initialized. But I never had a problem with it until now. However here is the code:

注意:在我的实现中有一个缺点:在初始化表单小部件之前,需要收集所有有状态数据。但直到现在我才对它产生了疑问。但是,下面是代码:

class ActiveFormWidget extends CActiveForm
{
    public static $inputNameMultiStepData = '_multiStepData';

    public $multiStep = false;
    public $multiStepData = array();

    public function init()
    {
        parent::init();

        # Hidden-Fields
        if ($this->multiStep) {
            echo Html::hiddenField(static::$inputNameMultiStepData, static::encodeInputData($this->multiStepData));
        }
    }

    /**
     * Gets all multi step data sent.
     * @return array|mixed
     */
    public static function getRequestMultiStepData()
    {
        return isset($_REQUEST[static::$inputNameMultiStepData]) ? static::decodeInputData($_REQUEST[static::$inputNameMultiStepData]) : array();
    }


    /**
     * Encodes form data like Yii does for stateful forms.
     * @param $data
     * @return string
     */
    public static function encodeInputData($data)
    {
        $data = Yii::app()->getSecurityManager()->hashData(serialize($data));

        return base64_encode($data);
    }

    /**
     * Decodes form data like Yii does for stateful forms.
     * @param $data
     * @return bool|mixed
     */
    public static function decodeInputData($data)
    {
        $data = base64_decode($data);
        $data = Yii::app()->getSecurityManager()->validateData($data);
        if ($data !== false) {
            return unserialize($data);
        } else {
            return false;
        }
    }
}

#1


35  

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:

有几种方法可以解决这个问题。我看到你在Yii论坛上发帖,所以我猜你也在那里搜索过,但如果你没有:

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.

我所做的是(仅针对一个简单的两步ActiveRecord表单)采取了一个操作,并基于按钮名将其划分为条件块,Yii将其发布到表单提交(注意:不使用ajax提交)。然后,根据按下的按钮,我呈现正确的表单,并在模型上设置正确的场景,以便进行验证。

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.

像您这样的隐藏“步骤”字段可以用于与检查submitButton名称相同的目的。我可能会将“步骤”保存到表单状态,而不是添加一个隐藏字段,但任何一个都可以。

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.

有些人使用有状态activeForm属性从向导中的单个步骤保存数据,或者您可以使用会话,甚至保存到临时数据库表。在下面的完全未测试的示例中,我使用有状态表单功能。

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

下面是我为ActiveRecord表单所做的一个例子。这是在“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:

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:

表格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!

我希望这能有所帮助!

#2


4  

Yii provides a feature called page states to implement things like a multi step / multi page form wizard.

Yii提供了一个名为page state的特性来实现多步骤/多页面表单向导。

Lets have a look at the Yii docs first:

让我们先看看Yii文档:

A page state is a variable that is persistent across POST requests of the same page. In order to use persistent page states, the form(s) must be stateful which are generated using {@link CHtml::statefulForm}.

页面状态是一个变量,它在同一页面的POST请求中持续存在。为了使用持久页状态,表单必须是有状态的,它是使用{@link CHtml: statefulForm}生成的。

So the forms of every step / page need to be stateful forms. To render a stateful form you just need to set the CActiveForm::stateful property to true when you start the ActiveForm-widget. Within your controller you can get and set your page states with CController::getPageState() or CController::setPageState().

因此,每一个步骤/页面的形式都需要有状态的形式。要呈现有状态表单,只需在启动ActiveForm-widget时将CActiveForm::stateful属性设置为true。在您的控制器中,您可以使用CController::getPageState()或CController::setPageState()来获取和设置页面状态。

So these are the basics that work quite well if the implementation of your multi page form wizard is made in the traditional style without AJAX requests.

因此,如果多页面表单向导的实现采用传统样式,而不需要AJAX请求,那么这些基本功能就能很好地工作。

If however you want to use AJAX calls to submit step data and display the next step, Yii's page states are not usable.

如果您想使用AJAX调用提交步骤数据并显示下一步,Yii的页面状态是不可用的。

Why? All the page states are transported through HTTP-POST within a hidden input field. The input field gets filled by Yii while the so called output processing. The output processing starts after the rendering and will replace parts of the output. So Yii's page states feature requires the output processing. AJAX responses on the other hand may become corrupted by it because the output processing may also add <link> or <script> tags at the beginning of the output to load required JS and CSS files.

为什么?所有页面状态都通过HTTP-POST在一个隐藏的输入字段中传输。输入字段由Yii填充,而所谓的输出处理。输出处理在呈现之后开始,并将替换输出的部分。因此Yii的页面状态特性需要输出处理。另一方面,AJAX响应可能会被它破坏,因为输出处理还可能在输出开始时添加

In the end I implemented my own version of stateful forms. I am able to get my stateful data with the static function call ActiveFormWidget::getRequestMultiStepData() every time I need it.

最后,我实现了自己的有状态表单。我可以使用静态函数调用ActiveFormWidget::getRequestMultiStepData()获取有状态数据。

Notice: There is one disadvantage in my implementation: all stateful data needs to be collected before the form widget will be initialized. But I never had a problem with it until now. However here is the code:

注意:在我的实现中有一个缺点:在初始化表单小部件之前,需要收集所有有状态数据。但直到现在我才对它产生了疑问。但是,下面是代码:

class ActiveFormWidget extends CActiveForm
{
    public static $inputNameMultiStepData = '_multiStepData';

    public $multiStep = false;
    public $multiStepData = array();

    public function init()
    {
        parent::init();

        # Hidden-Fields
        if ($this->multiStep) {
            echo Html::hiddenField(static::$inputNameMultiStepData, static::encodeInputData($this->multiStepData));
        }
    }

    /**
     * Gets all multi step data sent.
     * @return array|mixed
     */
    public static function getRequestMultiStepData()
    {
        return isset($_REQUEST[static::$inputNameMultiStepData]) ? static::decodeInputData($_REQUEST[static::$inputNameMultiStepData]) : array();
    }


    /**
     * Encodes form data like Yii does for stateful forms.
     * @param $data
     * @return string
     */
    public static function encodeInputData($data)
    {
        $data = Yii::app()->getSecurityManager()->hashData(serialize($data));

        return base64_encode($data);
    }

    /**
     * Decodes form data like Yii does for stateful forms.
     * @param $data
     * @return bool|mixed
     */
    public static function decodeInputData($data)
    {
        $data = base64_decode($data);
        $data = Yii::app()->getSecurityManager()->validateData($data);
        if ($data !== false) {
            return unserialize($data);
        } else {
            return false;
        }
    }
}