web-dev-qa-db-ja.com

Yiiマルチページフォームウィザードのベストプラクティス

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);
  }
 }

このアプローチは理にかなっていますか?それとも私はベースから離れており、Yii/PHPでこれを行うためのはるかに優れたより最適化された方法がありますか?

ありがとう!

24
Stinky Tofu

これにアプローチする方法はいくつかあります。あなたがYiiフォーラムに投稿しているのを見たので、あなたもそこで検索したと思いますが、まだ検索していない場合は:

私が行ったことは(単純な2ステップのActiveRecordフォームの場合)、単一のアクションを実行し、ボタン名に基づいて条件付きブロックに分割しました。これは、フォーム送信でのPOST Yii POST(注:ajax送信では機能しません)次に、どのボタンが押されたかに応じて、正しいフォームをレンダリングし、検証のためにモデルに正しいシナリオを設定します。

あなたが持っているような隠された「ステップ」フィールドは、submitButton名をチェックするのと同じ目的を果たすことができます。隠しフィールドを追加する代わりに、「ステップ」をフォーム状態に保存することもできますが、どちらでもかまいません。

一部の人々は、ステートフルactiveForm属性を使用して、ウィザードの1つのステップからデータを保存します。または、セッションを使用したり、一時データベーステーブルに保存したりすることもできます。以下の完全にテストされていない例では、ステートフルフォーム機能を使用しています。

これは私が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));
} ?>

フォームは次のようになります。

フォーム1:

<?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(); ?>

お役に立てば幸いです。

36
thaddeusmt

Yiiは、マルチステップ/マルチページフォームウィザードなどを実装するためのページ状態と呼ばれる機能を提供します。

最初にYiiドキュメントを見てみましょう:

ページ状態は、同じページのPOSTリクエスト間で永続的な変数です。永続的なページ状態を使用するには、{@ linkCHtmlを使用して生成されるフォームがステートフルである必要があります。 :: statefulForm}。

したがって、すべてのステップ/ページのフォームはステートフルフォームである必要があります。ステートフルフォームをレンダリングするには、ActiveFormウィジェットを起動するときに_CActiveForm::stateful_プロパティをtrueに設定する必要があります。コントローラ内では、CController::getPageState()またはCController::setPageState()を使用してページの状態を取得および設定できます。

したがって、これらは、マルチページフォームウィザードの実装がAJAXリクエストなしで従来のスタイルで行われる場合に、非常にうまく機能する基本です。

ただし、AJAX呼び出しを使用してステップデータを送信し、次のステップを表示する場合、Yiiのページ状態は使用できません。

どうして?すべてのページ状態は、非表示の入力フィールド内でHTTP-POSTを介して転送されます。入力フィールドは、いわゆる出力処理中にYiiで埋められます。出力処理は、レンダリング後に開始し、出力の一部を置き換えるため、Yiiのページ状態機能には出力処理が必要です。AJAX応答は、出力処理では、出力の先頭に_<link>_または_<script>_タグを追加して、必要なJSおよびCSSファイルをロードする可能性があるため、これによって破損します。

結局、私はステートフルフォームの独自のバージョンを実装しました。静的関数呼び出しActiveFormWidget::getRequestMultiStepData()を使用して、必要なたびにステートフルデータを取得できます。

注意:私の実装には1つの欠点があります。フォームウィジェットを初期化する前に、すべてのステートフルデータを収集する必要があります。しかし、今まで問題はありませんでした。ただし、コードは次のとおりです。

_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;
        }
    }
}
_
4
SaV