Como mejorar la usabilidad de un listado maestro-detalle con jQuery y Ajax

En este articulo vamos a ver como podemos mejorar muchísimo la experiencia de usuario de nuestros aburridos listados de datos. Habitualmente cuando incluimos un listado en una aplicación,incluimos un enlace que permite acceder al detalle de cada fila. 

Está forma de actuar, aún siendo perfectamente correcta, deteriora la experiencia que el usuario tiene al interactuar con nuestra aplicación. En su lugar, una buena técnica es cargar los datos adicionales a través de ajax y mostrarlos en su contexto. En este articulo explicamos como incluir dinámicamente una nueva fila a nuestro listado y cargar en ella el detalle, reutilizando además las páginas existentes.

Para realizar el ejemplo debemos crear un proyecto de tipo WebSite que estará compuesto por los siguientes elementos:

  • Dos WebForms, Default.aspx y page2.aspx. Son las páginas del listado y el detalle.
  • Una hoja de estilo, site.css.
  • Tres imágenes, que por simplicidad deben llamarse: logo1.jpg, logo2.jpg y logo3.jpg.

Por supuesto debemos añadir la referencia a jQuery con nuGet. El proyecto debería parecerse a esto …

image

Empezaremos por lo más fácil, el contenido del archivo site.css, es el siguiente:

.red{color:red}
.content{display:none}
table, th, td {border:1px groove grey }    
.table-content{width:750px }
.logo{float:left}
.logo-text{}

Ahora vamos a la primera página Default.aspx, es la página que muestra el listado

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html >
<html lang="es">
<head runat="server">
    <title></title>
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="css/site.css" />
    <!--Aqui pondremos el script que veremos mas adelante-->
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <table class="table-content">
            <tr>
                <td>
                    <a href="page2.aspx?cliente=1">Ver</a>                    
                </td>
                <td>
                    Dato
                </td>
                <td>
                    www.devjoker.com
                </td>
            </tr>
            <tr>
                <td>
                    <a href="page2.aspx?cliente=2">Ver</a>                    
                </td>
                <td>
                    Dato
                </td>
                <td>
                    Otro dato
                </td>
            </tr>
            <tr>
                <td>
                    <a href="page2.aspx?cliente=3">Ver</a>
                </td>
                <td>
                    Dato
                </td>
                <td>
                    Otro dato
                </td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

Fijaros en comentario en color verde que pone «<!--Aqui pondremos el script que veremos mas adelante—>», ya que mas adelante tendremos que sustituirlo por un script de jQuery. Inicialmente tenemos un enlace por cada fila que nos lleva a la página de detalle. Y no os extrañéis por el uso de table, la mejor forma de mostrar datos tabulares es el elemento table.

La segundo página, page2.aspx que corresponde con nuestro detalle – que en el ejemplo muestra una página con el detalle del  cliente  y su logotipo. El código .aspx sería el siguiente:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="page2.aspx.cs" Inherits="page2" %>

<!DOCTYPE html >
<html  lang="es">
<head runat="server">
    <title></title>
    <link href="css/site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    Esto NO SE MUESTRA cuando se llama a la página por AJAX
    <div id="content">        
        <div  class="logo">
            <asp:Image runat="server" ID="imglogo" />
        </div>
        <div class="logo-text" >
            <h1>
                Este es el contenido que se va a mostrar!!!!</h1>
            <asp:Label runat="server">Detalle:</asp:Label>
            <asp:Label ID="lblDetalle" runat="server"></asp:Label>
        </div>        
    </div>
    </form>
</body>
</html>

Fijaros que hay una parte del código en la que ponemos «Esto NO SE MUESTRA cuando se llama a la página por AJAX», este es un comportamiento intencionado que incluimos para que sea más sencillo reutilizar páginas existentes en nuestras aplicaciones (aunque debemos tener en cuenta que la página se ejecutará completamente y será jQuery quien recorte la parte le indiquemos con el selector).

Y su código correspondiente. Lo único destacable es que hemos encapsulado la variable “cliente” en una propiedad para trabajar más fácilmente:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class page2 : System.Web.UI.Page
{
    int Cliente
    {
        get { 
            int cliente =  0 ;
            Int32.TryParse(Request["cliente"], out cliente);
            return cliente;
        }
    
    }
    string GetDetalle() {
        return String.Format(
            "Este es el detalle del cliente {0} ..., aqui podemos hacer de todo un poco.", 
            Cliente);
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        lblDetalle.Text  = GetDetalle();
        imglogo.ImageUrl = VirtualPathUtility.ToAbsolute(
                String.Format("~/img/logo{0}.jpg", Cliente));
    }
}

Está sería la ejecución de cada una de las páginas de forma independiente …

image

image

La aplicación funciona perfectamente, pero sería mucho mas usable si pudiéramos tener toda la información visible en una única página. Siempre podríamos recurrir a un popup, pero estaríamos actuando de forma intrusiva a al usuario. 

Lo primero que hacemos es modificar los elemento link por botones – esto no es obligatorio, pero personalmente prefiero un botón, ya que con un link el usuario puede esperar que se navegue hacia otra página (si quisiéramos compatibilidad con navegadores que no utilicen javascript deberíamos mantener el enlace):

<%--<a href="page2.aspx?cliente=1"  >Ver</a>--%>
<input type="button" 
value="[+]" class="addRowButton" rel="1" />

