martes, 29 de mayo de 2012

[ASP.NET] - GridView sumar columnas con jquery

 

Introducción

Cuando se trabaja integrando código cliente (javascript) con asp.net seguramente en algún punto se necesite realizar operaciones con tablas, siendo el control GridView  usado en la mayoría de estos casos, es por eso se intentara mostrar las diferentes formas en que se puede realizar operaciones con código cliente usando jquery para trabajar con tablas

Para el ejemplo se listara productos con sus respectivos precios, el usuario completara la cantidad que se solicita de ese producto, realizando el calculo del precio final que deberá abonarse.

 

Calculo del precio por producto

La primer operación que debe programarse corresponde al calculo a nivel de fila, o sea de cada producto. La implementación completa podrá verse en el código dentro de al carpeta “Ej1”.

Como se observa en la imagen la columna del precio unitario es fija, el usuario ingresa una cantidad y al quitar el foco del textbox automáticamente se calcula el precio.

Para lograrlo será necesario adjuntar un evento de cambio a cada textbox del gridview, recordemos que el control termina renderizado como una tabla html, por lo tanto, se puede trabajar en el cliente con el html resultante.

<script language="javascript" type="text/javascript">

    $(document).ready(function() {

        $("#<%=GridView1.ClientID%> [id*='txtCantidad']").change(function() {
        
            var tr = $(this).parent().parent();
            var precio = $("td:eq(1)", tr).html();

            $("td:eq(3) span", tr).html($(this).val() * precio);

        });

    });
    
</script>

Ante un evento se recupera el textbox que lanza la acción, es por eso que se utiliza $(this), el this en este caso es el textbox, se sube dos niveles en la estructura del html, o sea a nivel del <tr> que contiene a ese textbox, para luego aplicar la selección a otra columna, en este caso la del precio, por eso se utiliza td:eq(1) seria la segunda columna (recordar que los índices comienzan en cero)

El ultimo paso será asignar el resultado, se aplica la misma técnica, se selecciona la columna y por tratarse de un label de asp.net el cual renderiza a <span> se usa este selector en jquery.

Si bien en la implementación puede observarse solo una única forma para asignar el evento a los textbox contenido en la columna de gridview pueden ser usados otro selectores de jquery dependiendo la necesitad:

 

Usando el atributo class

En este caso el selector hace uso de un atributo del control para poder localizarlo, todos los textbox que el gridview genere tendrán el mismo atributo “class”

