domingo 27 de noviembre de 2011

jqGrid – Listar Orden Compra (Maestro-Detalle)

 

Introducción

La mayoría de las veces los controles que incluye una herramienta de desarrollo puedo no cumplir con las expectativas que uno busca si quiere alcanzar una interfaz rica que aproveche toda la potencia de desarrollo, es por eso que se debe recurrir a componentes externos.

Esta situación suelo encontrarla al mostrar información en un grid, es por eso que jqGrid es un control ideal para potenciar el desarrollo de la interfaz del usuario en entorno web y además se trata de un componente de libre uso.

Lo único aspecto a tener en cuenta se relaciona con la necesidad de conocer algo de javascript, concretamente jquery y de ser posible invocación a webmethods para recuperar la información del grid.

Configuración

Para poder hacer uso de jqGrid es necesario introducir en el proyecto algunas librerías de javascript.

Las cuales pueden ser descargadas de la pagina jqGrid

La referencia a estas librerías podrían hacerse de dos formas:

- usando el tag <script> en cada una de las paginas donde se requiera el grid

<script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="Scripts/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script> 

<script src="Scripts/jqGrid/grid.locale-es.js" type="text/javascript"></script>
<script src="Scripts/jqGrid/jquery.jqGrid.min.js" type="text/javascript"></script>
<link href="Scripts/jqGrid/ui.jqgrid.css" rel="stylesheet" type="text/css" />

- o usando ScriptManager.RegisterClientScriptInclude(), esto es útil cuando se quiere registrar librerías para todas las paginas de forma global, aplicándolo en el código de la Master Page

public partial class SiteMaster : System.Web.UI.MasterPage
{
    protected override void OnInit(EventArgs e)
    {

        ScriptManager.RegisterClientScriptInclude(Page, typeof(Page), "jquery", ResolveUrl(@"~/Scripts/jquery-1.6.4.min.js"));
        ScriptManager.RegisterClientScriptInclude(Page, typeof(Page), "jqueryui", ResolveUrl(@"~/Scripts/jquery-ui-1.8.16.custom.min.js"));
        ScriptManager.RegisterClientScriptInclude(Page, typeof(Page), "json2", ResolveUrl(@"~/Scripts/json2.js"));
        
        ScriptManager.RegisterClientScriptInclude(Page, typeof(Page), "gridlocale", ResolveUrl(@"~/Scripts/jqGrid/grid.locale-es.js"));
        ScriptManager.RegisterClientScriptInclude(Page, typeof(Page), "jqgrid", ResolveUrl(@"~/Scripts/jqGrid/jquery.jqGrid.min.js"));

        base.OnInit(e);
    }

}

Al usarse un Master Page este podría verse afectado por la rutas relativas de las paginas, lo que ocasionaría una incorrecta resolución de la url y el acceso a los archivos .js, el método ResolveUrl() nos ayuda a evitar este problema.

 

Definición del grid

Definir el grid con las opciones básicas no es nada difícil, para separar el código de scripting del html de la pagina facilitando así el mantenimiento verán en el ejemplo que he definido 3 .js según la operación de cada uno

En este caso en concreto, se usara el “Grid.js”, el cual define el grid maestro

$("#tbOrders").jqGrid({
    datatype: 'json',
    colNames: ['Fecha Pedido', 'Fecha Solicitud', 'Direccion', 'Ciudad', 'Pais'],
    colModel: [
             { name: 'OrderDate', index: 'OrderDate', width: 100, sortable: false },
             { name: 'RequiredDate', index: 'RequiredDate', width: 100, sortable: false },
             { name: 'ShipAddress', index: 'ShipAddress', width: 250, sortable: false },
             { name: 'ShipCity', index: 'ShipCity', width: 110, sortable: false },
             { name: 'ShipCountry', index: 'ShipCountry', width: 110, sortable: false }
              ],
    height: "300px",
    onSelectRow: function (id) {

        getDetailsOrderByOrder(id);

    }
});

y un grid detalle

$("#tbDetailsOrder").jqGrid({
    datatype: 'json',
    colNames: ['Producto', 'Cantidad', 'Precio'],
    colModel: [
             { name: 'ProductName', index: 'ProductName', width: 250, sortable: false },
             { name: 'Quantity', index: 'RequiredDate', width: 100, sortable: false },
             { name: 'UnitPrice', index: 'UnitPrice', width: 100, sortable: false }
              ],
    height: "200px",
    width:"800px"
});

La configuración es bastante estándar para un uso básico, a partir de aquí hay miles de opciones, pero básicamente se define las columnas (atributo “colNames”), así como también las propiedades de cada columnas como ser el ancho de las mismas.

En el grid maestro además se define un evento, el cual enviara el id de la entidad seleccionada para cargar así el detalle, por supuesto el id que recibe como parámetro es el valor que mas adelante veremos en la estructura json devuelta por el webmethod

 

Definición de los Page Métodos

El siguiente paso será definir la información en el servidor para poder recuperar los registros que cargaran el grid

Para esto se define dos Page Methods en la propia pagina web que implementa los grid (podría usarse una pagina adicional para esta definición)

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public static string GetOrdersByCustomer(string customer)
{

    var orders = NorthwindData.GetOrdersByCustomer(customer);

    var grid = new
    {
        page = 1,
        records = orders.Count(),
        total = orders.Count(),

        rows = from item in orders
               let orderdate = item.OrderDate.HasValue ? item.OrderDate.Value.ToShortDateString() : ""
               let requireddate = item.RequiredDate.HasValue ? item.RequiredDate.Value.ToShortDateString() : ""
              
               select new
               {
                   id = item.OrderID,
                   cell = new string[]{
                       orderdate,
                       requireddate,
                       item.ShipAddress,
                       item.ShipCity,
                       item.ShipCountry,
                       item.Customers.CompanyName

                   }
               }

    };

    return JsonConvert.SerializeObject(grid);
}

 

la estructura que requiere jqGrid es un tanto especial, y gracias a los métodos anónimos es posible armarla, y como ultimo paso serializarla usando la librería JSON.NET

La estructura es bastante simple, se define la pagina, la cantidad de registros y el total, estos valores son útiles cuando el grid esta paginado, en este caso no implementamos la paginación.

Luego se definen las filas, en donde se transforma la entidad obteniendo un identidicado en el “id”, mas una propiedad “cell” que es en definitiva un array de string con la información de cada columna requiere, es importante en este punto respetar las posiciones en que debe ir cada dato con respecto a las definición de las columnas en el paso anterior.

El mismo proceso se aplica para recuperar los detalles

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public static string GetDetailsOrdersByOrder(int order)
{

    var orders = NorthwindData.GetDetailsOrdersByOrder(order);

    var grid = new
    {
        page = 1,
        records = orders.Count(),
        total = orders.Count(),

        rows = from item in orders
               select new
               {
                   id = item.OrderID,
                   cell = new string[]{
                       item.ProductsReference.Value.ProductName,
                       item.Quantity.ToString(),
                       item.UnitPrice.ToString("N2")
                   }
               }

    };

    return JsonConvert.SerializeObject(grid);
}

 

Invocar a los Page Methods

Los grid definidos no están conectados de forma directa para que estos invoquen los servicios de datos, sino que se definieron por separado para que uno desde código controle la invocación de los servicios.

En el archivos ServiceInvoke.js se encuentra la definición

function getOrdersByCustomer(customer) {

    var params = new Object();
    params.customer = customer;

    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "Default.aspx/GetOrdersByCustomer",
        data: JSON.stringify(params),
        dataType: "json",
        async: false,
        success: function (data, textStatus) {

            if (textStatus == "success") {

                $("#tbDetailsOrder").clearGridData();

                var grid = $("#tbOrders")[0];
                grid.addJSONData(jQuery.parseJSON(data.d));

            }

        },
        error: function (request, status, error) {
            alert(jQuery.parseJSON(request.responseText).Message);
        }
    });

}

 

Por medio de la línea

