Yii 1.1 - 使用验证创建多步骤表单

时间:2014-01-06 22:01:21

标签: yii

我基本上尝试使用Yii中的CActiveForm类创建一个多步骤表单。我的想法是希望使用内置功能以最简单的方式实现这一点。我的要求如下:

  • 多步骤ONE PAGE表单(使用显示/隐藏jQuery的DIV)
  • 每个步骤的AJAX验证(仅验证步骤特定的属性)
  • 验证必须使用validateOnChange()validateOnSubmit()方法

这是我迄今为止开发的半工作解决方案:

查看:

<div class="form">

        <?php $form = $this->beginWidget('CActiveForm', array(
            'id'=>'listing-form',
            'enableClientValidation'=>false,
            'enableAjaxValidation'=>true,
            'clientOptions'=>array(
                'validateOnChange'=>true,
                'validateOnSubmit'=>true,
                'afterValidate'=>'js:validateListing',
            ),
        )); ?>

        <?php echo $form->errorSummary($model); ?>

        <div class="step" id="step-1">
            // model input fields
            <?php echo CHtml::submitButton('Next Step', array('name'=>'step1')); ?>
        </div>

        <div class="step" id="step-2" style="display: none;">
            // model input fields
            <?php echo CHtml::submitButton('Next Step', array('name'=>'step2')); ?>
        </div>

        <div class="step" id="step-3" style="display: none;">
            // model input fields
            <?php echo CHtml::submitButton('Submit', array('name'=>'step3')); ?>
        </div>

        <?php $this->endWidget(); ?>

</div>

JavaScript的:

function validateListing(form, data, hasError)
{
        if(hasError)
        {
            // display JS flash message
        }
        else
        {
            if($('#step-1').css('display') != 'none')
            {
                $('#step-1').hide();
                $('#step-2').show();
            }
            else if($('#step-2').css('display') != 'none')
            {
                $('#step-2').hide();
                $('#step-3').show();
            }
            else if($('#step-3').css('display') != 'none')
            {
                return true; // trigger default form submit
            }
        }
}

控制器:

public function actionCreate()
{
        $model = new Listing;

        // step 1 ajax validation
        if(isset($_POST['step1']))
        {
            $attributes = array('name', 'address1', 'etc');

            $this->performAjaxValidation($model, $attributes);
        }

        // step 2 ajax validation
        if(isset($_POST['step2']))
        {
            $attributes = array('category', 'type', 'etc');

            $this->performAjaxValidation($model, $attributes);
        }

        // step 3 ajax validation
        if(isset($_POST['step3']))
        {
            $attributes = array('details', 'source', 'etc');

            $this->performAjaxValidation($model, $attributes);
        }

        // process regular POST
        if(isset($_POST['Listing']))
        {
            $model->attributes = $_POST['Listing'];

            if($model->validate()) // validate all attributes again to be sure
            {
               // perform save actions, redirect, etc
            }
        }

        $this->render('create', array(
            'model'=>$model,
        ));
}

protected function performAjaxValidation($model, $attributes=null)
{
    if(isset($_POST['ajax']) && $_POST['ajax']==='listing-form')
    {
        echo CActiveForm::validate($model, $attributes);
        Yii::app()->end();
    }
}

总结一下。基本上我所拥有的是一个带有3个提交按钮的表单(每个步骤一个)。在我的控制器中,我检查按下了哪个提交按钮,然后对特定于该步骤的属性运行AJAX验证。

我使用自定义afterValidate()功能来显示/隐藏提交时的步骤。在步骤3,触发默认表单提交,将所有表单属性发布到控制器。

这很有效,但它不适用于validateOnChange()(因为提交按钮没有发布)。此外,我想知道这是否真的是最好的方法,或者是否有人知道更好的方法?

感谢。

4 个答案:

答案 0 :(得分:3)

我建议使用方案打开和关闭相应的规则。根据发送到控制器的内容调整模型方案。

注意:这也可能是使用CFormModel而不是CActiveRecord的好地方,具体取决于表单中的内容。

修改:您可以为每个div部分添加一个隐藏字段,其中包含有关您所处步骤的信息吗?似乎应该工作而不是提交按钮。

答案 1 :(得分:1)

选项1 当您没有收到按钮时,为什么不验证整个表单,为什么只需要验证特定属性? Yii将验证整个模型,发送回所有错误,但只有特定错误将由活动表单显示,因为它已经如何工作。

选项2 你可以有3个表格(不像现在这样1个),每个步骤1个。同时为每个步骤创建3个方案1。

每个表单都有一个隐藏字段,该字段随表单一起发布,它实际上可以是方案名称,只是在它进入时验证它。使用此隐藏字段验证模型以设置您所处的场景。

您可以在成功提交表单时缓存模型上的部件,最后您拥有完整的模型。

答案 2 :(得分:1)

您始终可以进行自定义验证,并且不会破坏正常的表单验证

在您的模型中

private $step1 = false;
private $step2 = false;
private $all_ok = false;

protected function beforeValidate()
{
    if(!empty($this->attr1) && $this->attr2) // if the fields you are looking for are filled, let it go to next
    {
        $this->step1 = true;
    }

    if($this->step1)
    {
        ... some more validation
        $this->step2 = true;
    }

    if($this->step2)
    {
        ... if all your logic meets
        $this->all_ok = true;
    }

     // if all fields that your looking for are filled, let parent validate them all
     // if they don't go with their original rules, parent will notify
    if($this->all_ok)
        return parent::beforeValidate();

    $this->addError($this->tableSchema->primaryKey, 'please fillout the form correctly');
    return false;
}

答案 3 :(得分:1)

我认为最好为每个验证步骤创建特定的类,并使用带有规则的方案。以下是小例子。

//protected/extensions/validators
class StepOneMyModelValidator extends CValidator
{

    /**
     * @inheritdoc
     */
    protected function validateAttribute($object, $attribute)
    {
        /* @var $object YourModel */
        // validation step 1 here.
        if (exist_problems) {
            $object->addError($attribute, 'step1 is failed');
        }
...

为验证创建其他类(步骤)......

// in your model
    public function rules()
    {
        return array(
           array('attr', 'ext.validators.StepOneMyModelValidator', 'on' => 'step1'),
        ...

如何在控制器中使用:

$model = new Listing();
$steps = array('step1', 'step2', /* etc... */);
foreach($_POST as $key => $val) {
if (in_array($key, $steps)) {
       $model->setScenario($key);
       break;
   }
}
$model->validate();
echo '<pre>';
print_r($model->getErrors());
echo '</pre>';
die();

或者我们可以验证一个验证器中的所有步骤。