我正在尝试使用Yii构建一个多页面的表单,但我对PHP和Yii很新,我想知道编写多页面表单的最佳实践是什么。到目前为止,我打算做的是添加一个名为“step”的隐藏字段,其中包含用户在表单中的当前步骤(表单分为3个步骤/页)。因此,考虑到这一点,我打算如何处理用户点击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);
}
}
这种方法有意义吗?或者我离开基地,在Yii / PHP中有更好,更优化的方法吗?
谢谢!
答案 0 :(得分:35)
有几种方法可以解决这个问题。我看到你发布在Yii论坛上,所以我假设你也在那里搜索过,但万一你还没有:
我所做的是(仅用于一个简单的两步ActiveRecord表单)采取单一操作并将其分为基于按钮名称的条件块,Yii POST在表单上提交(注意:不起作用)用ajax提交)。然后,根据点击的按钮,我会渲染正确的表单并在我的模型上设置正确的场景以进行验证。
像您一样隐藏的“步骤”字段可以起到与检查submitButton名称相同的目的。我可能会将“步骤”保存到表单状态中,而不是添加隐藏字段,但是要么一切都会好的。
有些人使用有状态activeForm属性从向导中的单个步骤保存数据,或者您可以使用会话,甚至可以保存到临时数据库表。在我下面完全未经测试的示例中,我使用的是有状态表单功能。
这是我基本上为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));
} ?>
表单看起来像这样:
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(); ?>
表格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(); ?>
我希望这有用!
答案 1 :(得分:4)
Yii提供了一种称为页面状态的功能,用于实现多步/多页表单向导等功能。
让我们先看看Yii文档:
页面状态是一个在同一页面的POST请求中保持不变的变量。为了使用持久页面状态,表单必须是有状态的,使用{@link CHtml :: statefulForm}生成。
所以每个步骤/页面的形式都需要是有状态的形式。要呈现有状态表单,只需在启动ActiveForm-widget时将CActiveForm::stateful
属性设置为true
即可。
在您的控制器中,您可以使用CController::getPageState()
或CController::setPageState()
来获取和设置页面状态。
因此,如果您的多页表单向导的实现是在没有AJAX请求的传统样式下进行的,那么这些基础工作都非常有用。
但是,如果您想使用AJAX调用提交步骤数据并显示下一步,则Yii的页面状态不可用。
为什么呢?所有页面状态都通过HTTP-POST在隐藏的输入字段中传输。输入字段由Yii填充,而所谓的输出处理。输出处理在>>渲染后开始,并将替换部分输出。所以 Yii的页面状态功能需要输出处理。另一方面,AJAX响应可能会被它破坏,因为输出处理还可能在输出的开头添加<link>
或<script>
标记以加载所需的JS和CSS文件。
最后,我实现了自己的有状态表单版本。每次需要时,我都可以通过静态函数调用ActiveFormWidget::getRequestMultiStepData()
获取有状态数据。
注意:我的实现有一个缺点:需要在初始化表单窗口小部件之前收集所有有状态数据。但直到现在我才一直遇到问题。不过这是代码:
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;
}
}
}