$('#<%=GridView1.ClientID%> .cantidad').change(function() { ….

Es necesario definir este atributo en el control del témplate del grid

Usando un doble selector

En este caso se aplicara una doble selección, primero se selecciona lo definido a la derecha y sobre esto aplica lo definido en al izquierda.

$('td:eq(2) :text', '#<%=GridView1.ClientID%> tr').change(function() {

La parte derecha selecciona todas las filas del grid, mientras que la izquierda toma solo la tercer columna (Cantidad Pedido), el uso de “:text” define que solo los textbox de la celda serán incluidos

 

Usando el operador like *=

Por medio del operador de selección mediante búsqueda aproximada, se puede tomar todos aquellos controles que tengan un misma patrón en su id

$("#<%=GridView1.ClientID%> [id*='txtCantidad']").change(function() {

En el caso del gridview creara un control por cada dato que se le asigne, repitiendo el textbox N veces, pero todos tendrán la palabra txtCantidad en alguna parte de su id

 

Calculo del precio Total

Calcular el precio total, implica sumar una columna completa para tener el precio total que se sumara en el footer del gridview. La implementación completa podrá verse en el código dentro de al carpeta “Ej2”.

El añadido que se realiza a la suma de la fila del grid solo implica la llamada a una funciona para recalcular el total.

Un punto conflictivo cuando se suma una columna es el hecho de paginar la grid, vamos a ver en este caso como se lograría una implementación con y sin paginado

 

Calculo total sin paginado

Al no paginar se puede reconocer el footer como ultima fila de la tabla pudiendo aplicar selectores que tengan en cuenta esta situación.

function CalcularTotal() {

    var total = 0;
    $('#<%=GridView1.ClientID%> tr:not(:last)').each(function() {
    
        var coltotal = parseFloat($("td:eq(3) span", this).html());
        
        if (!isNaN(coltotal)) {
            total += coltotal;
        }

    });

    $('#<%=GridView1.ClientID%> tr:last td:eq(3) span').html(total);
}

Se puede hacer uso de “:last” para agregar o descartar esta ultima fila del grid donde se presentara el total.

Se recorre todas las filas del gridview, dejando fuera el footer, por eso es “:not(:last)”, se esta descartando el ultimo <tr>. Pero si se toma exactamente la ultima fila cuando se quiere asignar el total resultante, por eso se usa el “tr:last”

Nota: por supuesto hay que quitar la propiedad AllowPaging="True" del gridview para probarlo sin paginado

 

Calculo total con paginado

Al tener paginado la ultima final ya no será el footer, por lo tanto usar el :last no funciona, se requiere otras técnicas de selección

function CalcularTotal() {

    var total = 0;
    $("#<%=GridView1.ClientID%> [id*='lblPrecio']").each(function() {

        var coltotal = parseFloat($(this).html());

        if (!isNaN(coltotal)) {
            total += coltotal;
        }

    });

    $("#<%=GridView1.ClientID%> [id*='lblTotal']").html(total);

}

 

Se decide ir directamente a los controles que contienen la información.

Al tomar el valor del <span> (que representa el lblPrecio), puede que este no sea un valor numérico valido, es por eso que se parsea a float, si devuelve NaN es porque no es un numero que sumar.

 

Código

Se ha usado Visual Studio 2008, es necesario SP1 del VS, como base de datos Sql Compact 3.5

 

jueves, 17 de mayo de 2012

[Winforms] TreeView– Cambiar Estado Node con CheckBox

 

Introducción

Muchas veces es necesario cambiar el estado de ítems predecesores o hijo según ciertos criterios, el control TreeView por posee una estructura jerárquica en sus datos es un caso de esta situación.

 

Validar estado de los nodos


En la implementación de código que se puede observar aplica conceptos simples como de validación de estados de los nodos del treeview.

Básicamente consiste en dos parte

  •    validar si el nodo tiene un nodo padre, para lo cual será necesario verificar si todos los hijos están marcados
  •    cambiar el estado a todos los hijos según la marca de este nodo

Estas acciones se realizan sobre el mismo evento ya que implica de forma contextual al nodo que interviene en la acción

 

private void tvCategoryproducts_AfterCheck(object sender, TreeViewEventArgs e)
{
    //
    // Se remueve el evento para evitar que se ejecute nuevamente por accion de cambio de estado 
    // en esta operacion
    //
    tvCategoryproducts.AfterCheck -= tvCategoryproducts_AfterCheck;

    //
    // Se valida si el nodo marcado tiene presedente
    // en caso de tenerlo se debe evaluar los nodos al mismo nivel para determinar si todos estan marcados, 
    // si lo estan se marca tambien el nodo padre
    //
    if (e.Node.Parent != null)
    {
        bool result = true;
        foreach (TreeNode node in e.Node.Parent.Nodes)
        {
            if (!node.Checked)
            {
                result = false;
                break;
            }
        }

        e.Node.Parent.Checked = result;

    }

    //
    // Se valida si el nodo tiene hijos
    // si los tiene se recorren y asignan el estado del nodo que se esta evaluando
    //
    if (e.Node.Nodes.Count > 0)
    {
        foreach (TreeNode node in e.Node.Nodes)
        {
            node.Checked = e.Node.Checked;
        }
    }


    tvCategoryproducts.AfterCheck += tvCategoryproducts_AfterCheck;

}

 

Optimización usando Linq

Linq nos puede evitar tener que escribir un montón de código, sino lo creen solo basta con dar una mirada a la implementación alternativa que evalúa los nodos marcados

En este caso hice uso de los método de extensión ayudado con lambda, pero se podría haber usado la notación linq sin problemas

 

private void tvCategoryproducts_AfterCheck_UsingLinq(object sender, TreeViewEventArgs e)
{
    //
    // Se remueve el evento para evitar que se ejecute nuevamente por accion de cambio de estado 
    // en esta operacion
    //
    tvCategoryproducts.AfterCheck -= tvCategoryproducts_AfterCheck_UsingLinq;

    //
    // Se valida si el nodo marcado tiene presedente
    // en caso de tenerlo se debe evaluar los nodos al mismo nivel para determinar si todos estan marcados, 
    // si lo estan se marca tambien el nodo padre
    //
    if (e.Node.Parent != null)
    {
        e.Node.Parent.Checked = !e.Node.Parent.Nodes.Cast<TreeNode>().Any(x => !x.Checked);
    }

    //
    // Se valida si el nodo tiene hijos
    // si los tiene se recorren y asignan el estado del nodo que se esta evaluando
    //
    if (e.Node.Nodes.Count > 0)
    {
        e.Node.Nodes.Cast<TreeNode>().ToList().ForEach(x => x.Checked = e.Node.Checked);
    }


    tvCategoryproducts.AfterCheck += tvCategoryproducts_AfterCheck_UsingLinq;

}

Código


Se utilizo Visual Studio 2010 y Sql Compect 3.5

  [C# SkyDrive]

lunes, 30 de abril de 2012

[ASP.NET] PopUp Filtro – usando window.open()

 

Introducción

El objetivo del artículo apunta a demostrar como implementar la búsqueda de un determinado ítem haciendo uso de ventanas que se abran en popup.

Para la implementación se utilizara la funcionalidad de window.open() provista por javascript.

El ejemplo consiste en un listado de producto, pudiendo ser filtrados mediante una ventana popup que lista los proveedores.

El código fue implementado de dos formas distintas, si bien son muy parecidas tienen sutiles cambios, estas dos formas las denomine:

  • Selección con evento
  • Selección sin evento

Imagino se preguntaran del porque de esta diferencia del uso de un evento o no, básicamente difieren en el uso de controles asp.net (con evento) o tag de html (sin evento), cada alternativa cambia la forma en que se implementa la solución, en concreto la forma en que se toma el ítem elegido.

Empezaremos analizando la parte de la implementación que es común en ambos casos para luego ir a los detalles que afectan el usar un control de asp.net o uno de html

Abrir ventana de búsqueda

La definición del botón de búsqueda se logra adjuntando el evento click de jquery al tag <input>, como se refleja en la imagen

Esta acción lanzara la ventana de búsqueda que se ha implementado mediante el uso de window.open()

 

Filtro de productos

Una vez seleccionado el proveedor en la ventana de búsqueda esta acciona, o ejecuta, el botón de filtro, el cual se ha implementado, en en la pagina original, es decir la ventana hija invoca un boton en la ventana padre.

 

protected void btnFiltrar_Click(object sender, EventArgs e)
{
    LoadGridProducts();

    //
    // muestro la descripcion del proveedor
    //
    Suppliers supplier = NorthwindData.GetSupplierById(txtId.Text);

    string CompanyName = "";
    if (supplier != null)
    {
        CompanyName = supplier.CompanyName.Encode();
    }

    //
    // asigna el nombre al label que esta fuera del updatepanel
    //
    string script = @"$('#{0}').text('{1}');";
    script = string.Format(script, lblSupplierName.ClientID, CompanyName);

    ScriptManager.RegisterStartupScript(this, typeof(Page), "filterinfo", script, true);

}
private void LoadGridProducts()
{
    int? SupplierId = null;

    int id = 0;
    if (Int32.TryParse(txtId.Text, out id))
    {
        SupplierId = id;
    }
    

    gvProducts.DataSource = NorthwindData.GetProductsBySupplier(SupplierId);
    gvProducts.DataBind();
}

Algo que quizás no parezca tener sentido al principio es porque el nombre del proveedor seleccionado es asignado mediante código javascript (o mejor dicho jquery), en lugar de hacerlo directamente, bueno esto se deba al uso del control UpdatePanel, el label donde se asigna el nombre del proveedor seleccionado esta por fuera del panel lo cual requiere que al asignar un valor sea realice de forma indirecta.

 

Selección sin evento (usando html <img>)

Empezaremos analizando como realizar la selección usando un control simple de html, en este caso en el grid de proveedores se usa el tag <img> dentro del item template

Pro medio de la ayuda de jquery se asocia el evento click, usando el atributo class del <img> como selector

<script type="text/javascript">

    $(function() {

        $('#<%=gvSuppliers.ClientID %> .imgSelection').click(function() {

            var supplierID = $(this).attr('SupplierID');
            
            var tr = $(this).parent().parent();
            var companyName = $('td:eq(2)', tr).text();

            window.opener.$("[id*='txtId']").val(supplierID);
            window.opener.$("[id*='lblSupplierName']").text(companyName);

            window.opener.$("[id*='btnFiltrar']").click();

            window.close();
        })

    });

</script>

Como el evento lo lanza el propio tag <img> se puede acceder a este mediante $(this) tomando el atributo, pero para recuperar información adicional debe subirse hasta el tag <tr> de la tabla que genera el gridview para así poder acceder a los datos de las celdas, el td:eq(2), hace referencia a la segunda columna del grid

La integración de jquery con javascript nos permite realizar una búsqueda en la página padre usando window.opener, y mediante el *= sobre el id, una búsqueda aproximada, similar a usar el like.

en las ultimas dos líneas se lanza el evento click del boton de filtro y se cierra la ventana.

 

Selección con evento (uso control asp.net ImageButton)

A diferencia del caso sin evento, en este se hace uso de un control de asp.net para seleccionar el proveedor del grid, como se puede observar en la imagen

Este simple cambio impacta en la forma en que se implementa el resto

<script type="text/javascript">

    function SupplierSelected(supplierID, companyName) {

        window.opener.$("[id*='txtId']").val(supplierID);
        window.opener.$("[id*='lblSupplierName']").text(companyName);

        window.opener.$("[id*='btnFiltrar']").click();

        window.close();
    }

</script>

El javascript usado se reduce, ya no se implementa la búsqueda en al tabla del html que genera el gridview, pues se dispone acceso directo al control desde código .net

protected void gvSuppliers_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    
    int supplierId = Convert.ToInt32(gvSuppliers.DataKeys[e.NewSelectedIndex].Value);

    string companyName = gvSuppliers.Rows[e.NewSelectedIndex].Cells[2].Text;


    string script = @"$(function(){{
                        SupplierSelected('{0}','{1}');
                    }});"; //se define {{ para poder usar el string.Format()

    script = string.Format(script, supplierId, companyName.Encode());

    ScriptManager.RegisterStartupScript(Page, typeof(Page), "popupclose", script, true);

}

La asignación del valor de retorno no es tan directa, sino que debe ser escrita en el retorno del postback del evento, esto es necesario ya que el window.opener solo puede usarse desde código cliente.

 

Código
[C#]
 

[ASP.NET] PopUp Filtro – Usando jquery UI Dialog

 

Introducción

El objetivo del artículo apunta a demostrar como implementar la búsqueda de un determinado ítem haciendo uso de ventanas que se abran en popup.

Para implementarlo se hará uso de la librería jquery UI, la cual incluye funcionalidad para crear cuadros de dialog modales.

El código fue implementado de dos formas distintas, si bien son muy parecidas tienen sutiles cambios, estas dos formas las denomine:

  • Selección con evento (usando un ImageButton)
  • Selección sin evento  (usando el tag html <img>)

Imagino se preguntaran del porque de esta diferencia del uso de un evento o no, básicamente difieren en el uso de controles asp.net (con evento) o tag de html (sin evento), cada alternativa cambia la forma en que se implementa la solución, en concreto la forma en que se toma el ítem elegido.

Empezaremos analizando la parte de la implementación que es común en ambos casos para luego ir a los detalles que afectan el usar un control de asp.net o uno de html

 

Abrir ventana de filtro

La definición de la ventana por medio de jquery UI solo implica asociar al <div> que representara el dialog

 

$(function() {

    $('#btnOpenSupplierSearch').click(function() {

        registerGridEvent();
        
        $('#dialog').dialog('open');

    });

    $('#dialog').dialog({
        autoOpen: false,
        modal: true,
        width: 700,
        heigth: 250,
        title: 'Buscar Proveedor',
        buttons: {
            "Cancel": function() {
                $(this).dialog("close");
            }
        }
    });

});

En este caso será por medio de una acción del botón de búsqueda que se abrirá la ventana.

 

Selección sin evento (usando html <img>)

Es muy importante en este punto notar como se asocia el <img> que representa la selección de cada row del grid con su correspondiente acción en javascript.

En esta acción desde javascript no solo se toma la información del html, sino que además se cierra el dialog y se lanza el evento de búsqueda accionado el botón que tiene esta funcionalidad.

 

function registerGridEvent() {

    $('#<%=gvSuppliers.ClientID %> .imgSelection').click(function() {

        var supplierID = $(this).attr('SupplierID');

        var tr = $(this).parent().parent();
        var companyName = $('td:eq(2)', tr).text();

        //asigno datos a los controles
        $("[id*='txtId']").val(supplierID);
        $("[id*='lblSupplierName']").text(companyName);

        //cierro el popup de seleccion
        $('#dialog').dialog("close");
        
        //aplico el filtro
        $("[id*='btnFiltrar']").click();
    })

}

Re-asignación de evento jquery

Existe un punto de conflicto entre los eventos de asp.net y jquery, espacialmente cuando se usa el UpdatePanel para conservar los eventos y se mezclan acción de jquery.

Estos dos mundos no se llevan muy bien, un claro ejemplo se podría apreciar con el paginado del gridview, si bien al ir cambiando de pagina no se nota el refresco ya que el UpdatePanel lo hace simple, por debajo se genera una invocación al servidor para ejecutar del evento asociado al grid.

 

protected void gvSuppliers_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    gvSuppliers.PageIndex = e.NewPageIndex;

    LoadGridSuppliers();

    //
    // registro el evento click del boton, ya que al pagina el postback 
    // quita la asociacion del evento que jquery
    //
    string script = "registerGridEvent();";

    ScriptManager.RegisterClientScriptBlock(this, typeof(Page), "registerevent", script, true);

}

Jquery se ve afectado si es que en ese bloque se tenia eventos asociados en algún tag, en este caso concreto el tag <img> esta asociado al evento click() de jquery, por lo tanto hay que volver a re-asociar para que se revalide la acción.

Es por eso que en el evento de paginado en el server se lanza la acción en el cliente por medio del

ScriptManager.RegisterClientScriptBlock()

Esto es necesario para que a la vuelta del postback se ejecute la function javascript y asigne nuevamente el evento click() de jquery al selector definido.

 

Selección con evento (uso control asp.net ImageButton)

En este caso el camino tiene algunas vueltas entres server y cliente:

De la acción del ImageButton se ingresa al evento SelectedIndexChanged en el código .net, quien luego de tomar la información del gridview armara el script que asignara la info, cerrando el dialogo y por ultimo lanzando la acción de botón para filtrar los productos.

 

protected void gvSuppliers_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{

    int supplierId = Convert.ToInt32(gvSuppliers.DataKeys[e.NewSelectedIndex].Value);

    string companyName = gvSuppliers.Rows[e.NewSelectedIndex].Cells[2].Text;


    string script = @"$(function(){{
                        SupplierSelected('{0}','{1}');
                    }});"; //se define {{ para poder usar el string.Format()

    script = string.Format(script, supplierId, companyName.Encode());

    ScriptManager.RegisterStartupScript(Page, typeof(Page), "popupclose", script, true);

}

 

