Herramientas de usuario

Herramientas del sitio


cursos:yii2:views:grid

GridView

Este widget permite mostrar datos en forma de tabla, con posibilidad de filtrar y ordenar por cualquier columna. La propiedad “columns” especifica la lista de columnas del Grid, que pueden ser:

  1. SerialColumn: contador secuencial de filas
  2. Atributo: cualquier atributo del modelo
  3. Raw: Código HTMl
  4. ActionColumn: Botones de acciones, con enlaces

Para especificar atributos, se puede hacer de las siguientes formas:

GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            'fecha',
            'usuario.nombre',
            'aprobada',
            ['attribute'=>'categorias_id',
                'value'=>function($data){
                    return $data->categoria->nombre;
                },
            ],
 
            ['class' => 'yii\grid\ActionColumn'],
        ],

Cuando en una columna queremos incluir código HTML para formatear la entrada, poner colores, incluir imágenes o lo que sea, añadiremos 'format'⇒'raw' en la especificación de la columna:

            ['attribute'=>'aprobada',
              'format'=>'raw',
                'value'=>function($data){
                    return "<span class='estado$data->aprobada'>$data->aprobadaText</span>";
                },
            ],

Mostrar un array de objetos ActiveRecord en lugar de un ActiveDataProvider

Habitualmente el origen de datos de un Grid es un ActiveDataProvider, que gestiona el paginado, filtrado y ordenación de datos, pero cuando el número de objetos a mostrar no es grande, también podemos utilizar un ArrayDataProvider, creado a partir de array de objetos ActiveRecord (de una relación HAS_MANY o mediante un find()). Ejemplo: si tenemos un usuario con una relación a los roles que tiene asignados, en la vista view podemos mostrar

...DetailView de datos del usuario
 
<h3>Roles del usuario</h3>
<?= GridView::widget([
        'dataProvider' => new ArrayDataProvider($model->roles),
        ....
]);

En este caso, las etiquetas de los atributos no se heredan del modelo, por lo que se deben especificar en columns con la sintáxis atributo::etiqueta

También se puede crear un ActiveDataProvider a partir de una relación asociada a un modelo, o a partir de un ActiveQuery:

...DetailView de datos de un titulo
 
