jqGrid: Grids espectaculares para ASP.NET MVC, paso a paso

Dicen las malas lenguas 😉 que durante una reunión del equipo de diseño de ASP.NET MVC alguien dijo: “necesitaremos un control tipo Repeater”, refiriéndose a algún tipo de mecanismo para mostrar datos tabulados de forma sencilla. Y la respuesta del jefe técnico fue, “ya lo tenemos: se llama bucle foreach”.

Anécdotas aparte, es cierto que en ASP.NET MVC 1.0 no existe otro mecanismo que el bucle de toda la vida para mostrar datos en tablas, la típica rejilla con información sobre elementos de una colección tan habitual en nuestras aplicaciones. La máxima ayuda que tenemos “de serie” es el andamiaje generado por Visual Studio a la hora de generar una vista sobre una enumeración de elementos, que es válida únicamente en escenarios muy sencillos.

Y aquí es donde entra jQuery Grid Plugin (jqGrid para los amigos), un plugin para jQuery que se integra perfectamente con ASP.NET MVC y que permite simplificar enormemente el desarrollo  de vistas de tipo grid ofreciendo al mismo tiempo unas funcionalidades espectaculares: paginación, carga de datos de forma asíncrona vía Ajax, ordenación y redimensionado de columnas, edición de celdas, interfaz multi-idioma basado en temas, y un larguísimo etcétera.

En este post vamos a implementar un grid simple de consulta de datos utilizando jqGrid 3.5 y ASP.NET MVC 1.0, paso a paso. Además, al final del post encontraréis un enlace para descargar el proyecto de demostración para Visual Studio 2008 Express.

1. Descargamos jqGrid

Descarga de jqGridLo primero, como siempre, es descargar los componentes necesarios. Para hacernos con jqGrid, es necesario visitar la página de descargas del proyecto, en la que podemos encontrar un selector que nos permitirá elegir los módulos asociados a las funcionalidades que vayamos a utilizar.

En nuestro caso, dado que vamos a implementar un escenario muy simple, seleccionaremos únicamente el módulo “Grid base” y pulsamos el botón download situado en el pie de la tabla.

La descarga es un archivo .zip bastante ligerito cuyo contenido utilizamos así:

  • jquery.jqGrid.min.js, que encontraréis en la carpeta “js” del archivo descargado, lo copiamos a la carpeta “scripts” del proyecto ASP.NET MVC.
  • grid.locale-sp.js , el archivo de localización al español que encontraremos en la carpeta “js/i18n” del .zip, lo copiamos también a la carpeta “scripts” del proyecto.
  • ui.jqgrid.css , disponible en la carpeta “css” del archivo comprimido, lo pasamos a la carpeta “content” del proyecto.

Archivos del proyecto 2. Descargamos un tema visual

jqGrid utiliza los temas visuales de jQuery UI para componer el interfaz, por lo que es necesario descargar alguno de los disponibles o bien crear uno personalizado con la herramienta on-line disponible en su página. Para nuestro ejemplo, vamos a utilizar el tema “Cupertino”.

Como en el caso anterior, seleccionamos los componentes a obtener (en principio, basta con UI Core a no ser que vayáis a utilizar esta librería para otras cosas), el tema, y pulsamos el botón download. Descargaremos un archivo .zip en cuyo  interior encontramos, dentro de la carpeta “css”, una subcarpeta con el nombre del tema elegido y que copiamos a la carpeta “content” del proyecto MVC.

La imagen anterior muestra la distribución de los archivos descritos hasta el momento en las distintas carpetas en las que hay que colocarlos.

3. Incluimos los componentes en el proyecto ASP.NET MVC

Tras crear el proyecto ASP.NET MVC, vamos a incluirle ahora las referencias a librerías y archivos necesarios para poder utilizar jqGrid.

Básicamente, tendremos que incluir en las páginas donde vayamos a utilizarlo referencias a jQuery, jqGrid, el archivo de localización, la hoja de estilos utilizada por jqGrid, y el tema que estemos empleando. Lo más fácil es hacerlo a nivel de página maestra, por ejemplo así:

