CGridView

De Yii Framework en Español
Saltar a: navegación, buscar

Mejorando el CGridView

por: Christian Salazar (bluyell, @salazachris74, christiansalazarh@gmail.com)

Este conjunto de extensiones y documentación permitirán que le des un mejor comportamiento a un CGridView de una manera sencilla. Digo sencilla porque hay muchisimas extensiones, algunas muy buenas otras malas pero no hay un punto único donde sujetarse para comenzar. Trato de hacer de este espacio un punto de referencia.

Cómo hacer para que un CGridView mantenga la selección entre cambios de página

La extensión ekeepselection te ayuda en esta tarea, luego de agregarla tu CGridView no perderá la selección que hayas hecho tras cambiar de página, es mas, exitosamente te dará todos los items seleccionados en todas las páginas activas. Para consultar la selección hecha recurrirás a jQuery de la misma manera como se indica en Yii Framework oficial pero ahora consultaras la seleccion a un objeto jQuery llamado: keepSelectionData, abajo el ejemplo completo:

 
[php]
$this->widget('zii.widgets.grid.CGridView', array(
        'id'=>'my-gridview',                  // IMPORTANT
        'selectableRows'=>2,                  // 2 = significa selección múltiple habilitada
        'dataProvider'=> $anyDataProvider,
        'columns'=>array(
            array('class'=>'CCheckBoxColumn'),  // agregamos la columna especial del CGridView que permite selección de filas
            'firstname',
            'lastname',
        ),
    ));
Yii::import('application.extensions.ekeepselection.*');
$dummy = new EKeepSelection('#my-gridview');

Para recibir la selección hecha recurres a jQuery:

 
[javascript]
var selected_items = $('#my-gridview').keepSelectionData();
// selected_items es un array, no un string separado por comas.
// uso: $.each(selected_items, function(k, value){ .. })


Hacer que un CGridView tenga columnas editables

Para esto debes usar la extensión que he creado: "extensión eeditable" (ir al repositorio github), esta te permitirá que agregues una nueva columna especial al CGridView llamada EEditableColumn, esta columna hará el trabajo de presentar un campo de edición o un select (según lo que se indique en la definición de la columna mediante el atributo 'editable_type' como muestra el ejemplo a continuación), tras hacer enter o tras seleccionar un valor (acción del usuario) este campo enviara un POST a un action definido también en los atributos de la columna.

Eeditable1.png Eeditable2.png

 
[php]
	<?php
	Yii::import('application.extensions.eeditable.*');

	$grid_id = 'some-grid-view';
	$this->widget('zii.widgets.grid.CGridView', array(
		'id'=>$grid_id,
		'dataProvider'=>$dataProvider,

		// esta linea es importante, para que tras updates la extension siga funcionando:
		'afterAjaxUpdate'=>new CJavaScriptExpression("function(id){ $('#'+id).EEditable(); }"),

		'columns'=>array(
			array('name'=>'firstname'),
			array('name'=>'example_field',
				'class'=>'EEditableColumn', 'editable_type'=>'editbox',
				'action'=>array('/some/ajaxeditcolumn'),
			),
			array('name'=>'example_field_2',
				'class'=>'EEditableColumn', 'editable_type'=>'select',
				'editable_options'=>
					array(-1=>'--select--','1'=>'Yes','0'=>'No','3'=>'maybe!'),
				'action'=>array('/some/ajaxeditcolumn'),
			),
		),
	));
	?>

El siguiente código es requerido en el lado del servidor para poder manejar los datos recibidos:

 
[php]
public function actionAjaxEditColumn(){
        $keyvalue   = $_POST["keyvalue"];   // ie: 'userid123'
        $name       = $_POST["name"];   // ie: 'firstname'
        $old_value  = $_POST["old_value"];  // ie: 'patricia'
        $new_value  = $_POST["new_value"];  // ie: '  paTTy '
 
        // do some stuff here, and return the value to be displayed..
        $new_value = ucfirst(trim($new_value));
        echo $new_value;            // Patty
    }

Personalizando Los Metodos de Entrada