<h3>Comentarios</h3>
<?= GridView::widget([
        'dataProvider' => new ActiveDataProvider(['query'=>$model->getComentarios()->orderBy('fecha desc')],
        ....
]);

Cambiar el número de filas que se visualizan

El tamaño de página se define en el dataprovider mediante pageSize de la propiedad pagination.:

$dataProvider->pagination=array('pageSize'=>30);
o en el momento de la creación:
$dataProvider=new ActiveDataProvider(['query'=>...,'pagination'=>['pageSize'=>30]],
        ....

Convertir cada fila en un enlace a una acción

'rowOptions' => function ($model, $key, $index, $grid) {
                 return [
                    'style' => "cursor: pointer",
                    'onclick' => 'location.href="'
                     . Yii::$app->urlManager->createUrl(['usuarios/update','id'=>$key]).'"'
 
                ];
 

Personalizar los botones de acciones

Con la propiedad template de ActionColumn definimos los botones que queremos mostrar: Para mostrar únicamente las acciones view y update:

  ...
  ['class'=>'yii\grid\ActionColumn',
	'template'=>'{view} {update}'
  ],

Podemos añadir acciones nuevas, definiéndolas en la propiedad buttons:

  ...
  'columns'=>[
        ...
	['class'=>'yii\grid\ActionColumn',
	'template'=>'{view} {update} {email}',
	'buttons'=>[
		'email'=>function ($url, $model) {  
                                if($model->estado=='B') return ''; //No se muestra el botón
				$url=Url::toRoute(['mail','id'=>$model->id]);
				return Html::a('<span class="glyphicon glyphicon-envelope"></span>', $url);                                
			},	
	]]
  ],

Para cada botón definido en buttons, el closure devuelve el código HTML del botón, incluyendo enlace, texto o icono. Si el botón ha de ocultarse dependiendo de alguna condición, basta con devolver “” en ese caso.

Cambiar el controlador destino

El controlador al que apuntan las acciones del GridView es el mismo de la acción actual. En una vista donde el GridView muestra datos relacionados con el modelo principal (comentarios de un titulo, por ejemplo), nos llevaría a hacer enlaces incorrectos, por lo que hay cambiar la propiedad 'controller' del Grid al que deseemos:

['class' => 'yii\grid\ActionColumn',
   ...
   'controller'=>'comentarios'

Si queremos todavía más flexibilidad para crear las URL a las que apuntan los botones, podemos utilizar la propiedad urlCReator de ActionColumn:

['class' => 'yii\grid\ActionColumn',
   ...
   'urlCreator'=>function ($action,$model, $key,  $index) {
                    return Url::toRoute(['comentarios/'.$action,'id'=>$model->id]);
                } 
 ...                

Mostrar fechas

Si hemos configurado el componente formatter en config/main.php correctamente, los atributos date o datetime se pueden presentar en un grid, utilizando la opción “format”:

GridView::widget([
	'model' => $model,
	'attributes' => [
               ...
		['attribute'=>'fecha_alta','format'=>'date'],
	],
]);

Referenciar a datos relacionados

Utilizaremos la sintáxis: relación.dato

'columns'=>[
	...
	'usuario.nombre',  //Escribe el nombre del usuario, si usuario es una relación hasOne del modelo
  	'categoria.descripcion',
  ]

Si queremos personalizar la etiqueta de la cabecera para estas columnas relacionadas, lo haremos de la forma

[['attribute'=>'categoria.descripcion','label'=>'Categoría']]

Si queremos filtrar por los valores posibles de categoría, podemos definir un método lookup() en Categorias (Ver Crear y configurar modelos) y utilizarlo aquí:

'columns'=>[
	...
	['attribute'=>'categorias_id',
         'filter'=>Categorias::lookup(),
  	 'value'=>function($data) {
            return $data->categoria->descripcion;
            }
        ],
        ...
  ]

Incluir casillas de selección múltiple para ejecutar una acción

Incluiremos en el grid una columna de tipo CheckBoxColumn:

   'columns'=>[
      ....
	['class' => CheckboxColumn::className(),'name'=>'idselec',
			'checkboxOptions' => function ($model, $key, $index, $column) {
						return ['value' => $model->id];
					     }],
     ...

Si el grid lo incluimos dentro de una form, cuando se envíe ésta, el controlador recibirá en $_POST['idselec'] un array con las id de las filas seleccionadas. Desde la vista podemos acceder fácilmente al array de filas seleccionadas mediante var keys = $('#grid').yiiGridView('getSelectedRows'); , donde #grid es la id del grid deseado.

  <?=Html::beginForm(['entradas/estado'],'post');?>
 
    <div class=row>
        <div class=col-md-4>
            <?=Html::dropDownList('estado','',
               ['A'=>'Aceptadas','R'=>'Rechazadas'], // o mejor: Entradas::$estadoOptions, si está definida
               ['class'=>'form-control','prompt'=>'Selecciona...'])?>
        </div>        
        <div class=col-md-2>
            <?=Html::submitButton('Cambiar', ['class' => 'btn btn-info',]);?>
        </div>
    </div>
 
 
  echo GridView::widget(....)
  ....
  ));
 
 
<?= Html::endForm();?>

Fijate que al abrir el form, especificamos la acción que queremos ejecutar, que será distinta de la actual (index normalmente, si es un grid)

En el controlador, procesaremos las filas seleccionadas mediante find:

/** Cambia de estado un conjunto de entradas
 * 
 * @param char $estado POST
 * @param array $idselec POST
 */
public function actionEstado(){
        $estado=Yii::$app->request->post('accion');
        $idselec=(array)Yii::$app->request->post('idselec');
 
 
	foreach(Entradas::findAll($idselec) as $entrada){
		$entrada->estado=$estado;
		if(!$entrada->save()) {
			//Tratar el error, añadiendo mensajes a una lista, o lo que se desee
		} 		
	}
	$this->redirect(['index']);
}

Filtro de datos

Para que aparezca una línea con campos para poder filtrar datos en el Grid, debemos asignar la propiedad filterModel del Grid, asignándole un modelo de tipo Search (lo genera gii).

Por otra parte, cuando hay atributos para filtrar que tiene valores concretos (ej: estado: 'P'⇒'Pend Aceptar','A'⇒'Aceptado'), o está relacionado con otro modelo (categoría, por ejemplo), podemos hacer que el grid muestre un filtro con desplegable:

En el controlador:

public function actionIndex(){
  $usuariosSearch = new app\models\UsuariosSearch();
  // Podemos añadir filtros: $usuariosSearch->estado="A";
  $provider = $usuariosSearch->search(Yii::$app->request->queryParams);
  return $this->render('index',['usuariosSearch'=>$usuariosSearch,'provider'=>$provider]);
}

En la vista

echo GridView::widget([
      'dataProvider' => $provider,
      'filterModel'=>$usuariosSearch, //Habilita la linea de filtros
      'columns' => [
		['attribute'=>'estado',
		  'value'=>function ($model,$attribute) { 
			return $model->estadoText;
			},
		  'filter'=>app\models\Usuarios::$estadoOptions //Array de valor=>descri
 
		],

estadoText y estadoOptions son dos propiedades definidas en el modelo, que me devuelven la descripción como texto del dato estado, y el array de valores posibles de estado. (Ver Crear y configurar Modelos) En el caso de que el atributo esté relacionado con otro modelo, podemos utilizar el método lookup() para los valores de filter (ver Crear y configurar Modelos)

		['attribute'=>'categorias_id',
		  'value'=>function ($model,$attribute) { 
			return $model->categoria->descripcion;
			},
		  'filter'=>Categorias::lookup()
 
		],

CUIDADO: No se debe utilizar este sistema si el modelo relacionado puede tener muchos valores, ya que se cargarían todos en el desplegable y es poco óptimo. En ese caso, se ha de utilizar un autocomplete

Filtrar y ordenar por un dato de un modelo relacionado

Es muy habitual que mostremos en el grid datos que no pertenecen al modelo, sino a uno relacionado. Por ejemplo, en los comentarios de un título podemos mostrar el nombre del usuario que ha hecho el comentario. En este caso, ya no se muestra la opción de ordenar ni filtrar por este dato, ya que no pertenece al modelo. La forma de solucionarlo es incluir el dato por el que filtramos (nombre del usuario) en ComentariosSearch, forzar a que el acceso SQL haga un join con Usuarios, y aplicar el filtro del nombre a la consulta:

En ComentariosSearch.php

class ComentariosSearch extends Comentarios {
     public $usuario_nombre; //Se podría llamar simplemente 'nombre', pero así evitamos posibles ambigüedades
 
     public function rules(){
          ...
          [['xxx', 'usuario_nombre'], 'safe'], //Lo incluimos en rules-safe para que lo asigne desde el formulario
     }
 
     public function search($params) {
        $query = Comentarios::find()->innerJoinWith('usuario', true); //usuario es el nombre de la relación
 
	$dataProvider->sort->attributes['usuario_nombre'] = [
		'asc' => ['usuarios.nombre' => SORT_ASC],
		'desc' => ['usuarios.nombre' => SORT_DESC],
	];
        ...resto del código
        //Añadimos el filtro por nombre de usuario:
        $query->andFilterWhere( ['like','usuarios.nombre',$this->usuario_nombre]);
        return $dataProvider;
     }

En la vista:

echo GridView::widget([
    ...
    'columns' => [
         ['attribute' => 'usuario_nombre', 
		'label' => 'Usuario', 
		'format' => 'raw',
		'value' => function ($data){ return $data->usuario->nombre; }
	 ],
    ...
]);