<head runat="server">

    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>

    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />

 

    <link href="../../Content/ui.jqgrid.css" rel="stylesheet" type="text/css" />

    <link href="../../Content/cupertino/jquery-ui-1.7.2.custom.css" 

          rel="stylesheet" type="text/css" />

    <script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>

    <script src="../../Scripts/grid.locale-sp.js" type="text/javascript"></script>

    <script src="../../Scripts/jquery.jqGrid.min.js" type="text/javascript"></script>

</head>

Es importante incluir todos los archivos y en el orden correcto, de lo contrario el plugin no funcionará correctamente.

4. El modelo

En lugar de utilizar una base de datos, vamos a crear una simulación en memoria de un almacén de datos personales basado en la siguiente entidad:

public class Amigo

{

    public int Id { get; set; }

    public string Nombre { get; set; }

    public string Apellidos { get; set; }

    public DateTime FechaDeNacimiento { get; set; }

    public string Email { get; set; }

}

La lógica de negocio está implementada en la clase GestorDeAmigos, que incluye, además del constructor que carga los datos iniciales en el almacén (una colección de tipo List<Amigo> , los siguientes métodos:

public IEnumerable<Amigo> ObtenerAmigos(int pagina, int elementosPorPagina, 

                                        Func<Amigo, IComparable> orden, 

                                        Ordenacion ordenacion)

{

    IEnumerable<Amigo> datos;

    if (ordenacion==Ordenacion.Ascendente)

        datos = datosAmigos.OrderBy(orden);

    else

        datos = datosAmigos.OrderByDescending(orden);

 

    return datos.Skip((pagina - 1) * elementosPorPagina).Take(elementosPorPagina);

}

 

public int ContarAmigos()

{

    return datosAmigos.Count;

}

Del código anterior, sólo comentar los parámetros del método ObtenerAmigos:

  • pagina y elementosPorPagina, indican, respectivamente el número de página de datos a mostrar y el número de registros máximo a mostrar en cada una de ellas.
  • orden es una lambda que retorna la expresión por la cual ordenaremos los datos. Si estuviéramos utilizando consultas SQL directas o LINQ to SQL podríamos usar otros enfoques, como la construcción de queries dinámicas o Dynamic LINQ.
  • ordenacion indica, usando una enumeración, si el orden es ascendente o descendente.

Así, a continuación puede verse un ejemplo de lo simple que quedaría una llamada a este método, flexible pero con tipado fuerte:

var amigos = gestorDeAmigos.ObtenerAmigos(

                   1,                       // Página actual

                   25,                      // Registros por página

                   amigo=>amigo.Apellidos,  // Expresión de ordenación

                   Ordenacion.Ascendente    // Orden

             );

5. El controlador

Una de las principales ventajas que ofrece jqGrid es que la carga de los datos la realiza mediante peticiones Ajax. En la práctica, esto significa que irá lanzando llamadas a una acción de nuestro controlador para solicitarle la información conforme vaya necesitando datos, por ejemplo, durante la carga inicial de la página o cuando el usuario utiliza las herramientas de paginación, enviándole los siguientes parámetros:

  • sidx, el índice o nombre del campo de ordenación actual.
  • sord, “asc” o “desc”, indicando si el orden es ascendente o descendente.
  • page, el número de página actual.
  • rows, número de elementos a obtener para completar la página.

El retorno de este método de acción debe ser un objeto serializado en JSON (aunque también permite XML) y ha de ajustarse a un formato especificado en la documentación, que es el que jqGrid espera recibir, algo como:

{ 

  total: "xxx", 

  page: "yyy", 

  records: "zzz",

  rows : [

    {id:"1", cell:["cell11", "cell12", "cell13"]},

    {id:"2", cell:["cell21", "cell22", "cell23"]},

      ...

  ]

}

El método de obtención de datos,  nuestra acción, seguirá normalmente el patrón recogido en el código mostrado a continuación. Si os fijáis, lo único que estamos haciendo es reproducir fielmente esta estructura anterior en un objeto anónimo, de forma que  al transformarlo en JSON (gracias al retorno de tipo JsonResult) se genere justo lo que necesitamos:

public ActionResult ObtenerDatosGrid(string sidx, string sord, int page, int rows)

{

    var datos = [...]          // Obtener datos del modelo

    var totalRegistros = [...] // Contar todos los registros

 

    int totalPages = (int)Math.Ceiling((decimal)totalRegistros / (decimal)rows);

 

    var data = new

    {

        total = totalPages,        // Total de páginas

        page = page,               // Página actual

        records = totalRegistros,  // Total de registros (obtenido del modelo)

        rows = from a in datos     // Datos de filas

               select new {

                   id = a.Id,                // ID único de la fila

                   cell = new string[] {     // Array de celdas de la fila

                       a.Apellidos,                             // Primera columna,            

                       a.Nombre,                                // Segunda columna,

                       a.FechaDeNacimiento.ToShortDateString(), // Tercera columna,

                       a.Email                                  // Cuarta columna  

                   }

               }

    };

    return Json(data);

}

Nuestro ejemplo se ajusta totalmente al patrón anterior, aunque incluiremos código extra para tener en cuenta las ordenaciones indicadas por los parámetros sidx y sord en la invocación al modelo. Observad cómo construimos las expresiones de ordenación:

public ActionResult ObtenerDatosGrid(string sidx, string sord, int page, int rows)

{

    // Establecemos la función de ordenación dependiendo del valor del 

    // parámetro "sidx", que es el campo de orden actual

    Func<Amigo, IComparable> funcOrden =

        sidx == "Apellidos" ? a => a.Apellidos :

        sidx == "FechaDeNacimiento" ? a => a.FechaDeNacimiento :

        sidx == "Email" ? a => a.Email :

        (Func<Amigo, IComparable>)(a => a.Id);

 

    // Establecemos si se trata de orden ascendente o descendente, en

    // función del parámetro "sord", que puede valer "asc" o "desc"

    Ordenacion ordenacion = sord == "asc" ? Ordenacion.Ascendente : Ordenacion.Descendente;

 

    // Usamos el modelo para obtener los datos

    var datos = gestorDeAmigos.ObtenerAmigos(page, rows, funcOrden, ordenacion);

    int totalRegistros = gestorDeAmigos.ContarAmigos();

    ... 

}

6. La vista

En la vista debemos crear un elemento contenedor, concretamente una tabla XHTML con un identificador único que después usaremos para indicarle al plugin dónde tiene que actuar su magia. Si además, como bastante habitual, pensamos incluir herramientas de paginación, tendremos que incluir también un contenedor para la misma:

<table id="list"></table>

<div id="pager"></div>    

El siguiente paso es inicializar jqGrid cuando se termine de cargar la página, para lo que crearemos un código javascript como el mostrado a continuación, en el que se invoca la función de activación del plugin sobre la tabla cuyo ID se indica en el selector (“#list“), y especificando el comportamiento deseado en los parámetros que le siguen:

script type="text/javascript">

    jQuery(document).ready(function() {

        jQuery("#list").jqGrid({

            url: '<%= Url.Action("ObtenerDatosGrid") %>',

            datatype: 'json',

            mtype: 'GET',

            colNames: ['Apellidos', 'Nombre', 'Fecha Nac.', 'Email'],

            colModel: [

              { index: 'Apellidos', width: 150, align: 'left' },

              { index: 'Nombre', width: 150, align: 'left', sortable: false },

              { index: 'FechaDeNacimiento', width: 80, align: 'center' },

              { index: "Email", width: 120, align: 'left'}],

            pager: jQuery('#pager'),

            rowNum: 20,

            rowList: [20, 50, 100],

            sortname: 'Apellidos',

            sortorder: 'asc',

            viewrecords: true,

            imgpath: '/content/cupertino/images',

            caption: 'Agenda personal',

            height: 400,

            width: 900,

            onSelectRow: function(id) {

                alert("Pulsado Id: " + id);

            }

        });

    }); 

</script>    

Revisamos rápidamente el significado de los distintos parámetros:

  • url, la dirección de la acción que suministrará los datos.
  • datatype, que indica el formato de intercambio de datos.
  • mtype , el verbo HTTP (get o post) que se utilizará para las peticiones.
  • colnames, un array con los títulos de las columnas.
  • colmodel, un array en el que cada elemento es un objeto que define
    • index: el nombre del campo que recibiremos en el método de acción indicando la ordenación actual.
    • width: el ancho, en píxeles.
    • align: alineación de la columna.
    • sortable: si la columna se puede utilizar como criterio de ordenación.
  • pager, el elemento sobre el que se compondrá la herramienta de paginación.
  • rownum, número de elementos por página.
  • rowlist, elementos por página mostrados en un desplegable para que el usuario lo ajuste.
  • sortname, el campo de ordenación por defecto.
  • sortorder, si la ordenación por defecto es ascendente o descendente.
  • viewrecords, si se muestra el total de registros en la herramienta de paginación.
  • imgpath , ruta hacia la carpeta de imágenes del tema elegido.
  • caption, el título del grid.
  • height, width, alto y ancho del grid, en píxeles.
  • onSelectRow, función anónima ejecutada cuando el usuario selecciona una fila. Recibe como parámetro el identificador único del registro.

Y… ¡Voilá!

Demo de jqGrid en acción¡Hasta aquí hemos llegado! Hemos visto cómo utilizar el magnífico jqGrid en un escenario simple, paso a paso, comenzando por la descarga de los componentes, preparando el proyecto para poder utilizarlos y, finalmente, implementando el modelo, el controlador, y la vista.

Pero jqGrid no es sólo eso, ni mucho menos. No hemos visto más que la punta del iceberg: el plugin permite realizar búsquedas, edición, anidación de grids, árboles, escenarios maestro-detalle… en fin, una maravilla.

jqGrid se distribuye bajo licencia dual GPL y MIT, y puede ser utilizado tanto en proyectos comerciales como libres. Dispone de una documentación bastante razonable, y muchos ejemplos para que podamos apreciar sus virtudes.

Descargar proyecto de demostración:

Publicado en: Variable not found.

20 comentarios sobre “jqGrid: Grids espectaculares para ASP.NET MVC, paso a paso”

  1. La verdad es que con estos grids las aplicaciones se hacen mucho mas atractivas, y nos quitan ciertos quebraderos de cabeza a los desarrolladores, gracias a Jquery.

    Muchas Gracias por el articulo.

    Un saludo.

  2. Que gran verdad el “ya lo tenemos: se llama bucle foreach” yo estoy trabajando hace un rato con MVC y el tema de las grillas es un dolor de cabeza..parace que esto es un gran avance.

  3. Yo tengo una duda… si quiero ligar una vista parcial con los datos «detalle» de una elemento en la grilla, ¿cómo puedo pasar el ID que tomo de la grilla con Url.Action?

    Excelente artículo para empezarle con esto del jqGrid.

    Gracias

  4. Hola, gracias a todos por comentar! 🙂

    @jusay, puedes hacerlo de varias formas. Si quieres mostrar esta información al cambiar de registro, por ejemplo, fíjate en el evento onSelectRow, que te envía el «id» del registro cuando seleccionas una fila.

    También puedes añadir botones personalizados utilizando el método navButtonAdd() sobre la barra de navegación (navGrid).

    Saludos.

  5. Hola, Rodrigo.

    debes tener en cuenta que en MVC 2 las peticiones que retornan datos JSON no pueden ser de tipo GET, y esas son las utilizadas por jqGrid por defecto.

    Pero hay formas para evitar este problema.

    Por ejemplo, una posibilidad es introducir en la función de retorno de datos del controlador, algo así:

    return Json(losdatos, JsonRequestBehavior.AllowGet);

    De esa forma, autorizas expresamente las peticiones GET.

    Otra posibilidad es hacer que jqGrid utilice peticiones Post, también te valdría.

    Saludos.

  6. Hello,

    Thanks a lot for the examples.

    I’m implementing jqGrid with areas in MVC2 but it doesn’t show me something, i’ve created another MVC2 app from scratch without areas (test purposes) and all goes great.

    Could you give me some idea? (Many thanks in advance) I’ve leaved you my mail.

    Regards from Cali, Colombia.

  7. Hi, Diego.

    Thank you for your comments.

    Regarding your question, you should check if the scripts and css files are being referenced properly from the view. It’s a common mistake to use relative paths (such as «../../scripts/») in the script and style tags, which are valid only on design time. You could use Fiddler or Firebug to detect 404 errors when reading this files.

    Regards,
    Jose.

    PD: A todo esto, ¿por qué hablamos en inglés? ;-D

  8. Hola,

    Jeje tienes razón, mejor en Español.

    Quisiera agradecer el tiempo que empleas para ayudarme, desafortunadamente la idea que me diste no funcionó. Sigo sin poder visualizar la grilla, este error está acabando conmigo.

    Tengo 3 imágenes bastante ilustrativas de la situación, de poder compartirtelas me avisas que yo te las envío de una.

    De nuevo gracias.

  9. Hola, Diego! 🙂

    Lo primero que debes hacer es detectar en qué momento se produce el problema. Para ello, como te comentaba, nada mejor que firebug o fiddler, con el que puedes monitorizar las conexiones.

    El primer error que te aparezca es bastante significativo; si no se pueden cargar los scripts o estilos, verás un 404 accediendo a estos recursos; si, en cambio, no puede realizarse la llamada Ajax para obtener los datos del grid, serán también un error 404 (not found) o 500 (error en servidor).

    También puede deberse a la carga de script en un orden incorrecto. Si, como dices, has podido usarlo sin áreas, ese mismo código debería valerte en las áreas.

    De todas formas, si quieres seguir profundizando, puedes contactarme en el buzon josemariaaguilar, sobre el dominio .com de gmail.

    Saludos!

  10. Muchas gracias por la colaboración e ideas Jose.

    Finalmente pude solucionarlo, y fue mas sencillo de lo que parecía:

    1. Ubicar las referencias a los scrips de JS, arriba del ContentPlaceHolder de la página ASPX, y no abajo como los tenía.

    2. Al inicializar la grilla jqGrid utilizando áreas, en la url no funciona el método que conocemos como url.Action(«NombreMetodoGrilla»), sino.. url (‘../../../../NombreArea/NombreControlador/NombreMetodoGrilla’)

    Espero que pueda ser de utilidad en un futuro.

  11. Hola,

    si me permites una última sugerencia: jamás uses rutas directas (como esa que indicas) hacia acciones. Usa los helpers, como Url.Action.

    En el caso de las acciones incluidas en áreas puedes especificar el área en los parámetros, más o menos así:

    Url.Action(«miaccion», new { controller=»micontrolador», area=»miarea» })

    Saludos!

  12. Si claro que si, de hecho.. asi lo he hecho a lo largo de todo el desarrollo 😀

    Pero en este caso específico de la grilla, no funcionó, y el modelo que me sugieres también lo probé, pero sin éxito. 🙁

  13. Hola, si.

    Mira que es curioso, ya que, siempre he trabajado con el modelo que sugieres y mucho más ahora con áreas 😀

    Pero para ese caso específico del url de la grilla, nunca me funcionó.. el unico que me dió resultado fué el de los puntos :S :S

    A lo mejor no tuvieron en cuenta ese detalle con la liberación del MVC2.

    Saludos. (De que país eres?)

  14. Hola de nuevo!

    Pues ya te digo que es raro, en teoría no hay ninguna diferencia entre generar la URL para la rejilla o para otros usos… en fin, lo dicho, cuando tenga un ratillo le echaré un vistazo.

    Ah, soy de España.

    Saludos!

  15. Hola, estoy teniendo problemas cuando agrego parametros perzonalizados a la URL que obtiene los datos de la grilla. Alguien tendrá idea porque no ingresa al método del controlador?. Con los parametros default de la grilla funciona correctamente: public ActionResult ObtenerDetPresu(string sidx, string sord, int page, int rows)

    A la espera de comentarios.
    Gracias.

  16. Muchas gracias por el ejemplo, solo una duda, estoy tratando en MVC2 pero no me funciona, veo que se tendría que hacer el retorno con los datos del controlador, de esta manera:

    return Json(losdatos, JsonRequestBehavior.AllowGet);

    pero no entiendo que son «losdatos» se refiere a la «var data» que creamos? o a que cosa? disculpa la ignorancia

Responder a anonymous Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *