2010-08-23 15 views
23

Estoy tratando de crear un formulario de varias páginas con Yii, pero soy bastante nuevo en PHP y Yii y me pregunto cuál es la mejor práctica para escribir un formulario de varias páginas. Hasta ahora, lo que planeo hacer es agregar un campo oculto llamado 'paso' que contiene el paso actual en el que está el usuario en el formulario (el formulario está dividido en 3 pasos/páginas). Así que con esto en mente, así es como tengo la intención de manejar el usuario haga clic en los botones anterior/siguiente en el controlador:Yii multi página formulario asistente de mejores prácticas

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

hace esto enfoque tiene sentido? ¿O estoy fuera de la base y hay una forma mucho mejor y más optimizada de hacerlo en Yii/PHP?

Gracias!

+4

Tome un vistazo a 'chtml :: statefulForm' y una [hilo en el foro de Yu] (http://www.yiiframework.com/forum/index.php?/topic/ 8413-wizard-forms /) –

Respuesta

35

Hay un par de formas de abordar esto. Veo que ha escrito en el foro Yu, así que supongo que has buscado por allí también, pero en caso de que no tiene:

Lo que he hecho es (justo para un simple formulario ActiveRecord de 2 pasos) tomó una sola acción y la dividió en bloques condicionales basados ​​en el nombre del botón, que Yii POSTs en un formulario envía (nota: no funciona con ajax submits). Luego, dependiendo del botón al que se presionó, presento la forma correcta y establezco el escenario correcto en mi modelo para fines de validación.

Un campo oculto de "paso" como el que tiene podría servir para el mismo propósito que la comprobación del nombre de submitButton. Tal vez guarde el "paso" en el estado de formulario en lugar de agregar un campo oculto, pero cualquiera estaría bien.

Algunas personas usan el atributo stateful activeForm para guardar los datos de un solo paso en el asistente, o puede usar la sesión, o incluso guardar en una tabla de base de datos temporal. En mi ejemplo completamente no probado, estoy usando la funcionalidad de formulario con estado.

Aquí hay un ejemplo de lo que básicamente hice para un formulario ActiveRecord. Esto va en el "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)); 
} ?> 

Las formas se vería algo como esto:

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

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

espero que sea ¡servicial!

+1

¡Gracias fue muy útil! Me gusta bastante su enfoque de usar los botones de envío para determinar qué paso mostrar. También veré el enfoque ActiveForm, que suena interesante. –

+0

Gracias asignar. ¿Dónde puedo aprender más cosas así? Como desarrollo profesional con yii framework. – FDisk

+1

con respecto a "nota: no funciona con ajax submits", esto es cierto, especialmente en jquery, pero solo puede agregar el nombre/valor del botón al resultado de form.serialize y funcionará –

4

Yii proporciona una función denominada estados de página para implementar cosas como un asistente de formulario de múltiples pasos/múltiples páginas.

Vamos a echar un vistazo a la documentación Yii primeros:

Un estado de la página es una variable que permanece después de las peticiones POST de la misma página. Para utilizar estados de página persistentes, los formularios deben ser con estado, que se generan mediante {@link CHtml :: statefulForm}.

Por lo tanto, las formas de cada paso/página deben ser formas con estado.Para generar una forma con estado, solo necesita establecer la propiedad CActiveForm::stateful en true cuando inicia el widget ActiveForm. Dentro de su controlador puede obtener y establecer los estados de su página con CController::getPageState() o CController::setPageState().

Estos son los conceptos básicos que funcionan bastante bien si la implementación de su asistente de formularios de varias páginas se realiza en el estilo tradicional sin solicitudes AJAX.

Sin embargo, si desea utilizar las llamadas AJAX para enviar los datos del paso y mostrar el siguiente paso, los estados de la página Yii no se pueden utilizar.

¿Por qué? Todos los estados de la página se transportan a través de HTTP-POST dentro de un campo de entrada oculto. El campo de entrada se llena con Yii mientras se procesa la salida. El procesamiento de salida inicia después de la representación y reemplazará partes de la salida. Por lo tanto, La función de estados de página de Yii requiere el procesamiento de salida. Por otro lado, las respuestas AJAX pueden corromperse porque el procesamiento de salida también puede agregar etiquetas <link> o <script> al comienzo de la salida para cargar los archivos JS y CSS necesarios.

Al final implementé mi propia versión de formularios con estado. Puedo obtener mis datos de estado con la función estática llamada ActiveFormWidget::getRequestMultiStepData() cada vez que lo necesito.

Aviso: Hay una desventaja en mi implementación: se deben recopilar todos los datos con estado antes de inicializar el widget de formulario. Pero nunca tuve un problema con eso hasta ahora. Sin embargo aquí está el código:

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