miércoles, 30 de mayo de 2012

Diferencias entre bind, live, delegate y on

Está claro que una de las partes fundamentales de jQuery es el manejo de eventos, pero cuando tenemos que ponernos manos a la obra se nos presentan distintas opciones y no siempre sabemos cual es la idónea.

A nuestra disposición tenemos diversas funciones que aparentemente hacen lo mismo:

·         bind

·         live

·         delegate

·         on

El porqué de esta diversidad es fruto de querer hacer una mejor librería jQuery a la vez que se mantiene retrocompatibilidad con versiones anteriores del producto. Aquí está claro que no todos los proyectos pueden cambiar de jQuery 1.0 a jQuery 1.7.2 sin que ello conlleve una refactorización de código de magno esfuerzo.

Cronológicamente hablando (y espero no equivocarme), la primera función en aparecer en escena fue bind. Después apareció live para cubrir un hueco que no llenaba bind (la delegación de eventos), pero enseguida vieron que live no funcionaba como se esperaba de ella, así que incorporaron delegate en detrimento de live. Por último y para evitar que la gente se volviera loca teniendo que decidir si utilizar bind, live o delegate, surgió on, que en un solo método reúne la funcionalidad de todos los métodos anteriores.

Una vez que ya tenemos claro el orden cronológico de aparición de todos estos métodos, la siguiente pregunta a contestar sería ¿Por qué apareció live? ¿Qué es eso de la delegación que bind no podía hacer?

Con bind podemos adjuntar manejadores de eventos (o callbacks en terminología javascript) a elementos existentes en el DOM. Lo cierto es que bind siempre ha funcionado maravillosamente bien pero tiene un grave problema cuando nos encontramos en escenarios donde el DOM cambia dinámicamente (por ejemplo aplicaciones con AJAX). El problema está en que bind adjunta manejadores sólo a elementos existentes, es decir, futuros elementos que se incorporen al DOM no sabrán nada de los eventos manejados por bind (aunque coincidan con el selector suministrado a bind).

Por ejemplo pensemos que tenemos un ul con un solo li hijo. Adjuntamos un manejador al evento click en los li hijos del elemento ul (no sólo para el único hijo existente, sino en general para todos los hijos li de ul). Cada vez que hagamos click en un li agregaremos un nuevo li al ul padre. ¿Si hacemos click en los nuevos elementos li creados dinámicamente se disparará nuestro manejador? Pues no, porque bind adjuntó el evento sólo a elementos existentes en el momento de su declaración, así que nuevos elementos que coinciden con nuestro selector de bind no se darán por aludidos.

    <ul id="lista">

        <li>1</li>

    </ul>


$(document).ready(function () {

    $("#lista li").bind("click", function () {

        var ul = $(this).parent();

        var length = ul.children().length;

        // aunque el nuevo li coincide con el selector $("#lista li"), bind no funcionará con el nuevo elemento recién agregado

        ul.append("<li>" + (length + 1) + "</li>");

    });

});


Lógicamente tenemos un problema con este código ¿Cómo adjuntar o re-adjuntar nuestro manejador a los nuevos elementos li? Pues para eso apareció live.

Live funciona con la delegación de eventos, es decir, es vez de adjuntar el manejador click al único elemento li presente que cumplía el selector $("#lista li") en el momento de la declaración, lo que hace es declarar un manejador global a elementos que coincidan con el selector, esto es tanto a elementos que existen ahora como elementos que vayan a existir en el futuro.

Veamos ahora que nuestro código sólo requiere un simple cambio para trabajar con live en vez de con bind.