function SupplierSelected(supplierID, companyName) {

    $("[id*='txtId']").val(supplierID);
    $("[id*='lblSupplierName']").text(companyName);

    //cierro el popup de seleccion
    $('#dialog').dialog("close");

    //aplico el filtro
    $("[id*='btnFiltrar']").click();
    
}

Un punto que no hay que pasar por alto que afecta al evento del control dentro del dialog, jquery reasigna las acciones del dialogo a nivel del body y no del form, por eso línea cambia esa acción de esta forma los eventos de asp.net se habilitan nuevamente.

 

Código
[C#]
 

domingo, 29 de abril de 2012

[ASP.NET] PopUp Filtro – Usando Ajax Toolkit ModalPopupExtender

 

Introducción

En esta oportunidad analizaremos como implementar un filtro usando los controles de ajax tookit, mas concretamente el control ModalPopupExtender

Se hará uso de una lista de productos la cual será filtrada por proveedores, desplegados en el popup.

Algo que notaran en el ejemplo será dos formas de implementar la selección del proveedor:

- en una se contara con al ayuda de ToolkitScriptManager para registrar la información seleccionada, será necesario un .js (ListProducts.js), encargado de tomar la info y asignarla a los controles

- la otra será una asignación directa usando jquery (esta ultima seria la recomendable, porque requiere menos objetos involucrados en su implementación)

 

Abrir ventana de búsqueda

La apertura de la ventana de filtro será realizada por medio de un evento asp.net, pero para poder hacerlo es necesario deshabilitar el javascript que asocia el botón con el ModalPopupExtender.

Para lograrlo es que se asigna la propiedad TargetControlID a un objeto hidden, como muestra la imagen

Esto permite que desde el botón se pueda cargar los ítems en el gridview que formara parte del popup de filtro, si se hubiera asignado directo el botón al ModalPopupExtender, este deshabilita los evento de asp.net, por lo que la carga del grid no se hubiera podido llevar a cabo.

 protected void btnOpenSupplierSearch_Click(object sender, EventArgs e)
 {
     LoadGridSuppliers();

     ModalPopupExtender1.Show();

 }

private void LoadGridSuppliers()
 {
     gvSuppliers.DataSource = NorthwindData.GetAllSuppliers();
     gvSuppliers.DataBind();
 }

El uso del Show() sobre el ModalPopupExtender abrirá la ventana, luego de cargar el grid que contiene.

 

Selección ítem en el popup

La selección del proveedor será implementada por medio de un botón de imagen de asp.net, el cual lanzara el evento del gridview, es por eso que se asocia el CommandName=”Select”

La acción de este evento podría ser implementada de dos formas:

Usando al funcionalidad de ToolkitScriptManager

En la implementación de esta opción puede implicar varios puntos a tener en cuenta, el primero involucra hacer uso del ToolkitScriptManager para asignar la información que se quiere devolver como resultado de la selección

protected void gvSuppliers_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    string id = Convert.ToString(gvSuppliers.DataKeys[e.NewSelectedIndex].Value);
    string name = gvSuppliers.Rows[e.NewSelectedIndex].Cells[2].Text;

    //
    // asigno la informacion a los controles que estan fuera de updatepanel
    //
    ToolkitScriptManager1.RegisterDataItem(txtId, id);
    ToolkitScriptManager1.RegisterDataItem(lblSupplierName, name);


    //
    // Cargo grid de productos con el dato seleccionado
    //
    LoadGridProducts(id);

    //
    // cierro el popup
    //
    ModalPopupExtender1.Hide();
}

 

