Spanish (Español) translation by Luis Chiabrera (you can also view the original English article)
En esta lección, crearemos una biblioteca CodeIgniter que nos permite generar cuadrículas de datos automáticamente para administrar cualquier tabla de base de datos. Explicaré cada paso necesario para crear esta clase; por lo que probablemente aprenderás algunas técnicas/conceptos nuevos de programación orientada a objetos en el proceso.
Como beneficio adicional, procederemos a escribir un código jQuery que permitirá al usuario actualizar el contenido de la cuadrícula de datos sin tener que esperar a que se actualice la página.
Por favor, ten en cuenta...
Este tutorial asume que tienes un conocimiento modesto de los marcos CodeIgniter y jQuery.
¿Qué es una cuadrícula de datos?
Una cuadrícula de datos es una tabla que muestra el contenido de una base de datos o una tabla junto con los controles de clasificación.
Una cuadrícula de datos es una tabla que muestra el contenido de una base de datos o una tabla junto con los controles de clasificación. En este tutorial, se nos asignará la tarea de proporcionar esta funcionalidad, pero también evitar que el usuario espere a que la página se actualice cada vez que se realiza una operación. ¡Gracias a jQuery, esta será una tarea bastante simple!
¿Qué pasa con los usuarios que no tienen Javascript habilitado? ¡No te preocupes, también los compensaremos!
Paso 1: crear una clase de generador de cuadrícula de datos
Queremos construir una herramienta que nos permita crear cuadrículas de datos dinámicamente para cualquier tabla de base de datos que tengamos. Esto significa que el código no está vinculado a ninguna estructura de tabla específica y, por lo tanto, es independiente de los datos en sí. Todo lo que el codificador (el desarrollador que usa nuestra clase) debe saber es el nombre de la tabla que se va a transformar en una cuadrícula y la clave principal de esa tabla. Aquí está el previo de la clase que desarrollaremos durante la mayor parte de este tutorial:
<?php class Datagrid{ private $hide_pk_col = true; private $hide_cols = array(); private $tbl_name = ''; private $pk_col = ''; private $headings = array(); private $tbl_fields = array(); } ?>
La clase Datagrid bien podría agregarse a la carpeta de la aplicación/biblioteca, pero la vamos a agregar como ayuda al marco CodeIgniter. ¿Por qué? Debido a que cargar bibliotecas no nos permite pasar argumentos al constructor de la clase, cargarlo como ayuda resolverá el problema. Este punto tendrá más sentido para ti cuando hayamos terminado de escribir el constructor.
El método constructor de clase
public function __construct($tbl_name, $pk_col = 'id'){ $this->CI =& get_instance(); $this->CI->load->database(); $this->tbl_fields = $this->CI->db->list_fields($tbl_name); if(!in_array($pk_col,$this->tbl_fields)){ throw new Exception("Primary key column '$pk_col' not found in table '$tbl_name'"); } $this->tbl_name = $tbl_name; $this->pk_col = $pk_col; $this->CI->load->library('table'); }
Ya tenemos mucho que hacer; pero no te preocupes, ya que te lo explicaré todo en el siguiente párrafo.
El constructor toma dos argumentos: el primero es el nombre de la tabla en tu base de datos que desea mostrar como una cuadrícula de datos al usuario; el segundo parámetro es el nombre de la columna que sirve como clave principal para esa tabla (más sobre esto más adelante). Dentro del cuerpo del constructor, instanciamos el objeto CodeIgniter, el objeto de base de datos y la clase/biblioteca de tabla HTML. Todos estos serán necesarios durante la vida útil de un objeto de cuadrícula de datos y ya están integrados en el marco de CI. Ten en cuenta que también verificamos si la clave principal realmente existe en la tabla dada y, en caso de que no, lanzamos una excepción que informa el error. Ahora la variable miembro $this->tbl_fields
estará disponible para su uso posterior, por lo que no tenemos que recuperar la base de datos nuevamente.
"Podemos usar el comando
$CI->db->list_fields($tbl_name)
para obtener los nombres de todos los campos que tiene una tabla. Sin embargo, para un mejor rendimiento, recomiendo almacenar los resultados en caché ".
Método para personalizar los títulos de las tablas
public function setHeadings(array $headings){ $this->headings = array_merge($this->headings, $headings); }
Esto te permite personalizar los encabezados de la tabla de la cuadrícula de datos, es decir, puedes sobrescribir los nombres de las columnas originales para ciertos campos de la tabla. Se necesita un array
asociativo, como este: regdate =>"Registration Date". En lugar de solo el "Regdate" técnico como el encabezado de la columna para ese tipo de datos, tenemos un título más legible por humanos en su lugar. En breve se dará a conocer el código responsable de la aplicación de los títulos.
Método para ignorar/ocultar campos de tabla
public function ignoreFields(array $fields){ foreach($fields as $f){ if($f!=$this->pk_col) $this->hide_cols[] = $f; } }
ignoreFields
recibe un array
que contiene los campos que se ignorarán al obtener datos de la base de datos. Esto es útil cuando tenemos tablas con muchos campos, pero solo queremos ocultar un par de ellos. Este método es lo suficientemente inteligente como para rastrear un intento de ignorar el campo de clave principal y luego omitirlo. Esto es así porque la clave principal no se puede ignorar por razones técnicas (verás por qué en breve). Aún así, si deseas ocultar la columna de clave principal para que no aparezca en el Reino Unido, puedes usar el método hidePkCol
:
public function hidePkCol($bool){ $this->hide_pk_col = (bool)$bool; }
Este método recibe un valor booleano para indicar si queremos ocultar la columna de clave primaria para que no aparezca en la cuadrícula de datos. A veces, es una idea desagradable mostrar los datos pkey
, que generalmente es un código numérico sin ningún significado para el usuario.
Método de la siguiente instancia:
private function _selectFields(){ foreach($this->tbl_fields as $field){ if(!in_array($field,$this->hide_cols)){ $this->CI->db->select($field); // hide pk column heading? if($field==$this->pk_col && $this->hide_pk_col) continue; $headings[]= isset($this->headings[$field]) ? $this->headings[$field] : ucfirst($field); } } if(!empty($headings)){ // prepend a checkbox for toggling array_unshift($headings,"<input type='checkbox' class='check_toggler'>"); $this->CI->table->set_heading($headings); } }
Aquí tenemos un método auxiliar; por eso tiene el modificador "private" y tiene como prefijo un carácter subrayado (convención de código). Será utilizado por el método generate()
- explicado brevemente - para tener los campos de tabla apropiados seleccionados y también los encabezados apropiados establecidos para la tabla (generador) object
. Observa la siguiente línea:
$headings[]= isset($this->headings[$field]) ? $this->headings[$field] : ucfirst($field);
Aquí es donde aplicamos los encabezados personalizados o recurrimos a los predeterminados si no se da ninguno. Si se supone que la columna pk
está oculta de la pantalla, se omitirá su encabezado. También observa la siguiente línea:
array_unshift($headings,"<input type='checkbox' class='dg_check_toggler'>");
El comando anterior indica al programa que anteponga una casilla de verificación "Master" como el primer encabezado de la tabla. Esa casilla de verificación es diferente de otras casillas de verificación en la cuadrícula en que le permite al usuario marcar o desmarcar todas las casillas de verificación de una sola vez. Esta funcionalidad de alternancia se implementará en unos momentos con un simple fragmento de código jQuery.
Método para generar/renderizar la cuadrícula de datos
Ahora viene lo que realmente hace el trabajo por nosotros:
public function generate(){ $this->_selectFields(); $rows = $this->CI->db ->from($this->tbl_name) ->get() ->result_array(); foreach($rows as &$row){ $id = $row[$this->pk_col]; // prepend a checkbox to enable selection of items/rows array_unshift($row, "<input class='dg_check_item' type='checkbox' name='dg_item[]' value='$id' />"); // hide pk column cell? if($this->hide_pk_col){ unset($row[$this->pk_col]); } } return $this->CI->table->generate($rows); }
El método generate
, como su nombre indica, es responsable de generar la propia cuadrícula de datos. Debes llamar a este método solo después de haber configurado el objeto de acuerdo a tus necesidades. Lo primero que hace es llamar al método $this->_selectFields()
para realizar las acciones que explicamos anteriormente. Ahora tiene que buscar todas las filas de la base de datos y luego recorrerlas, agregando casillas de verificación al comienzo de cada fila:
// prepend a checkbox to enable selection of items/rows array_unshift($row, "<input class='dg_check_item' type='checkbox' name='dg_item[]' value='$id' />");
Dentro del bucle foreach
en el método generate
, si el indicador $this->hide_pk_col
se establece en true
, entonces debemos desarmar la entrada de clave primaria en la matriz $row array
para que no aparezca como una columna cuando $this->CI->table
object
procesa todas las filas y genera la salida html final. En este punto, está bien eliminar la clave principal, si es necesario, porque ya no necesitamos esa información. .
Pero, ¿qué hace el usuario con las filas seleccionadas/marcadas? Para responder a esto, he preparado algunos métodos más. El primero nos permite crear "action buttons" sin tener que conocer ningún detalle técnico sobre cómo funciona internamente el sistema de cuadrícula:
Método para agregar botones a un formulario de cuadrícula de datos
public static function createButton($action_name, $label){ return "<input type='submit' class='$action_name' name='dg_action[$action_name]' value='$label' />"; }
Simplemente pasa el nombre de la acción como primer argumento y un segundo argumento para indicar la etiqueta del botón generado. Se genera automáticamente un atributo class
para ese botón para que podamos jugar con él más fácilmente cuando estamos trabajando con él en nuestro JavaScript. Pero, ¿cómo sabemos si el usuario ha pulsado un determinado botón de acción? La respuesta se puede encontrar en el siguiente método:
public static function getPostAction(){ // get name of submitted action (if any) if(isset($_POST['dg_action'])){ return key($_POST['dg_action']); } }
¡Sí! Otro método estático que nos ayuda cuando se trata de formularios. Si se ha enviado alguna cuadrícula de datos, este método devolverá el nombre de la acción (u "operation") asociada con ese evento de envío. Además, otra herramienta útil para procesar nuestros formularios de cuadrícula de datos es ...
public static function getPostItems(){ if(!empty($_POST['dg_item'])){ return $_POST['dg_item']; } return array(); }
... que devuelve un array
que contiene los ids
seleccionados para que puedas rastrear qué filas se han seleccionado en la cuadrícula y luego realizar alguna acción con ellas. Como ejemplo de lo que se puede hacer con una selección de ids
de fila, he preparado otro método, este es un método de instancia, y no uno estático, porque hace uso de los recursos de instancia del objeto para hacer su negocio:
public function deletePostSelection(){ // remove selected items from the db if(!empty($_POST['dg_item'])) return $this->CI->db ->from($this->tbl_name) ->where_in($this->pk_col,$_POST['dg_item']) ->delete(); }
Si se marcó al menos una casilla de verificación, el método deletePostSelection()
generará y ejecutará una declaración SQL como la siguiente (supón $tbl_name='my_table'
y $pk_col='id'
):
DELETE FROM my_table WHERE id IN (1,5,7,3,etc...)
... que eliminará efectivamente las filas seleccionadas de la capa persistente. Podría haber más operaciones que podrías agregar a una cuadrícula de datos, pero eso dependerá de las características específicas de tu proyecto. Como consejo, podrías extender esta clase a, digamos, InboxDatagrid
, por lo que, más allá del método deletePostSelection
, podría incluir operaciones adicionales, como moveSelectedMessagesTo($place)
, etc...
Poniendo todo junto
Ahora, si has seguido este tutorial paso a paso, deberías haber terminado con algo similar a lo siguiente:
class Datagrid{ private $hide_pk_col = true; private $hide_cols = array(); private $tbl_name = ''; private $pk_col = ''; private $headings = array(); private $tbl_fields = array(); function __construct($tbl_name, $pk_col = 'id'){ $this->CI =& get_instance(); $this->CI->load->database(); $this->tbl_fields = $this->CI->db->list_fields($tbl_name); if(!in_array($pk_col,$this->tbl_fields)){ throw new Exception("Primary key column '$pk_col' not found in table '$tbl_name'"); } $this->tbl_name = $tbl_name; $this->pk_col = $pk_col; $this->CI->load->library('table'); } public function setHeadings(array $headings){ $this->headings = array_merge($this->headings, $headings); } public function hidePkCol($bool){ $this->hide_pk_col = (bool)$bool; } public function ignoreFields(array $fields){ foreach($fields as $f){ if($f!=$this->pk_col) $this->hide_cols[] = $f; } } private function _selectFields(){ foreach($this->tbl_fields as $field){ if(!in_array($field,$this->hide_cols)){ $this->CI->db->select($field); // hide pk column heading? if($field==$this->pk_col && $this->hide_pk_col) continue; $headings[]= isset($this->headings[$field]) ? $this->headings[$field] : ucfirst($field); } } if(!empty($headings)){ // prepend a checkbox for toggling array_unshift($headings,"<input type='checkbox' class='dg_check_toggler'>"); $this->CI->table->set_heading($headings); } } public function generate(){ $this->_selectFields(); $rows = $this->CI->db ->from($this->tbl_name) ->get() ->result_array(); foreach($rows as &$row){ $id = $row[$this->pk_col]; // prepend a checkbox to enable selection of items array_unshift($row, "<input class='dg_check_item' type='checkbox' name='dg_item[]' value='$id' />"); // hide pk column? if($this->hide_pk_col){ unset($row[$this->pk_col]); } } return $this->CI->table->generate($rows); } public static function createButton($action_name, $label){ return "<input type='submit' class='$action_name' name='dg_action[$action_name]' value='$label' />"; } public static function getPostAction(){ // get name of submitted action (if any) if(isset($_POST['dg_action'])){ return key($_POST['dg_action']); } } public static function getPostItems(){ if(!empty($_POST['dg_item'])){ return $_POST['dg_item']; } return array(); } public function deletePostSelection(){ // remove selected items from the db if(!empty($_POST['dg_item'])) return $this->CI->db ->from($this->tbl_name) ->where_in($this->pk_col,$_POST['dg_item']) ->delete(); } }
Aviso: no olvides guardar este archivo como datagrid_helper.php
y colocarlo en "application/helper/"
Paso 2: Probar la clase auxiliar de cuadrícula de datos con un controlador CodeIgniter
Ahora crearemos un controlador de prueba simple y cargaremos la clase Datagrid como ayuda en su constructor. Pero antes de eso, debemos definir una tabla de base de datos ficticia y completarla con algunos datos de muestra.
Ejecuta el siguiente SQL para crear la base de datos y la tabla de usuario:
CREATE DATABASE `dg_test`; CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(80) NOT NULL, `password` varchar(32) NOT NULL, `email` varchar(255) NOT NULL, UNIQUE KEY `id` (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
A continuación, agreguemos algunos usuarios:
INSERT INTO `users` (`id`, `username`, `password`, `email`) VALUES (1, 'david', '12345', 'david@domain.com'), (2, 'maria', '464y3y', 'maria@domain.com'), (3, 'alejandro', 'a42352fawet', 'alejandro@domain.com'), (4, 'emma', 'f22a3455b2', 'emma@domain.com');
Ahora, guarda el siguiente código como "test.php
", y agréguelo a la carpeta "aplicación/controladores":
<?php class Test extends CI_Controller{ function __construct(){ parent::__construct(); $this->load->helper(array('datagrid','url')); $this->Datagrid = new Datagrid('users','id'); } function index(){ $this->load->helper('form'); $this->load->library('session'); $this->Datagrid->hidePkCol(true); $this->Datagrid->setHeadings(array('email'=>'E-mail')); $this->Datagrid->ignoreFields(array('password')); if($error = $this->session->flashdata('form_error')){ echo "<font color=red>$error</font>"; } echo form_open('test/proc'); echo $this->Datagrid->generate(); echo Datagrid::createButton('delete','Delete'); echo form_close(); } function proc($request_type = ''){ $this->load->helper('url'); if($action = Datagrid::getPostAction()){ $error = ""; switch($action){ case 'delete' : if(!$this->Datagrid->deletePostSelection()){ $error = 'Items could not be deleted'; } break; } if($request_type!='ajax'){ $this->load->library('session'); $this->session->set_flashdata('form_error',$error); redirect('test/index'); } else { echo json_encode(array('error' => $error)); } } else { die("Bad Request"); } } } ?>
An instance of this class is created and passed as a reference to the $this->Datagrid
member. Ten en cuenta que obtendremos datos de una tabla llamada "usuarios" cuya clave principal es la columna "id"; luego, en el método de índice, tomamos los siguientes pasos: configurar el objeto Datagrid, renderizarlo dentro de un formulario con un botón de eliminar agregado y ver si todo funciona como se esperaba:

Pregunta: ¿Qué sucede cuando se envía el formulario?
Respuesta: El método "Test::proc()
" se encarga de procesar el formulario y elegir la operación correcta para aplicar contra los id
que fueron seleccionados por el remitente del formulario. También se encarga de las solicitudes AJAX, por lo que enviará un objeto JSON al cliente. Esta función compatible con AJAX será útil cuando jQuery entre en acción, ¡que es ahora mismo!
"Siempre es una buena idea crear aplicaciones web que compensen cuando JavaScript/AJAX no está disponible. De esta manera, algunos usuarios tendrán una experiencia más rica y más rápida, mientras que aquellos que no tengan JavaScript habilitado aún podrán usar la aplicación con normalidad ".
Paso 3: Implementando de Ajax (¡jQuery al rescate!)
Cuando el usuario hace clic en el botón (o cualquier otro botón de acción), nos gustaría, quizás, evitar que la página se recargue y tenga que generar todo de nuevo; ¡Esto podría hacer que el usuario de nuestra aplicación se duerma! Eludir este problema no será una tarea difícil si nos ceñimos a la biblioteca jQuery. Dado que este no es un tutorial para "principiantes", no voy a repasar todos los detalles relacionados con cómo obtener la biblioteca, cómo incluirla en la página, etc. Se espera que conozcas estos pasos por tu cuenta.
Crea una carpeta, llamada "js
", agrega la biblioteca jQuery dentro y crea un archivo de vista, llamado users.php
. Abrd este nuevo archivo y agrega:
<html> <head> <title>Users Management</title> <script src="<?php echo base_url(); ?>js/jquery-1.6.3.min.js"></script> <script src="<?php echo base_url(); ?>js/datagrid.js"></script> </head> <body> <?php $this->Datagrid->hidePkCol(true); if($error = $this->session->flashdata('form_error')){ echo "<font color=red>$error</font>"; } echo form_open('test/proc',array('class'=>'dg_form')); echo $this->Datagrid->generate(); echo Datagrid::createButton('delete','Delete'); echo form_close(); ?> </body> </html>
¿Te diste cuenta de que hemos movido el código de Test::index
al nuevo script de vista? Esto significa que debemos cambiar el método Test::index()
en consecuencia:
function index(){ $this->load->helper('form'); $this->load->library('session'); $this->load->view('users'); }
Eso está mejor. Si deseas agregar algo de estilo a la cuadrícula, puedes usar el siguiente CSS (o hacer un mejor diseño por tu cuenta):
.dg_form table{ border:1px solid silver; } .dg_form th{ background-color:gray; font-family:"Courier New", Courier, mono; font-size:12px; } .dg_form td{ background-color:gainsboro; font-size:12px; } .dg_form input[type=submit]{ margin-top:2px; }
Ahora, por favor, crea un archivo "datagrid.js", colócalo en el directorio "js" y comienza con este código:
$(function(){ // cool stuff here... })
Dentro de este cierre, escribiremos código que se encargará de controlar ciertos eventos de envío una vez que la página se haya cargado por completo. Lo primero que debemos hacer es rastrear cuando un usuario hace clic en un botón de envío en el formulario de cuadrícula de datos y luego enviar esos datos para que se procesen en el servidor.
$('.dg_form :submit').click(function(e){ e.preventDefault(); var $form = $(this).parents('form'); var action_name = $(this).attr('class').replace("dg_action_",""); var action_control = $('<input type="hidden" name="dg_action['+action_name+']" value=1 />'); $form.append(action_control); var post_data = $form.serialize(); action_control.remove(); var script = $form.attr('action')+'/ajax'; $.post(script, post_data, function(resp){ if(resp.error){ alert(resp.error); } else { switch(action_name){ case 'delete' : // remove deleted rows from the grid $form.find('.dg_check_item:checked').parents('tr').remove(); break; case 'anotherAction' : // do something else... break; } } }, 'json') })
Alternativamente, podríamos haber comenzado con algo como: $('.Dg_form').submit(function(e){...})
. Sin embargo, dado que quiero rastrear qué botón se ha presionado y extraer el nombre de la acción elegida en función de él, prefiero vincular un controlador de eventos al botón de enviar y luego subir en la jerarquía de nodos para encontrar el formulario que el botón presionado pertenece a:
// finds the form var $form = $(this).parents('form'); // extracts the name of the action var action_name = $(this).attr('class').replace("dg_action_","");
A continuación, agregamos un elemento de entrada oculto dentro del elemento del formulario para indicar qué acción se está enviando:
// create the hidden input var action_control = $('<input type="hidden" name="dg_action['+action_name+']" value=1 />'); // add to the form $form.append(action_control);
Esto es necesario porque la función no considera que el botón de envío sea una entrada de formulario válida. Entonces debemos tener ese truco en su lugar al serializar los datos del formulario.
action_control.remove();
"¡No lo olvides: la función ignora el botón de enviar y lo descarta como una simple pieza de chatarra de marcado!"
Envíando datos del formulario al servidor
A continuación, procedemos a obtener el atributo action
del elemento de formulario y anexamos la cadena "/ajax
" a esa url, para que el método sepa que, de hecho, se trata de una solicitud AJAX. Después de eso, usamos la función jQuery.post
para enviar los datos para ser procesados por el controlador apropiado, del lado del servidor, y luego interceptar el evento de respuesta con un callback/closure registrado:
... var script = $form.attr('action')+'/ajax'; $.post(script, post_data, function(resp){ if(resp.error){ alert(resp.error); } else { switch(action_name){ case 'delete' : // remove deleted rows from the grid $form.find('.dg_check_item:checked').parents('tr').remove(); break; case 'anotherAction' : // do something else... break; } } },'json')
Observa que estamos pidiendo que la respuesta se codifique como "json" ya que estamos pasando esa cadena como el cuarto argumento de la función $.post
. El contenido de la devolución de llamada que trata de la respuesta del servidor debería ser bastante sencillo de comprender; determina si hay un error y, de ser así, lo alerta. De lo contrario, indicará que la acción fue procesada con éxito (en este caso, si se trata de una "" acción, eliminamos las filas relacionadas con los id
s que fueron seleccionados por el usuario).
Paso 4: ¡Marca todo o nada!
Lo único que falta ahora es la funcionalidad de alternancia que prometí antes. Debemos registrar una función de devolución de llamada para cuando se haga clic en la casilla de verificación "Master", que tiene un atributo de clase establecido en "dg_check_toggler
". Agrega el siguiente fragmento de código después del anterior:
$('.dg_check_toggler').click(function(){ var checkboxes = $(this).parents('table').find('.dg_check_item'); if($(this).is(':checked')){ checkboxes.attr('checked','true'); } else { checkboxes.removeAttr('checked'); } })
Cuando se hace clic en la casilla de verificación "toggler", si pasa a un estado "checked", todas las filas de la cuadrícula de datos correspondiente se marcarán simultáneamente; de lo contrario, todo estará desmarcado.
Pensamientos finales
No hemos llegado a la punta del iceberg cuando se trata de cuadrículas de datos para sistemas de gestión de contenido más complejos. Otras características que pueden resultar útiles son:
- Ordenar la cuadrícula de datos por nombres de columna
- Enlaces de paginación para navegar por la cuadrícula de datos
- Editar/modificar enlaces para actualizar los datos de una sola fila
- Mecanismo de búsqueda para filtrar resultados
Gracias por leer. Si desea un tutorial de seguimiento, házmelo saber en los comentarios.