$("#tbDetailsOrder").clearGridData();

es que se limpian los registros del grid de detalle, ya que al recargarse el principal ya no hay un registro seleccionado.

function getDetailsOrderByOrder(order) {

    var params = new Object();
    params.order = order;

    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "Default.aspx/GetDetailsOrdersByOrder",
        data: JSON.stringify(params),
        dataType: "json",
        async: false,
        success: function (data, textStatus) {

            if (textStatus == "success") {

                var grid = $("#tbDetailsOrder")[0];
                grid.addJSONData(jQuery.parseJSON(data.d));

            }

        },
        error: function (request, status, error) {
            alert(jQuery.parseJSON(request.responseText).Message);
        }
    });

}

 

Ejemplo de Código


El ejemplo fue desarrollado con visual Studio 2008 y Sql Server 2008 R2 Express

Dentro de la carpeta “DbScript” se encuentra un .sql con la estructura de la db en caso de no poder usar el .mdf adjunto en la solución

 

[C#]
[C# SkyDrive]

12 comentarios:

Edalo dijo...

Muchas gracias Leandro x el articulo, era justo lo que necesitaba para animarme a empezar a usar jqGrid.
A propósito de tu articulo y de que esta leyendo algo de información al respecto en Internet me quedan algunas dudas, como por ejemplo con éste articulo
http://codeasp.net/articles/asp-net/229/using-jqgrid-with-asp-net y si bien es cierto que la paginación y el ordenamiento funcionan correctamente, la configuración de éste ultimo requiere de bastante código(en sql server en un procedimiento almacenado se puede incluir una cadena en la clausula order by??)

O los mismo demos de jqGrid http://www.trirand.com/blog/jqgrid/jqgrid.html (Loading Data=>JSON Data) que basándome en ellos estoy tratando de acceder a los valores de "sidx" y "sord", en un WebMethod cualquiera de esta manera HttpContext.Current.Request.Form("sidx") pero aparentemente no es posible y creo que por eso optaron x usar Controlador genérico(.ashx), no sé si tendrías idea de xq no puede acceder a ésas variables desde un WebMethod pero si desde un controlador??

Gracias de antemano

Leandro Tuttini dijo...

hola Edalo

me parece que no puedes acceder al sidx y sord porque esta es informacion que el propio jqgrid genera cuando se renderiza en el cliente
para estar seguro cuando se carga en el browser el grid ve al codigo fuente del mismo y busca

es mas si analisas las opciones

http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options

veras que en el "prmNames" tienes esta informacion, solo sera cuestion desde jquery tomar la informacion y enviarla por parametro el webmethod

de forma automatica se enviar en la url, por eso el Request en el handler funcion, lo hace directo el jqgrid
pero al usar algo personalizado sin tratar la url debes recuperarlo previamente para poder enviarlo

podrias usar

var pageoptions = $("#grid").getGridParam("prmNames");

esto debolvera un array con todas las opciones que intervienen en la paginacion

si obtienes el sidx, puedes enviarlo en al webmethod


saludos

Edalo dijo...

Buenas Leandro, al parecer ya lo tengo funcionando como quería, creo que me deje llevar de los demos del jqGrid (en php) donde los valores de "sidx" y "sord" si se pueden recuperar directamente. ¿Crees que me podrías explicar en breve que hace que un controlador genérico sí pueda tener tener acceso a ésos valores??, tampoco me queda muy claro la parte en la que señalas :"usar algo personalizado sin tratar la url"

Bueno en cuanto a lo que hice para recuperar los valores de "sidx" y "sord" utilicé : $("#tbDetailsOrder").getGridParam("postData") "postData" contiene los datos necesarios para la ordenación y paginación, "prmNames" sirve para personalizar los nombres de estos valores.

En cuanto a lo de la ordenación como señalas en el articulo al no estar los grids conectados directamente,como sí sucede en el articulo donde usan un controlador genérico http://codeasp.net/articles/asp-net/229/using-jqgrid-with-asp-net, tengo que invocar manualmente el evento onSortCol http://www.trirand.com/jqgridwiki/doku.php?id=wiki:events y obviamente también modifique los WebMethods : getOrdersByCustomer y getDetailsOrderByOrder agregándole los parámetros necesarios para el ordenamiento y paginación, y eso es más o menos a grandes rasgos lo que tuve que hacer.

Otra vez muchas gracias Leandro ya hacía varios meses que quería usar jqGrid pero no encontraba un ejemplo práctico en asp.net que me sirviera como punto de inicio en el uso de este plugin y de verdad que en ese sentido tu articulo me ha caído como bajo de cielo :D literalmente

Saludos

Leandro Tuttini dijo...

hola

Por controlador generico te refieres a un handler ? porque si es asi recuerda que este se comporta similara a una pagina web (puede acceder al request y response) solo que se programa desde codigo (sin un diseñadro html) y se define la extension que usa en el web.config

en el articulo donde grabo imagenes en uan db los utilizo como medio para obtener estos archivos desde la db y enviarlo en el response


que bueno que con el postData pudiste recuperar esta informacion para enviar el servicios

saludos

Angelito dijo...

Que onda Leandro, oye tengo una duda, fijate que no puedo visualizar al final de la funcion ajax el resultado del post del json, grid.addJSONData(jQuery.parseJSON(data.d)); exactamente ahí. le agregue un alert antes de esa linea y me regresa el siguiente valor {"ID":"1","Name":"ANGEL MARTIN"} alguna idea de ´por que el el grid no me pinte los valores?

Leandro Tuttini dijo...

hola Angelito

no entendi como se relaciona el final de la funcion ajax, con el alert que agregas y en el grid no pinte los valores

parecieran temas diferentes los que alli planteas, es mas por lo que comentas en el alert recibes un json de respuesta, aunque parece ser un solo item

saludos

Angelito dijo...

Ok Leandro, en la funcion getOrdersByCustomer() me imagino que es donde hace la asignacion de los valores devueltos por el webmethod, pero mi grid no muestra los valores devueltos por el json le puse un alert antes de parsear (grid.addJSONData(jQuery.parseJSON(data.d); ) y me muestra lo que te mencionaba antes ( [{"ID":"1","Name":"ANGEL MARTIN"}] ), osea que por el lado del codebehind no tengo problemas. No se si me hace falta algun archivo o alguna version en especifica por las funciones de parseo.

Leandro Tuttini dijo...

hola Angelito

pero esto que mencionas
[{"ID":"1","Name":"ANGEL MARTIN"}]
eso es una respuesta json, o sea estas recuperando algo en formato json desde el webmethod

quizas deberias valida que este webmethod que invocas devuelve la entidad con los datos que necesitas, pero devolver un registro al menso revuelve, por eso que comentas es json

podrias poner un breakpoint en el webmethod y ver como se crea la entidad que retornas

saludos

Unknown dijo...

Muy buen Blog

wilfred martinez garcia dijo...

Es muy buena la publicacion, a mi me sirve para empezar a utilizar este control, a mi me da un poco de cacao al tratar de hacer una pagina sencilla, ya que siempre he practicado en visual basic 6 y me propuse a desarrollar una pagina de lo mas sencilla. La pregunta es si este ejemplo puedo encontrarlo en vb.net

wilfred martinez garcia dijo...

Mi agradecimiento Leandro tuttini

Leandro Tuttini dijo...

hola wilfred martinez garcia

a veces publico articulos tanto en c# como vb.net, pero algunos por la complejidad solo los dejo en el lenguaje del cual son MVP, o sea c#

igualmente si la idea es usar este control tienes mucho de javascript, mas puntualmente jquery, por lo que vas a tener que aprender la notacion de c#, ya que javascript y c# en la notacion son similares

por lo tanto conocr vb.net, c# y javascript seguro debas hacerlo
sin olvidad html

una recomendacion si vas a ingresar al desarrollo web no seria mejor si comienzas con algo mas basico de asp.net, para aprender conceptos en lugar de arrancar con componentes jquery

saludos