Pero además implica hacer uso de JavaScript para poder tomar la información enviada en el RegisterDataItem(), es por eso que se asocia un .js que cumplirá esta función

<asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/ListProducts.js" />
    </Scripts>
</asp:ToolkitScriptManager>

El contenido del .js implementa el siguiente código

Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(SupplierFilterHandler);

function SupplierFilterHandler(sender, args) {
    var datos = args.get_dataItems();

    //obtenermos el id geenrado por asp.net, o sea el ClientID
    var txtId = $("[id*='txtId']").attr('id');
    var lblSupplierName = $("[id*='lblSupplierName']").attr('id');
    

    if (datos[txtId] == undefined)
        return;

    $("[id*='txtId']").val(datos[txtId]);
    $("[id*='lblSupplierName']").text(datos[lblSupplierName]);

}

La idea es poder tomar la información enviada desde el código .net desde el cliente y asignarla a los controles todo ello desde código cliente, sin mezclar parte de .net con parte javascript, separando cada responsabilidad

Se usa el *= ya que el id del control es generado por asp.net (el cual puede cambiar del asignado desde el diseñador), por eso se ubica el nombre real generado, ya que no se disponer del ClientID para tomarlo desde el .js

 

Usando directamente jquery

Este seria el camino directo para asignar la información del filtro en la pagina principal

protected void gvSuppliers_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    string id = Convert.ToString(gvSuppliers.DataKeys[e.NewSelectedIndex].Value);
    string name = gvSuppliers.Rows[e.NewSelectedIndex].Cells[2].Text;

    //
    // asigno la informacion a los controles que estan fuera de updatepanel
    //
    string script = @" $(""[id*='txtId']"").val('{0}');
                       $(""[id*='lblSupplierName']"").text('{1}');";

    script = string.Format(script, id, name.Encode());

    ScriptManager.RegisterStartupScript(this, typeof(Page), "filterinfo", script, true);

    //
    // Cargo grid de productos con el dato seleccionado
    //
    LoadGridProducts(id);

    //
    // cierro el popup
    //
    ModalPopupExtender1.Hide();
}

 