Lee este post (issue #1) el cual indica cómo controlar los input o select que se presentan para editar los valores de las celdas. Alli también encontrarás un ejemplo para hacer mascaras con jQuery.mask y una forma de oir eventos para los metodos de entrada.

Un ejemplo mas completo de la extensión EEditable

El siguiente action recibira los cambios de las columnas de una tabla llamada 'user', como verás no se necesita especificar el update para cada columna, ya que gracias a las bondades de CModel del framework y sus magick getters/setters podemos pasar automaticamente la columna seleccionada mediante: $this[$name] = $new_value;

En otras palabras, cualquier cambio que se haga al cgridview podrá será recibido por el mismo action no importa cual sea la columna.

  
---archivo: protected/controllers/siteController.php---
	public function actionAjaxEditColumn(){
		$keyvalue	= $_POST["keyvalue"];  	// ie: 'userid123'
		$name		= $_POST["name"];	// ie: 'firstname'
		$old_value  = $_POST["old_value"];	// ie: 'patricia'
		$new_value  = $_POST["new_value"];	// ie: '  paTTy '

		$model = User::model()->findByPk($keyvalue);
		if($model){
			$model[$name] = $new_value;
			if($model->validate()){
				$model->save();
			}
			else{
				echo $old_value;
				return;
			}
		}
		
		echo $new_value;			// Patty
	}

A continuación este sería el widget de CGridView definido en una vista (admin o manage) que el Crud por defecto que te da GII, pero alterado para que use las nuevas columnas:

  
---archivo: protected/views/user/admin.php (simplificado, solo muestro el widget) ---
<?php 
	Yii::import('application.extensions.eeditable.*');

	$this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'user-grid',
	'dataProvider'=>$model->search(),
	'afterAjaxUpdate'=>new CJavaScriptExpression("function(id){ $('#'+id).EEditable(); }"),
	'filter'=>$model,
	'columns'=>array(
		'iduser',
		'username',
		array('name'=>'fullname',
			'class'=>'EEditableColumn', 'editable_type'=>'editbox',
			'action'=>array('/site/ajaxeditcolumn'),
		),
		array('name'=>'enabled', 'type'=>'boolean',
			'class'=>'EEditableColumn', 'editable_type'=>'select', 
				'editable_options'=>array(-1=>'--select--', '1'=>'Yes', '0'=>'No', '3'=>'maybe!'),
			'action'=>array('/site/ajaxeditcolumn'),
		),
		array('name'=>'profile',
			'class'=>'EEditableColumn', 'editable_type'=>'editbox',
			'action'=>array('/site/ajaxeditcolumn'),
		),
		array(
			'class'=>'CButtonColumn',
		),
	),
)); ?>

Creando varios botones personalizados en una misma columna con CButtonColumn

Por ahora la información se encuentra en el blog. ir al blog.

Creando una columna personalizada con CLinkColumn

Supongamos que tenemos un CGridView con una lista de usuarios, queremos una columna que sirva para que con solo hacerle click entonces el usuario en esa fila se active o desactive. Algo como esta imagen a continuación, con la especialidad de que la etiqueta del enlace diga "Activar" (si no esta activado) y que en cambio diga "Desactivar" (si estaba activado), además de que queremos que esta acción de activado y desactivado se haga con solo un click mediante ajax, sin refrescar el CGridView ni la página.

CLinkColumn 1.jpg

Pues bien, dos clases sirven a este propósito: CButtonColumn y CLinkColumn, las diferencias son que CButtonColumn permite agregar multiples botones en una misma celda, mientras que CLinkColumn esta orientado a crear solo una, CLinkColumn cumple mejor el requisito, así que haremos esto con CLinkColumn. CLinkColumn es una clase de columna para el CGridView que permite a este presentar un enlace dentro de una columna, tan simple como eso. Finalmente hará algo como: "<td><a href='alguna'>Activar</a></td>" nada mas. Lo que nos permite CLinkColumn es mejorar la forma en como se llega a ese código.

Implementando un CLinkColumn Personalizado con comportamiento Ajax

1. Definir la columna CLinkColumn, el siguiente código produce una columna como la de la imagen arriba, pero no hará nada.

  
<?php 
	$this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'user-grid',
	'dataProvider'=>$model->search(),
	'filter'=>$model,
	'columns'=>array(
                // ...he omitido las columnas irrelevantes para este ejemplo...
		array(
			'class'=>'CLinkColumn',
			'header'=>'Activador',
			'label'=>'Activar',
		),
	),
)); ?>

