Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
Mostrando entradas con la etiqueta jqGrid. Mostrar todas las entradas
Mostrando entradas con la etiqueta jqGrid. Mostrar todas las entradas
lunes, 1 de febrero de 2010
aspnetmvc Con objeto de mejorar la seguridad de nuestras aplicaciones, la Release Candidate de ASP.NET MVC 2 introdujo un cambio importante en la forma de procesar peticiones que retornan información serializada como JSON: por defecto, ahora sólo se responde a peticiones de tipo POST.

Dado que en MVC 1.0 era justo al contrario, esta pequeña reorientación hace que aplicaciones que antes funcionaban correctamente dejen de hacerlo al migrarlas a la última versión del framework. Un ejemplo lo tenemos con las aplicaciones que aprovechan la potencia de jqGrid para mostrar y editar rejillas de datos. Recordemos que el intercambio de información entre cliente y servidor se realiza mediante llamadas Ajax que retornan siempre datos en notación JSON.

Los síntomas que notaremos en estos casos son muy simples: ¡las acciones que deberían devolvernos información dejan de hacerlo! En algunas ocasiones, dependiendo del uso que se dé en la vista a la la información retornada, es posible que aparezcan errores de script en el navegador, pero otras ni siquiera eso.

En cualquier caso, no es difícil dar con la solución. Con Firebug, Fiddler, o cualquier herramienta que nos permita monitorizar las peticiones, podremos observar que en la petición Ajax se está produciendo un error HTTP 500 (error interno de servidor):

image

Y si seguimos profundizando en dicha petición, podemos ahora conocer el mensaje descriptivo que envía el servidor, un auténtico detalle por parte del equipo de desarrollo de ASP.NET MVC 2, en el que incluso nos indican la solución al problema:

<html>
<head>
<title>This request has been blocked because sensitive 
information could be disclosed to third party web sites when 
this is used in a GET request. 
To allow GET requests, set JsonRequestBehavior to AllowGet.</title>
<style>

Ante esta situación, podemos optar por dos soluciones. La primera sería indicar explícitamente en la instancia del resultado, de tipo JsonResult, que queremos permitir el método GET, así:

return Json(data, JsonRequestBehavior.AllowGet);

Y otra posibilidad sería realizar las peticiones Ajax utilizando el método POST, algo que podemos conseguir muy fácilmente la mayoría de las veces. En el caso de los ejemplos con jqGrid, sería sustituyendo el ’GET’ por ‘POST’ en código:

[...]
jQuery("#list").jqGrid({
url: '<%= Url.Action("ObtenerDatosGrid") %>',
datatype: 'json',
mtype: 'POST',    // <- Aquí
colNames: ['Apellidos', 'Nombre', 'Fecha Nac.', 'Email'],
[...]

¡Gracias, Maxi, por los comentarios que inspiraron este post!

Publicado en: Variable not found.
martes, 24 de noviembre de 2009

En un post anterior dedicado a jqGrid y ASP.NET MVC vimos lo sencillo que resultaba implementar un potente grid para mostrar datos tabulares, permitiendo paginación, ordenación y redimensionado de columnas.

Pero, como ya comenté entonces, jqGrid es mucho más que eso. En este artículo estudiaremos la implementación de la funcionalidad de borrado de filas integrada en el propio componente, utilizando intercambio de datos Ajax con el lado servidor para actualizar el modelo.

Partiremos del ejemplo que desarrollamos anteriormente, y nos centraremos únicamente en introducir los cambios necesarios para introducir esta nueva capacidad. También, como en el caso anterior, encontraréis al final un enlace al proyecto de demostración, para Visual Web Developer Express 2008 SP1 y ASP.NET MVC 1.0.

1. Preparamos la infraestructura

Selección de módulos en jqGridAntes de nada, y es algo que es importante recordar cuando trabajemos con jqGrid, debemos pensar qué módulos de este componente necesitamos en nuestro proyecto.

En el post anterior descargamos exclusivamente los necesarios para implementar la visualización del grid; ahora, dado que vamos a utilizar más funcionalidades del componente, debemos seleccionar en la herramienta de descarga aquellos que nos harán falta, el base y los relativos a la edición de datos.

En teoría deberíamos seleccionar los módulos estrictamente necesarios para nuestros fines, pero en la práctica no es fácil adivinar cuál de ellos implementa justamente lo que estamos buscando. De hecho, es bastante frecuente encontrarse con errores de script cuando no acertamos con el módulo exacto, al que siguen bastantes iteraciones prueba-error hasta que conseguimos averiguar cuál debemos indicar en la descarga.

Por eso en este caso, seleccionaremos todos los módulos relativos a la edición; eso nos permitirá, además, seguir implementando funcionalidades como el alta o la modificación sin tener que volver a descargar componentes.

Aparte de los módulos a incluir en la descarga, el resto de los pasos de preparación de la infraestructura son idénticos a los descritos en los puntos 1 al 3 del post inicial:

  • Copiar los archivos de script (jqGrid y el archivo de localización) en el proyecto.
  • Descargar el tema visual de jQuery UI y añadirlo al proyecto.
  • referenciar las librerías de scripts y estilos en la master (o vistas donde vayamos a usarlo).

2. El modelo

Vamos a ampliar la clase GestorDeAmigos anterior para que sea capaz de emular el almacenamiento en una base de datos, pero utilizando como soporte una colección en memoria que haremos persistir en una variable de aplicación. Además, aprovecharemos para añadirle un método Delete() que nos permita eliminar del almacén la persona cuyo “Id” le pasemos como parámetro.

public bool Delete(int id)
{
    Amigo amigo = DatosAmigos.FirstOrDefault(a => a.Id == id);
    if (amigo == null)
        return false;
 
    DatosAmigos.Remove(amigo);
    return true;
}
 
No profundizaré más en el modelo, pues el código es de lo más convencional. El que tenga curiosidad por ver cómo se implementa el almacén en una variable de aplicación, que acuda al fuente del proyecto de demostración.

3. La vista

En la vista debemos hacer muy pocos ajustes para permitir la eliminación de los datos. Básicamente, tendremos que habilitar un panel de botones en el grid, indicar que deseamos que aparezca el botón de eliminación, y configurar el comportamiento de éste, para lo cual invocaremos al método navGrid() después de la llamada de inicialización del grid que ya vimos el otro día:

<script type="text/javascript">
    jQuery(document).ready(function() {
    
        jQuery("#list").jqGrid({
            [...] // OMITIDO. Idéntico al del post anterior.
        });
 
        $("#list").navGrid(
                null,
                { refresh: true, add: false, edit: false, del: true, search: false },
                null, // parámetros para el alta
                null, // parámetros para la edición
                {     // parámetros para la eliminación
                    url: '<%= Url.Action("Eliminar") %>',
                    width: 500,
                    afterSubmit: function(r, d) {
                        return [r.responseText=="", r.responseText];
                    }
                }
        );
});

Los parámetros que estamos pasando al método navGrid() son los siguientes:

  • el primer parámetro debería ser jQuery('#pager'), que es la referencia hacia el control de paginación que estamos utilizando. En este caso es un nulo porque esta referencia se incluyó en la inicialización de jqGrid.
  • A continuación, creamos un objeto anónimo en el que establecemos a true las propiedades del y refresh, que indica que queremos mostrar los botones de eliminación y recarga de datos. El resto de propiedades predefinidas, add, edit, y search, equivalentes a los botones de añadir, editar y buscar registros, respectivamente, las establecemos a false con objeto de que no aparezcan botones para invocarlas; ya las activaremos en otros posts ;-)
  • el siguiente parámetro se trata de un objeto donde configuramos el comportamiento del botón de alta de registros. Dado que no vamos a implementarlo ahora, lo establecemos a nulo.
  • el cuarto parámetro es lo mismo que el anterior, pero para configurar la edición, por lo que también se encuentra establecido a null.
  • a continuación, y por último, el objeto en cuyas propiedades definimos el comportamiento del botón de eliminación:

    • La URL a la acción que se invocará en servidor, que la obtenemos utilizando el UrlHelper de MVC. En este caso, invocaremos a una acción llamada “Eliminar”, a la que el sistema enviará el Id del registro activo.
    • El ancho del cuadro de diálogo de confirmación.
    • En afterSubmit implementamos una función callback que jqGrid llamará cuando haya recibido el resultado de la petición Ajax. El primer parámetro que nos envía es el objeto XMLHttpRequest donde encontraremos la respuesta obtenida desde el servidor; el segundo parámetro contiene los datos que han sido enviados a la petición.

      El retorno de la función callback debe ser siempre un array con dos elementos. El primero es un booleano indicando si la eliminación ha tenido éxito, y el segundo es el mensaje de error, en caso de que se haya producido:

      return [ exito , "mensaje de error" ];

      Es importante ahora resaltar una cosa: salvo los parámetros de entrada y el tipo de retorno descritos anteriormente, jqGrid no nos impone ninguna particularidad más respecto a cómo debemos implementar este método, el tipo de información que recibiremos desde el controlador o cómo la procesaremos al recibirla. Somos totalmente libres de elegir la forma en la que haremos las cosas.

      En nuestro caso, vamos a hacer una implementación muy simple en base a la siguiente convención: el controlador retornará un string con una descripción del error en caso de que se produzca algún problema borrando el registro, y retornará un nulo cuando todo vaya bien. Esto nos permite implementar el callback utilizando la siguiente expresión:
      return [r.responseText=="", r.responseText];

      Si observáis, estamos llenando el array de retorno de tal forma que el primer parámetro será cierto si la respuesta obtenida está vacía (o sea, no hay error), y en el segundo parámetro introducimos la respuesta tal cual la hemos obtenido del servidor.

4. El controlador

Dado que la lógica la tenemos implementada en el modelo, y que la vista ya está preparada para ponerse en contacto con el controlador vía Ajax y para recibir el feedback sobre el éxito de la operación, sólo nos queda implementar muy rápidamente el método de acción:

public ActionResult Eliminar(int id)
{
    if (!gestorDeAmigos.Delete(id))
        return Content("Error eliminando el registro");
 
    return null;
}

Podréis observar que si se produce un error, retornamos un ContentResult describiendo el problema; en otros casos, devolvemos un nulo.

Para probar el funcionamiento de una eliminación errónea podéis, por ejemplo, abrir dos navegadores contra la aplicación, borrar un registro desde uno de ellos e intentar borrarlo también desde el otro.

Y… ¡Voilá!

Una vez habiendo implementado el modelo, la vista y el controlador, sólo nos queda probar la aplicación, que veremos que funciona perfectamente ;-P

jqGrid+ASP.NET MVC en acción

Resumiendo, en este post hemos seguido profundizando en las capacidades de jqGrid, implementando paso a paso la funcionalidad de eliminación de registros. Hemos podido observar también el escaso código (¡a pesar de la longitud del post!) que hay que añadir para disponer de esta funcionalidad, y de lo sencillo y reutilizable que resulta su implementación.

Descargar proyecto de demostración:

Publicado en: Variable not found.

lunes, 26 de octubre de 2009

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.