En esta implementación solo es necesario definir el script que se encargara de asignar los datos a los controles y enviarlo al cliente por medio del ScriptManager.RegisterStartupScript().

Esto es mucho mas directo y sin vueltas que el paso anterior, aunque requiere generar código javascript (ayudado con jquery) inserto en medio del con código .net en el servidor, a veces puede gustar, a veces no, mezclar este tipo de código, pero si hay que reconocer que es el camino directo en su implementación.

 

Código
[C#]
 

lunes, 2 de abril de 2012

ASP.NET–GridView anidados (maestro-detalle) (GridView nested)

 

Introducción

El objetivo en el articulo será el demostrar como armar un grid con jerarquía, en este caso mostrando las ordenes de compra de un cliente

Además se pretende demostrar como se puede armar una entidad compleja que retorne los datos en una estructura anidada de listas

 

Detectar despliegue detalle

Uno de los puntos mas importantes en la implementación esta relacionado con la forma en que se detecta el expandir\colapsar del detalle

En la imagen se remarcan dos atributos creados con este propósito, la idea es poder relacionar la imagen con el tag <tr> que permite ocultar o mostrar el grid del detalle

La idea detrás de esto es ayudar luego a jquery para poder determinar que elementos intervienen en la acción

con jquery se extrae el id de la orden, para luego usarlo de selector del <tr> que posee ese mismo id

el toggle() solo permite switchear entre visible\invisible, y detectando el estado mostrar una imagen u otra

 

Estructura de datos jerárquica

La estructura usada para el modelo relaciona tres clases

La idea es mostrar como al cargar los datos, es analizar como los métodos se invocan unos a otros para recuperar la info enlazada en al jerarquía

 

Código

El codigo fue confeccionado con Visual Studio 2008 y base de datos Sql Compact 3.5

[C#]
[C# SkyDrive]

martes, 20 de marzo de 2012

jqGrid–Crear jerarquía de registros con Tree Grid

 

Introducción

La finalidad buscada con el articulo articulo será demostrar como por medio del componente jqGrid se pueden representar estructuras de árbol en diferentes niveles de agrupación

El ejmeplo esta basado en la ordenes de compra de la db Northwind, tomando el campo “OrderData” y agrupando por año y mes

Es por eso que la opciones de expandir primero mostrarán los años, luego los mese y al final la info de la orden.

Se puede dejar el campo de Customer sin ingresar un valor, lo cual listara todas las ordenes registradas

Es muy importante notar en el código como se crea el grid que se enviara por json al grid

var grid = new
{
    page = 1,
    records = orderList.Count,
    total = orderList.Count,

    rows = from item in orderList
            select new
            {
                id = item.IdUnique,
                cell = new string[]
                {
                    item.Id,
                    item.Desc,
                    item.Level.ToString(),
                         item.Parent,
                         item.IsLeaf.ToString().ToLower(),
                        "false"
                }
            }

 

};

luego de los campos de datos se debe definir en orden los siguiente ítems:

  • nivel
  • parent
  • si es un nodo hoja
  • si debe mostrarse expandido el nodo

 

Código


El ejemplo esta confeccionado con VS 2010 y Sql Compact (la db se encuentra en la carpeta App_Data)

[C#]
[C# Skydrive]

lunes, 27 de febrero de 2012

[ASP.NET] Mantener información al cambiar de pagina

 

Introducción

Cuando se desarrolla pantallas de captura de datos por lo general una característica deseable implica que no se pierda los datos cargados cuando se navega a otro sitio a buscar información y se vuelve.

Lamentablemente asp.net no conserva de forma automática el estado de esta información ingresada, por esa razón seremos nosotros quienes mediante código que volquemos la info ingresada para no perderla y facilitar al usuario al operación con la aplicación.

En el ejemplo planteado se cuenta con una lista de productos, con la posibilidad de ingresa una cantidad y opciones provistas para completar la operación.

 

 

Mantener el estado de los datos ingresados

Si bien esta funcionalidad es muy similar a la implementada para mantener el estado de los check, cuenta con algunas diferencias que hay que remarcar.

En este caso se conserva algo mas de información que una simple estado de true/false, es por eso que se crea una clase para mantener los datos que son útiles.

internal class ProductInfo
{
    public int Id { get; set; }
    public int Amount { get; set; }
    public int Shipper { get; set; }
}

Es desde y hacia esta clase que se realizaran las conversiones.

El mantener la información consta de varios pasos:

  1. se recuperan del gridview solo aquellas rows en donde se ingresara una cantidad y un medio de envió. Se arma en este caso una entidad ProductInfo representativa de los datos que se quiere conservar
  2. se recupera la información previamente registrada en la session
  3. se cruzan las listas para quitar las que están en la pagina actual
  4. se ingresa la nueva información para conservar en session

 

public static void KeepProductInfo(GridView grid)
{
    //
    // se obtienen la lista de producto con informacion proporcionada por el usuario
    //
    var listProd = from item in grid.Rows.Cast<GridViewRow>()
                        let amount = ((TextBox)item.FindControl("txtAmount")).Text
                        let shipper = ((DropDownList)item.FindControl("ddlShippers")).SelectedValue
                    where !(string.IsNullOrEmpty(amount) || shipper == "0")
                    select new ProductInfo()
                    {
                        Id = Convert.ToInt32(grid.DataKeys[item.RowIndex].Value),
                        Amount= Convert.ToInt32(amount),
                        Shipper = Convert.ToInt32(shipper)
                    };

    //
    // se recupera de session la lista de seleccionados previamente
    //
    List<ProductInfo> prodInfo = HttpContext.Current.Session["ProdInfo"] as List<ProductInfo>;

    if (prodInfo == null)
        prodInfo = new List<ProductInfo>();

    //
    // se cruzan todos los ingresados en la pagina actual, con los previamente conservados 
    // en Session, devolviendo solo aquellos donde no hay coincidencia
    //
    prodInfo = (from item in prodInfo
                 join item2 in listProd
                    on item.Id equals item2.Id into g
                 where !g.Any()
                 select item).ToList();

    //
    // se agregan la actualizacion realizada por el usuario
    //
    prodInfo.AddRange(listProd);

    HttpContext.Current.Session["ProdInfo"] = prodInfo;

}

 

Por supuesto esta misma técnica puede ser usada para conservar cualquier tipo de dato de cualquier pantalla quiere se quiera evitar perder lo ingresado, se podría haber mantenido la info de textbox simple, o de un checkboxlist, combos, etc.

En este caso se uso un gridview porque es un objeto representativo de información compleja para procesar.

 

Recuperar la info de los datos conservados en session

La acción de recuperar se podría resumir en tres paso

  1. recuperar la información de session
  2. cruzar las row del gridview de la pagina actual con la lista de productos existente
  3. volcar las coincidencias en los controles textbox y dropdownlist

 

public static void RestoreProductInfo(GridView grid)
{

    List<ProductInfo> prodInfo = HttpContext.Current.Session["ProdInfo"] as List<ProductInfo>;

    if (prodInfo == null)
        return;

    //
    // se comparan los registros de la pagina del grid con los recuperados de la Session
    // los coincidentes se devuelven para ser seleccionados
    //
    var result = (from item in grid.Rows.Cast<GridViewRow>()
                  join item2 in prodInfo
                      on Convert.ToInt32(grid.DataKeys[item.RowIndex].Value) equals item2.Id into g
                  where g.Any()
                  select new
                  {
                      gridrow = item,
                      prodonfo = g.First()
                  }).ToList();

    //
    // se recorre cada item para asignar la informacion 
    //
    result.ForEach(x =>
    {
        ((TextBox)x.gridrow.FindControl("txtAmount")).Text = Convert.ToString(x.prodonfo.Amount);
        ((DropDownList)x.gridrow.FindControl("ddlShippers")).SelectedValue = Convert.ToString(x.prodonfo.Shipper);
    });
    
}

 

Uso de la funcionalidad implementada

Si bien al momento de conservar los datos ante un paginado se contaba con eventos concretos pre y post para llevar la acción de conservar o recuperar los datos, en este caso también se deberá hallar momentos equivalentes para los cuales volcar los datos.

 

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        LoadGridProducts();
        
        //
        // lusgo de cargar el grid se asigna la info que el usuario habia ingresado
        //
        ProductsManager.RestoreProductInfo(gvProducts);

    }
}

private void LoadGridProducts()
{
    gvProducts.DataSource = NorthwindData.GetAllProducts();
    gvProducts.DataBind();
}


protected void gvProducts_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    //
    // se persiste la informacion ingresada por el usuario
    //
    ProductsManager.KeepProductInfo((GridView)sender);

    //
    // se recupera el producto seleccionado
    //
    int productId= Convert.ToInt32(gvProducts.DataKeys[e.NewSelectedIndex].Value);

    Response.Redirect(string.Format("ProductDetails.aspx?productId={0}", productId));
}

Tanto en la carga del formulario, como al momento de seleccionar unos de los ítems del grid es que se invocaran la funcionalidad que interactúa con el medio que conserva los datos

 

Código


El ejemplo ha sido implementado con VS2008 y hace uso de una base de datos Sql Compact

 

[C#]
[C# Skydrive]

domingo, 19 de febrero de 2012

[GridView] Mantener checkbox durante la paginación

 

Introducción

Es bien sabido que asp.net no mantiene estado en muchas de sus operaciones, uno de estos casos podría ser al momento de paginas una lista o grid, es por eso que uno será el responsable de mantener la información que el usuario selecciona.

El artículo demuestra como linq no puede ayudar en esta tarea, para ello contaremos con una interfaz simple que permita paginar una lista de productos.

Diseño del GridView

La creación del GridView no tiene nada especial, solo el uso de una columna de témplate para definir el checkbox

<asp:GridView ID="gvProducts" runat="server" CellPadding="4" ForeColor="#333333" 
    GridLines="None" AllowPaging="True" AutoGenerateColumns="False"  DataKeyNames="Id"
    onpageindexchanging="gvProducts_PageIndexChanging" 
    onpageindexchanged="gvProducts_PageIndexChanged">
    <RowStyle BackColor="#EFF3FB" />
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:CheckBox ID="chkSelection" runat="server" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="Id" HeaderText="Id" />
        <asp:BoundField DataField="Name" HeaderText="Nombre" />
        <asp:BoundField DataField="UnitPrice" HeaderText="Precio Unitario" />
        <asp:BoundField DataField="UnitsInStock" HeaderText="Unidades Stock" />
        <asp:BoundField DataField="SupplierName" HeaderText="Proveedor" />
        <asp:BoundField DataField="CategoryName" HeaderText="Categoria" />
    </Columns>
    <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
    <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
    <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
    <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
    <EditRowStyle BackColor="#2461BF" />
    <AlternatingRowStyle BackColor="White" />
</asp:GridView>

Es importante destacar en este punto la utilización de dos eventos claves para poder lograr el objetivo de mantener el estado de los check seleccionados por el usuario.

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        LoadGridProducts();
    }
}

private void LoadGridProducts()
{
    gvProducts.DataSource = NorthwindData.GetAllProducts();
    gvProducts.DataBind();
}

protected void gvProducts_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    ProductsSelectionManager.KeepSelection((GridView)sender);

    gvProducts.PageIndex = e.NewPageIndex;
    LoadGridProducts();

}

protected void gvProducts_PageIndexChanged(object sender, EventArgs e)
{
    ProductsSelectionManager.RestoreSelection((GridView)sender);
}

Es justamente en el evento previo a la paginación PageIndexChanging que se guarda el estado actual de los check de la pagina en curso.

Mientras que en el evento posterior a la paginación, PageIndexChenged, o sea cuando se cargo los registros de la pagina a la cual se navega, es que se restablece las marcas que pudiera haber para la misma.

 

Mantener y Restablecer estado checkbox

La implementación de esta funcionalidad esta centrada en la clase ProductsSelectionManager, esta tiene por objetivo no solo contener la lógica que se requiere, sino también encapsular el uso del objeto Session, vital para mantener el estado la información. La idea es que sea algo transparente si se usa el objeto Session o algún otro para mantener el estado.

Los pasos para mantener el estado de los check implican:

  1. recuperar los check marcados de la pagina actual
  2. obtener el estado previo conservado en el objeto Session
  3. comparar los id de producto de la pagina actual con los previos que se tenían en el objeto Session, si alguno esta presente se descarta (esto es necesario porque se conservara el estado actual de la selección para la página, descartando los anterior que estén presente en la lista)
  4. se asignan los id seleccionado a la colección, que se conservara en Session

 

public static void KeepSelection(GridView grid)
{
    //
    // se obtienen los id de producto checkeados de la pagina actual
    //
    List<int> checkedProd = (from item in grid.Rows.Cast<GridViewRow>()
                             let check = (CheckBox)item.FindControl("chkSelection")
                            where check.Checked
                            select Convert.ToInt32(grid.DataKeys[item.RowIndex].Value)).ToList();

    //
    // se recupera de session la lista de seleccionados previamente
    //
    List<int> productsIdSel =  HttpContext.Current.Session["ProdSelection"] as List<int>;

    if (productsIdSel == null)
        productsIdSel = new List<int>();

    //
    // se cruzan todos los registros de la pagina actual del gridview con la lista de seleccionados,
    // si algun item de esa pagina fue marcado previamente no se devuelve
    //
    productsIdSel = (from item in productsIdSel
                     join item2 in grid.Rows.Cast<GridViewRow>()
                        on item equals Convert.ToInt32(grid.DataKeys[item2.RowIndex].Value) into g
                     where !g.Any()
                     select item).ToList();

    //
    // se agregan los seleccionados
    //
    productsIdSel.AddRange(checkedProd);

    HttpContext.Current.Session["ProdSelection"] = productsIdSel;

}

Los pasos para restablecer la selección en la pagina que se visualiza implican:

  1. obtener el estado previo conservado en el objeto Session
  2. comparar los registros de la pagina con los seleccionado, se devuelve las coincidencias
  3. recorrer cada fila del grid que resultara del paso anterior seleccionando el checkbox

 

public static void RestoreSelection(GridView grid)
{

    List<int> productsIdSel = HttpContext.Current.Session["ProdSelection"] as List<int>;

    if (productsIdSel == null)
        return;

    //
    // se comparan los registros de la pagina del grid con los recuperados de la Session
    // los coincidentes se devuelven para ser seleccionados
    //
    List<GridViewRow> result = (from item in grid.Rows.Cast<GridViewRow>()
                                    join item2 in productsIdSel
                                    on Convert.ToInt32(grid.DataKeys[item.RowIndex].Value) equals item2 into g
                                 where g.Any()
                                 select item).ToList();

    //
    // se recorre cada item para marcarlo
    //
    result.ForEach(x => ((CheckBox)x.FindControl("chkSelection")).Checked = true);
    
}

Código

El ejemplo hace uso de una base de datos de Sql Compact 3.5, por eso del archivo .sdf en la carpeta App_Data.

Fue desarrollado usando Visual Studio 2008.

[C#]
[C# SkyDrive]

domingo, 27 de noviembre de 2011

[Linq] Categoría Jerárquica múltiple niveles

 

Introducción

El objetivo del artículo es demostrar como linq puede ser verdaderamente potente a la hora de convertir estructuras simple y planas como ser entidades (o tablas) en objetos jerárquicos que faciliten la asignación de datos a controles, en este caso un treeview.

El resultado del artículo cargara un árbol como se muestra en la imagen, el cual podrá tener tantos niveles como uno necesite.

 imagen1

Recuperar los registro de forma básica

El primer paso para armar la estructura será contar con información plana y sin procesar de la base de datos.

public static List<CategoriaEntity> ObtenerCategorias()
{
    string sql = @"SELECT C.IdCategoria,
                            C.IdCategoriaPadre,
                            C.Descripcion,
                            C.Posicion
                    FROM Categorias C
                    ORDER BY C.IdCategoria, C.Posicion";

    List<CategoriaEntity> categorias = new List<CategoriaEntity>();

    using (SqlCeConnection conn = new SqlCeConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        conn.Open();

        SqlCeCommand cmd = new SqlCeCommand(sql, conn);
        IDataReader reader = cmd.ExecuteReader();

        while (reader.Read())
        {
            categorias.Add(ConvertirCategoria(reader));
        }
        
    }

    return categorias;
}

private static CategoriaEntity ConvertirCategoria(IDataReader reder)
{
    return new CategoriaEntity()
    {
        IdCategoria = Convert.ToInt32(reder["IdCategoria"]),
        IdCategoriaPadre = reder["IdCategoriaPadre"] == DBNull.Value ? null : (int?)Convert.ToInt32(reder["IdCategoriaPadre"]),
        Descripcion = Convert.ToString(reder["Descripcion"]),
        Posicion = Convert.ToInt16(reder["Posicion"])
    };
}
Aplicar jerarquía a los datos

La información plana hay que darle jerarquía, determinando que ítem son hijos de que otros.

public static List<CategoriaJerarquica> ObtenerCategoriarJerarquia()
{
    List<CategoriaEntity> categoriasList = ObtenerCategorias();

    List<CategoriaJerarquica> query = (from item in categoriasList
                                        where item.IdCategoriaPadre == null
                                        select new CategoriaJerarquica
                                        {
                                            IdCategoria = item.IdCategoria,
                                            Descripcion = item.Descripcion,
                                            CategoriaHija = ObtenerHijos(item.IdCategoria, categoriasList)
                                        }).ToList();

    return query;
}

private static List<CategoriaJerarquica> ObtenerHijos(int idCategoria, List<CategoriaEntity> categoriasList)
{

    List<CategoriaJerarquica> query = (from item in categoriasList
                                       let tieneHijos = categoriasList.Where(o => o.IdCategoriaPadre == item.IdCategoria).Any()
                                       where item.IdCategoriaPadre == idCategoria
                                       select new CategoriaJerarquica
                                        {
                                            IdCategoria = item.IdCategoria,
                                            Descripcion = item.Descripcion,
                                            CategoriaHija = tieneHijos ? ObtenerHijos(item.IdCategoria, categoriasList) : null
                                        }).ToList();

    return query;

}

 

Con el primer linq al usar

where item.IdCategoriaPadre == null

se obtienen los nodos padres, o sea aquellos que no dependen de ningún otro nodo

 

Asignación de la jerarquía al control TreeView

Como ultimo paso y no menos importante será crear los TreeNode que requiere el control TreeView

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        List<CategoriaJerarquica> categoriaList = CategoriasDAL.ObtenerCategoriarJerarquia();

        CrearNodoHijo(categoriaList, null);
                 
    }
}


private void CrearNodoHijo(List<CategoriaJerarquica> categorias, TreeNode parentNode)
{
    categorias.ForEach(x =>
    {
        TreeNode node = new TreeNode(x.Descripcion, Convert.ToString(x.IdCategoria));

        if (x.CategoriaHija != null)
        {
            CrearNodoHijo(x.CategoriaHija, node);
        }

        if (parentNode == null)
            TreeView1.Nodes.Add(node);
        else
            parentNode.ChildNodes.Add(node);
    });   

}

Como se observa hay una invocación recursiva para armar la estructura del árbol.

 

Código de Ejemplo

El articulo fue creado con visual Studio 2008

La db es Sql Compact Edition (.sdf), por lo tanto esta se encuentra en la carpeta App_Data

 

  [C# SkyDrive]

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.

image

 

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#] 
 

lunes, 10 de octubre de 2011

[GridView] Eventos de controles contenidos en el GridView (2/2)

 

Introducción

Este artículo representa una continuación de:

[GridView] Eventos de controles contenidos en el GridView (1/2)

en donde se planteara un ejemplo práctico del uso de eventos en controles contenidos en el gridview

El ejemplo representa la edición de un empleado a quien se le asigna determinadas prendas de vestir por parte de la empresa. Lo importante es que debe registrarse la fecha en que fue asigna esa indumentaria.

Un punto a que no debe dejarse pasar por alto es que la selección de la prenda asignada no debe actualizar la tabla en al db directamente, la información se encontrara registrada en memoria, cuando el usuario confirme los cambios mediante el botón de aceptar será el momento en que  toda la información, incluida las indumentarias, impactara en la base de datos.

Diseño de objetos

Las entidades estarán representadas mediante el siguiente diagrama

La entidad Empleado se relaciona con las listas de estudios he indumentarias que tiene asociada. Es por esta razón que en la capa de Acceso a datos se recuperan estas relaciones cuando se obtiene una entidad.

Esta información relacionada al empleado será utilizada en las acciones de actualización cuando se efectúen las acciones en la presentación, esto se llevara a cabo mediante el “EmpleadoManager”

 

Actualizar Entidad

Una técnica bastante utilizada para encapsular la funcionalidad de entidades en memoria cuando se debe conservar la información dentro del objeto Session, consiste en crear una clase que aplique el patrón singleton.

Este patrón usar el objeto Session para contener su información entre request.

Se puede ver en este caso la implementación en la clase “EmpleadoManager”

/// <summary>
/// El objetivo de esta clase es generar una abstraccion al uso de la Session para administrar la entidad del empleado
/// </summary>
public class EmpleadoManager
{
    private EmpleadoEntity empleado;

    private EmpleadoManager()
    {
        empleado = new EmpleadoEntity();
    }

    public static EmpleadoManager Instance()
    {
        if(HttpContext.Current.Session["empleado"] == null)
        {
            HttpContext.Current.Session["empleado"] = new EmpleadoManager();
        }

        return (EmpleadoManager)HttpContext.Current.Session["empleado"];
    }

    public static void NuevoEmpleado()
    {
        HttpContext.Current.Session.Remove("empleado");
    }

    public EmpleadoEntity Empleado
    {
        get { return empleado; }
        set { empleado = value; }
    }


    public void IndumentariaAsignar(int idIndumentaria)
    {
        if (HttpContext.Current.Session["empleado"] == null)
            return;
        
        //Se recupera la entidad lista de la session
        EmpleadoManager empleadoManager =(EmpleadoManager)HttpContext.Current.Session["empleado"];

        List<IndumentariaEntity> indumentaria = empleadoManager.Empleado.IndumentariaAsignada;

        //se recupera la entidad de la lista y se actualiza la fecha de asignacion
        if (indumentaria.Exists(x => x.IdIndumentaria == idIndumentaria))
        {
            indumentaria.Where(x => x.IdIndumentaria == idIndumentaria)
                                            .First()
                                            .FechaAsignacion = DateTime.Now;
        }
        else
        {
            indumentaria.Add(new IndumentariaEntity()
            {
                 IdIndumentaria = idIndumentaria,
                 FechaAsignacion = DateTime.Now
            });
        }

    }

    public void IndumentariaDesasignar(int idIndumentaria)
    {
        if (HttpContext.Current.Session["empleado"] == null)
            return;

        //Se recupera la entidad lista de la session
        EmpleadoManager empleadoManager = (EmpleadoManager)HttpContext.Current.Session["empleado"];

        List<IndumentariaEntity> indumentaria = empleadoManager.Empleado.IndumentariaAsignada;

        //se recupera la entidad de la lista y se actualiza la fecha de asignacion
        if (indumentaria.Exists(x => x.IdIndumentaria == idIndumentaria))
        {
            indumentaria.RemoveAll(x => x.IdIndumentaria == idIndumentaria);                           
        }

    }


}

La utilización desde la pagina es muy simple

protected void checkIndumentaria_CheckedChanged(object sender, EventArgs e)
{
    CheckBox checkBox = sender as CheckBox;

    //NamingContainer devuelve el objeto donde esta contenidos el checkbox
    //en este caso se trata de una row del gridview
    GridViewRow row = checkBox.NamingContainer as GridViewRow;
    
    //conociendo la row se puede obtener el id de la entidad que se esta seleccionando
    int idindumentaria = Convert.ToInt32(gvIndumentaria.DataKeys[row.RowIndex].Value);

    if (checkBox.Checked)
        EmpleadoManager.Instance().IndumentariaAsignar(idindumentaria);
    else
        EmpleadoManager.Instance().IndumentariaDesasignar(idindumentaria);


    AsignarIndumentaria(EmpleadoManager.Instance().Empleado);

}

Es en este punto donde se aplica la técnica que permite recuperar la fila seleccionada al marcar el checkbox contendió en el gridview.

 

Código


El ejemplo se desarrollo con VS 2008, y base de datos Sql Server Express 2008 R2

[C#]
[C# SkyDrive]