$("#lista li").live("click",

 

A partir de este momento, cualquier elemento li que sea hijo de #lista tendrá adjunto el manejador de evento especificado (danto igual si el hijo existía en el momento de la declaración del manejador o fue creado después de forma dinámica).

El principal problema del que adolece live es que para hacer funcionar la delegación de eventos, adjunta el manejador al elemento document. De este forma, cada vez que se haga click en el documento (en cualquier sitio del documento) se chequeará si el elemento clicado coincide con el selector $("#lista li"), y si coincide se lanzará el evento.

Para solucionar el cómo funciona live, rápidamente se incorporó delegate.

Delegate respeta el mismo principio de delegación de eventos que live, pero a diferencia de éste, delegate adjunta el manejador a un padre específico. De esta forma ya no podemos hablar de un manejador global, sino de un manejador local con un ámbito concreto.

Para adaptar nuestro código a delegate, tenemos ahora que especificar un padre y un selector de hijos que se validarán ahora y en el futuro:

$("#lista").delegate("li", "click", function () { …

 

Si nos fijamos, con delegate hemos especificado que queremos adjuntar el manejador click a todos los hijos li (ahora y el futuro) para el padre #lista. Y todo esto confinado a un padre, es decir, sólo se chequeará el selector si el click sucede dentro de #lista, no a nivel de documento como sucedía con live.

Llegados a este punto está claro que hemos descartado live y sólo nos quedaría elegir entre bind y delegate según el escenario, pero lo cierto es que utilizando la nueva función on podemos olvidarnos de todo lo anterior y tener en un solo método toda la funcionalidad de bind y delegate.

Por ejemplo nuestro código anterior con bind pasaría a ser:

$("#lista li").on("click", function () { …


Y nuestro anterior delegate sería ahora:

$("#lista").on("click", "li", function () { …

 

Como vemos, con on podemos tanto gestionar eventos al estilo de bind (sólo para elementos existentes) como al estilo de delegate (con delegación de eventos para elementos futuros).

Ya no tienes excusa… utiliza on ... que no lo digo yo, lo dice jQuery!

Un saludo!

viernes, 11 de mayo de 2012

ResolveUrl en un servicio web de ASP.NET

Si programas en Web Forms estarás muy acostumbrado a utilizar los métodos ResolveUrl y ResolveClientUrl para devolver direcciones Url al código de cliente a partir de direcciones de ASP.NET (es decir, aquellas direcciones que son relativas a la raíz del proyecto y que comienzan con el carácter ~)

Los métodos ResolveUrl y ResolveClientUrl son métodos de la clase System.Web.UI.Control y es por ello que están disponibles tanto en un WebForm (.aspx) como en un control de usuario (.ascx).

Sin embargo, si estamos ejecutando código en un servicio web de ASP.NET (.asmx) no tenemos disponible estas funciones (porque un servicio web hereda de System.Web.Services.WebService) y lógicamente puede ser que queremos devolver al cliente direcciones url válidas a partir de direcciones del tipo ASP.NET (recuerda que son aquellas que incluyen el carácter ~).

En esta situación, nos será muy útil una función como la siguiente que “simula” el comportamiento de ResolveUrl y ResolveClientUrl. Digo “simula” porque con esta función estamos devolviendo rutas absolutas en vez de relativas, pero en cualquier caso estamos devolviendo rutas que el cliente puede consumir.

Imports System.Web
Public Class Utilities
    Public Shared Function ResolveUrl(ByVal url As String) As String
        Dim webApplicationRootUrl = _
            String.Format("{0}://{1}{2}", _
                HttpContext.Current.Request.Url.Scheme, _
                HttpContext.Current.Request.ServerVariables.Item("HTTP_HOST"), _
                HttpContext.Current.Request.ApplicationPath)
        If Not webApplicationRootUrl.EndsWith("/") Then
            webApplicationRootUrl &= "/"
        End If
        url = url.Substring(2) ' Quitar ~/
        url = webApplicationRootUrl & url
        Return url
    End Function
End Class

Ahora por ejemplo para una llamada a ResolveUrl("~/Default.aspx"), el método nos devolverá una dirección absoluta como http://localhost:7572/WebSite1/Default.aspx donde:


  • http es el esquema.
  • localhost:7572 es la variable HTTP_HOST
  • WebSite1 es la ruta a la aplicación.
  • Default.aspx es la url suministrada al método.

Un saludo!