Herramientas de usuario

Herramientas del sitio


cursos:yii2:views:form

Formularios: ActiveForm

Para definir un formulario en una vista, utilizamos ActiveForm::begin() y AcriveForm::end(). Equivale a <form> </form>, pero además genera un control que evita el hackeo por referencias cruzadas CSRF.

$form = ActiveForm::begin();
 
.... Campos ----
 
ActiveForm::end() 

Por defecto, el formulario irá a la misma controlador/action en el que estemos. Si queremos enviarlo a otra acción, lo especificaremos en el begin:

  ActiveForm::begin(['action'=>['/entradas/cambiaestado']);

Si queremos especificar otros atributos de form, como por ejemplo multipart/form-data que se utiliza cuando subimos fichero, o desactivar el autocomplete del navegador, que interfiere con nuestros autcomplete y fechas, lo pondremos en la propiedad “options”

  ActiveForm::begin(['options'=>[
  'enctype'=>'multipart/form-data',
  'autocomplete'=>'off'
  ]]);

Desplegables: Atributos con un conjunto de valores permitidos

Cuando un atributo solo admite un conjunto de valores, lo típico será mostrar un desplegable. En el caso de atributos con valores fijos (ver estado en Crear/configurar Modelos), utilizaremos como opciones la propiedad ATRIBUTOOptions:

use app\models\Usuarios;
...
echo $form->field($model, 'estado')->dropDownList(Usuarios::$estadoOptions,['prompt'=>'Selecciona...']);

Atributos relacionados con otro modelo

Si tenemos un atributo cuyos valores están relacionados con otro modelo mediante una relación hasOne, lo típico será mostrar un desplegable con los valores posibles. Para ello, cargaremos los valores con find()→all(), que devuelve un array de objetos, y lo convertiremos a un array clave⇒descripción mediante la función ArrayHelper::map(), que es lo que necesita el input dropdownList

//Utilizamos asArray para que sea más óptimo el acceso, al devolver una lista de arrays 
$options=ArrayHelper::map(Usuarios::find()->asArray()->all(),'id','nombre');
echo $form->field($model, 'usuarios_id')->dropDownList($options,['prompt'=>'Seleccione...']);

Campos de fecha con selector de calendario

Consulta Trabajar con fechas para configurar la conversión de formatos de fecha en las vistas. Una vez hecho, en un formulario lo haremos de la forma:

<?= $form->field($model, 'fecha')->widget(DateControl::classname(),['pluginOptions' => ['autoclose'=>true]);  ?>

Autocomplete

Cuando el modelo relacionado con un atributo tiene muchos valores posibles, un desplegable no es adecuado por cuestiones de rendimiento. Podemos utilizar un autocomplete. El widget Typehead de Kartik funciona muy bien y tiene muchas opciones. Permite utilizar datos locales o pedirlos vía Ajax al servidor. Esto último será lo habitual, y como lo que necesitamos es que nos envíe con el formulario la id del modelo relacionado y no su descripción, tenemos que usar un campo oculto que se actualiza cuando seleccionamos una entrada.

Si tenemos el modelo Libros y queremos asociarle un autor (autores_id) con un Typehead:

use kartik\typeahead\Typeahead;
 
$value=$model->autor ? $model->autor->nombre : '';  //Valor inicial que mostramos
echo Html::activeHiddenInput($model,'autores_id',['id'=>'autor-hidden'])
echo Typeahead::widget([
	'name' => autor_th',
	'value'=>$value,
	'options' => array_merge(['placeholder' => 'Escribe el nombre...'],$options),
	'pluginOptions' => ['highlight'=>true],
	'pluginEvents' => [
		"typeahead:select" => 'function(ev, resp) {
			$("#autor-hidden").val(resp.id);
			$("#autor-hidden").change();
		}',
	],
	'dataset' => [
		[
		'datumTokenizer' => "Bloodhound.tokenizers.obj.whitespace('id')",
			'display' => 'label',
			'remote' => [
				'url' => Url::to(['autores/lookup']) . '&term=%QUERY',
				'wildcard' => '%QUERY'
			]
		]
	]
]);

Es más práctico crear nuestro propio “Widget” autocomplete y así podemos utilizarlo en los formularios de una forma más sencilla: Creamos el fichero app\components\THtml.php

<?php
namespace app\components;
use Yii;
use yii\helpers\Html;
use kartik\typeahead\Typeahead;
use yii\helpers\Url;
 
class THtml {
	public static function autocomplete($model,$atributo,$lookupaction,$relation,$options=[]){
		if(!isset($options['id']))
			$id=Html::getInputId($model, $atributo);
		else
			$id=$options['id'];
		echo "<div class='form-group ".($model->getErrors($atributo)?'has-error':'')."'>";
		echo Html::activeHiddenInput($model,$atributo,['id'=>$id.'-hidden']);
		echo Html::activeLabel($model,$atributo);
		echo Typeahead::widget([
			'name' => $atributo.'_a',
			'value'=>$model->$relation,
			'options' => array_merge(['placeholder' => 'Nombre, apellidos, Código...'],$options),
			'pluginOptions' => ['highlight'=>true],
			'pluginEvents' => [
				"typeahead:select" => 'function(ev, resp) {
					$("#'.$id.'-hidden").val(resp.id);
					$("#'.$id.'-hidden").change();
				}',
			],
			'dataset' => [
				[
					'datumTokenizer' => "Bloodhound.tokenizers.obj.whitespace('id')",
					'display' => 'label',
				  //  'prefetch' => $baseUrl . '/samples/countries.json',
					'remote' => [
						'url' => Url::to(array_merge($lookupaction,['term'=>'XQY'])),
						'wildcard' => 'XQY'
					]
				]
			]
		]);
		echo Html::error($model,$atributo);
		echo '</div>';
	}
}

Así, en la vista del formulario pondremos:

use app\components\THtml;
<?= THtml::autocomplete($model,'autores_id',['/autores/lookup'],'autor');?>

Solamente nos falta crear la acción autores/lookup que se encarga de devolver en formato JSON los autores que cumplen el criterio de búsqueda: En AutoresController.php:

public function actionLookup($term) {
   $results = [];
   foreach (Autores::find()->andwhere("(nombre like :q )", [':q' => '%' . $term . '%'])->asArray()->all() as $model) {
        $results[] = [
           'id' => $model['id'],
           'label' => $model['nombre'],
        ];
   }
   return \yii\helpers\Json::encode($results);
 
}

Una alternativa a THtml más “elegante” es crear una clase derivada de ActiveForm y añadirle el método autocomplete (y otros para fechas, editores o lo que queramos) . Así, en la vistas:

class TActiveForm extends ActiveForm {
 
  public function autocomplete($model,$atributo,$lookupaction,$relation='',$options=[]){
  ...
  }
}