2. Agregaremos un comportamiento ajax para el link insertado, de modo que en vez de redirigir a otra página al hacerle click en cambio invoque la url del enlace pero vía ajax, para ello hay que agregar lo siguiente:

  
array(
	'class'=>'CLinkColumn',
	'header'=>'Activador',
	'label'=>'Activar',
	'htmlOptions'=>array('rel'=>'link_activador'),  // marcamos cada enlace con un tag REL para posterior identificación.
),

3. Agregamos un script que se encargue de manejar el click sobre cada "link_activador" detectado en la vista. Lo que evita el comportamiento por defecto del link es la llamada a "e.preventDefault()".

 
<?php
Yii::app()->getClientScript()->registerScript(
"activador_script",
"
	$(document).on('click','[rel=link_activador]',function(e) {
	   	e.preventDefault();
	   	var a = $(this).find('a');
		$.ajax({ url: a.attr('href'), type: 'post', 
			success: function(newlabel){
			a.html(newlabel);
		}, error: function(e){ alert('error:'+e.responseText); }});
	 });

",CClientScript::POS_HEAD);
?>


4. Ahora viene lo bueno, un poco de lógica de negocio. La etiqueta de cada enlace no puede ser definida mediante el atributo "label" porque eso aplicará para todas las columnas del CGridView, resulta que en este ejemplo queremos que esta etiqueta cambie según si el usuario está activo o no, pero aqui lo importante del modelado, podrias pensar que basta con un atributo en la base de datos que diga "activo true o false" pero no siempre es asi, a veces el estado de un usuario puede depender de alguna combinación de cosas, es posible que para alguna operacion el usuario este activo pero para otra no. Supongamos en este ejemplo que queremos activarlo para alguna actividad ficticia, por tanto no bastará con guardar en la base de datos un "true" o "false" ya que ahora es: "TRUE para tal actividad pero FALSE para otra actividad". Ten en cuenta esto para este ejemplo.

Para que la etiqueta cambie según una lógica de negocios recurrimos a "labelExpression" en vez de "label". En el siguiente extracto de codigo verás que le preguntamos el estatus del usuario que esta siendo presentado en la fila del CGridView a la controladora activa que esta gestionando la vista (pudimos haberle hecho la pregunta a un componente especializado, pero eso saca el ejemplo del foco). Como resultado la etiqueta cambiará según una lógica de negocio.

  
array(
	'class'=>'CLinkColumn',
	'header'=>'Activador',
	'labelExpression'=>'Yii::app()->controller->obtenerEtiqueta($data)',
	'urlExpression'=>'CHtml::normalizeUrl(array("ajaxactivador","id"=>$data->primarykey))',
	'htmlOptions'=>array('rel'=>'link_activador'),
),

el código en el controller (solo puesto ahi como un ejemplo, debe estar en un componente de negocio) ponemos un método de negocio que por ahora solo pregunta si el usuario indicado ha sido marcado como activado en la sesion activa, pero pudimos haber preguntado perfectamente si: "¿ tiene el usuario activo permiso para acceder a tal parte ?" o "¿ha sido activado el usuario en la lista de personal de nomina ?". Por favor no orientes al objeto a lo puesto en las siguientes lineas demostrativas, el objetivo es mas lejano que simplemente preguntar si el usuario esta activo en la sesión activa.

 
 // en la controladora o componente:
	public function obtenerEtiqueta($model){
		// pudimos haber preguntado: "está el usuario activado en la nomina seleccionada ?"
		$listado = Yii::app()->user->getState("usuarios_activados");
		if(isset($listado[$model->primarykey]) && (true==$listado[$model->primarykey])){
			return "Desactivarlo";
		}else{
			return "Activarlo";
		}
	}
	public function actionAjaxActivador($id){
		// activara al usuario en la sesion de ejemplo, pudimos haber dicho aqui:
		//   "por favor activa este usuario en la nomina seleccionada"
		$listado = Yii::app()->user->getState("usuarios_activados");
		$etiqueta = "";
		if(isset($listado[$id]) && (true==$listado[$id])){
			$listado[$id] = false;
			$etiqueta = "Activarlo";
		}else{
			$listado[$id] = true;
			$etiqueta = "Desactivarlo";
		}
		Yii::app()->user->setState("usuarios_activados",$listado);
		echo $etiqueta;
	}

El resultado es algo como esto:

CLinkColumn 2.jpg