Observemos que el botón no tiene asociada ninguna acción para el evento onclick ni ningún otro.

Ahora vamos a incluir un poco de código javascript con jQuery el la primera página, Default.aspx (hay que incluirlo en el comentario de color verde que hemos incluido en el código  de Default.aspx):

<script type="text/javascript">
    $(document).ready(function () {
        $(".addRowButton").one("click", addChildrenRow);           
    });

    function showdiv(div, row) {            
        $(row).show();
        $(div).fadeIn("slow");
    }
    function hidediv(div, row) {            
        $(div).fadeOut("slow", function () {
            $(row).hide(); 
        });            
    }

    function addChildrenRow(source) {
        var button = $(source.currentTarget);
        var tr = button.parents("tr").first();
        tr.addClass("red");
        var trTemplate =
            $("<tr><td colspan=\"3\"><div class=\"content\"></div></td></tr>");
        tr.after(trTemplate);          
        var divElement = trTemplate.find("div.content");

        button.toggle(function () { tr.removeClass("red"); hidediv(divElement, trTemplate); },
                      function () { tr.addClass("red"); showdiv(divElement, trTemplate); });

        var cliente = button.attr("rel");
        divElement.load("page2.aspx?cliente=" + cliente +" #content", function () {
            divElement.fadeIn("slow"); 
        });
    }
</script>

Este script realiza las siguientes tareas:

  • En el script de inicio $(document),ready(…) se vincular el click del botón de cada fila a  la función addChildrenRow  (selector .addRowButton). Es importante destacar que este enlace se realiza a través del método one, que vincula únicamente la primera pulsación del boton.
  • La funcion addChildrenRow es la que realmente realiza el trabajo sucio, por un lado detecta el elemento que produce el evento, y localiza el tr inmediatamente superior, para insertar una nueva fila – con un elemento div- a continuación.  A continuación,  con el método toggle asigna dos funciones para mostrar y ocultar el div anterior, y por ultimo carga el contenido de la pagina en el div. Añade un poco de color y efectos con las funciones addClass y fadeIn, fadeOut

Si ejecutamos la página … el resultado de la ejecución es el siguiente:

image

image

image

Como podemos ver ahora la página despliega y oculta la información de detalle bajo demanda del usuario. Mucho mejor! Además garantiza que solo trae la información una única vez.

El código completo de la página Default.aspx quedaría de la siguiente manera:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html >
<html lang="es">
<head runat="server">
    <title></title>
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="css/site.css" />
    <script type="text/javascript">
        $(document).ready(function () {
            $(".addRowButton").one("click", addChildrenRow);           
        });

        function showdiv(div, row) {            
            $(row).show();
            $(div).fadeIn("slow");
        }
        function hidediv(div, row) {            
            $(div).fadeOut("slow", function () {
                $(row).hide(); 
            });            
        }

        function addChildrenRow(source) {
            var button = $(source.currentTarget);
            var tr = button.parents("tr").first();
            tr.addClass("red");
            var trTemplate =
                $("<tr><td colspan=\"3\"><div class=\"content\"></div></td></tr>");
            tr.after(trTemplate);          
            var divElement = trTemplate.find("div.content");

            button.toggle(function () { tr.removeClass("red"); hidediv(divElement, trTemplate); },
                          function () { tr.addClass("red"); showdiv(divElement, trTemplate); });

            var cliente = button.attr("rel");
            divElement.load("page2.aspx?cliente=" + cliente +" #content", function () {
                divElement.fadeIn("slow"); 
            });
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <table class="table-content">
            <tr>
                <td>                    
                    <input type="button" 
                    value="[+]" class="addRowButton" rel="1" />
                </td>
                <td>
                    Dato
                </td>
                <td>
                    Otro dato
                </td>
            </tr>
            <tr>
                <td>                   
                    <input type="button" 
                    value="[+]" class="addRowButton" rel="2" />
                </td>
                <td>
                    Dato
                </td>
                <td>
                    Otro dato
                </td>
            </tr>
            <tr>
                <td>                    
                    <input type="button" 
                    value="[+]" class="addRowButton" rel="3" />
                </td>
                <td>
                    Dato
                </td>
                <td>
                    Otro dato
                </td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

Nota. Las marcas de coche cuyos logotipos se muestran en este articulo no me han regalado nada Triste.

Podemos descargar el código fuente desde este enlace.(Archivo website16.zip)

Saludos,

DJK!

Pedro  Herrarte  Sánchez
Como mejorar la usabilidad de un listado maestro-detalle con jQuery y Ajax
Pedro Herrarte Sánchez

Pedro Herrarte, es consultor independiente, ofreciendo servicios de consultoría, análisis, desarrollo y formación. Posee mas de diez años de experiencia trabajando para las principales empresas de España. Es especialista en tecnologías .NET, entornos Web (ASP.NET, ASP.NET MVC,jQuery, HTML5), bases de datos (SQL Server y ORACLE) e integración de sistemas. Es experto en desarrollo (C#, VB.Net, T-SQL, PL/SQL, , ASP, CGI , C, Pro*C, Java, Essbase, Vignette, PowerBuilder y Visual Basic ...) y bases de datos (SQL Server y ORACLE). Pedro es MCP y MAP 2012, es fundador, diseñador y programador de www.devjoker.com..
Fecha de alta:26/01/2012
Última actualizacion:26/01/2012
Visitas totales:10073
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com