viernes, 26 de noviembre de 2010

Eventos cross-browser

Hola, hoy hablaremos sobre algo que me ha quitado el sueño en más de una ocasión: el tratamientos de eventos de javascript en los distintos navegadores.

El objeto event es implementado de distinto forma en IE y en FF (y en general en navegadores conformes a w3c).

En IE hay un objeto global window.event mientras que en el resto de navegadores se pasa de forma automática un parámetro con la información del evento a la función que gestiona el evento.

Además de esto, una vez hayamos conseguido acceder al objeto que almacena la información del evento, tampoco las propiedades que exponen son iguales, de nuevo hay diferencias entre IE y el resto.

Veremos varias formas de registrar eventos y por cada una de ellas estudiaremos sus ventajas e inconvenientes.

La primera forma, la más sencilla y la que primero aprende cualquiera cuando se enfrenta con HTML y javascript es la de registrar el evento en línea, esto es a través de una propiedad de la propia etiqueta HTML.

<input type="button" id="Button1" value="Button1" onclick="clickEnButton1();" />

 

            function clickEnButton1(e) {

                var evento = window.event || e;

                alert(evento);               

            }

 

Esto funciona en IE, Google Chrome, Opera y Safari, pero no funciona en Firefox.

Para hacer que funcione en Firefox (y también en el resto de navegadores) y por consiguiente nuestro código sea cross-browser, necesitamos pasar el evento como parámetro de forma explícita en la llamada a la función:

<input type="button" id="Button1" value="Button1" onclick="clickEnButton1(event);" />

 

Aunque hubiésemos apostado a que sólo IE entendería event en este contexto (hay que recordar que event y window.event para IE son lo mismo, porque si se omite el padre se asume que es el objeto window), pasa que la palabra “event” en el contexto de una llamada en línea a un manejador de evento la entienden todos los navegadores. Esto es sólo uno más de los misterios de la guerra entre navegadores y los oscuros caminos de los estándares web.

Por acabar con esta primera forma de adjuntar un manejador de evento en línea, cabe mencionar que no hay problema en agregar más parámetros a la llamada. Por ejemplo:

<input type="button" id="Button1" value="Button1" onclick="clickEnButton1(event, 'sergio');" />

 

            function clickEnButton1(e, nombre) {

                var evento = window.event || e;

                alert(evento);

                alert(nombre);

            }

 

Además hay otra palabra especial que también se utiliza asiduamente y que merece una explicación por sí sola, estamos hablando de la palabra this. Por ejemplo, es muy común ver el siguiente código:

<input type="button" id="Button1" value="Button1" onclick="clickEnButton1(event, this);" />

 

            function clickEnButton1(e, elemento) {

                var evento = window.event || e;

                alert(evento);

                alert(elemento.tagName);

            }

 

En ese contexto this siempre será la etiqueta a la que se está adjuntando el manejador de evento, es decir el INPUT. De este modo, no es necesario acceder al objeto event para saber quien ha sido el origen del evento, sino que siempre accederemos directamente a la etiqueta a través de this. Cuidado de no confundir this en la llamada al evento (que será el INPUT) con this accediendo directamente desde dentro de la función (que entonces será el objeto window) porque dentro de la función this hace referencia al propietario de la función que en el caso de eventos en línea será el objeto window.

Hasta aquí hemos utilizado la inserción en línea de un manejador pero ahora te diré que esta no es la forma más correcta de hacer las cosas. Esto es denomina “javascript intrusivo” porque estamos mezclando contenido con comportamiento (pasa igual que si utilizas el atributo style para dar estilo al elemento, en realidad debería utilizar class para separar contenido de presentación). Ahora se lleva (y parece que ya es una norma de la que no podrás escapar) el “javascript no intrusivo”.

Una primera aproximación para llevar a cabo javascript no intrusivo sería la siguiente:

<input type="button" id="Button1" value="Button1" />

 

        document.getElementById("Button1").onclick = function (e) {

                    var evento = window.event || e;

                    alert(evento);

                    alert(this); //es Button1

                }

 

Aquí hay fijarse en que igualmente estamos soportando el paso del parámetro e que representa el evento, pero además this dentro de la función ya no es window sino el botón, es decir, el propietario de la función.

Además y para curiosos, la función no tiene nombre, esto es una función anónima.

Lo único que no se puede (o no sé hacer) registrando los eventos de este modo, es pasar parámetros extra la función, porque e lo pasa el navegador automáticamente pero un parámetro más no sabría hacerlo (aunque tampoco sé si resultaría necesario en algún caso).

Una segunda aproximación (que también se ve bastante) es igualmente “javascript no intrusivo” pero cambiando una función anónima por una función con nombre:

<input type="button" id="Button1" value="Button1" />

 

            function clickEnButton1(e) {

                var evento = window.event || e;

                alert(evento);

                alert(this); //es Button1

            }

 

            document.getElementById("Button1").onclick = clickEnButton1; //sin parentesis!!

 

Ahora creamos una función nombre y le decimos que función se adjunta para el evento onclick, fijarse (importantísimo) que se dice la función (sin paréntesis) no el resultado de la función (que sería si hubiéramos puesto paréntesis).

Ahora y después de que sepamos como registrar eventos de las distintas formas disponibles, hay que resolver otras dudas relativas a los eventos que hay tener en cuenta si se quiere lidiar con ellos de forma efectiva. Estoy hablando de la “acción predeterminada” y “el burbujeo o propagación”.

La “acción predeterminada” hace referencia a etiquetas HTML que hacen “algo de serie”. Por ejemplo un enlace (A) “navega de serie” y un botón de envío de formulario (SUBMIT) “envía el formulario de serie”. Entonces ¿Cómo se puede anular este comportamiento de serie si fuera necesario?. Veámoslo con un ejemplo:

El siguiente código muestra una alerta “Click en A” pero después navega a google.

<a href="http://www.google.es" onclick="clickEnA(event, this);">google</a>

 

            function clickEnA(e, elemento) {

                var evento = window.event || e;

   alert("Click en A");

            }

 

Si queremos que no navegue a google (que no haga su acción predeterminada) o por lo menos queremos controlarlo tenemos que hacer lo siguiente:

<a href="http://www.google.es" onclick="return clickEnA(event, this);">google</a>

 

            function clickEnA(e, elemento) {

                //si la función devuelve false no navegará a google

                return confirm("¿Quiere navegar a google?")

            }

 

El kit de la cuestión está en la palabra return clickEnA…, porque si se devuelve false estamos diciendo que “no queremos que la etiqueta haga su acción predeterminada”. Esto funciona porque siempre que adjuntamos manejadores de eventos a una etiqueta, primero se lanzan nuestros eventos y después la acción predeterminada (si la tuviera). Si por el camino una de nuestros manejadores devuelve false, estamos anulando este comportamiento y rompiendo la acción predeterminada.

Igualmente podríamos controlar la acción predeterminada con códigos como:

        <a href="http://www.google.es" id="A1">google</a>

       

 

            document.getElementById("A1").onclick = function (e) {

                return confirm("¿Quiere navegar a google?")

            }

           

O también este otro:

<a href="http://www.google.es" id="A1">google</a>

 

            function navegarAGoogle(e) {

                return confirm("¿Quiere navegar a google?")

            }

 

            document.getElementById("A1").onclick = navegarAGoogle;

 

Ahora toca el turno del “burbujeo o propagación”. La propagación de eventos se puede mostrar de forma más clara con un ejemplo. Si tengo una capa “Padre” que contiene una capa “Hija” y ambas tienen un manejador de evento para el evento onclick, si hago click en la capa hija, saltará el evento onclick de la hija y después también el evento onclick de la capa padre (es decir, el evento ha “burbujeado” desde la capa hija a la capa padre). Y entonces ¿Qué pasa si quiere que “no burbujee” del hijo al padre? Pues toca cancelar esta propagación de eventos de hijos a padres.

Por ejemplo, veamos en ejecución el siguiente código:

        <div id="Padre" style="height: 100px; width: 100px; background-color: Red;">

            <div id="Hijo" style="height: 50px; width: 50px; background-color: Yellow;">

            </div>

        </div>

 

        <script type="text/javascript">

 

            function ClickPadre(e) {

                alert("Padre");

            }

 

            function ClickHijo(e) {

                alert("Hijo");

            }

 

            document.getElementById("Padre").onclick = ClickPadre;

            document.getElementById("Hijo").onclick = ClickHijo;

           

        </script>

 

clip_image002

Si hago “click” en la zona roja (Padre)

clip_image004

Si hago “click” en la zona amarilla (Hijo)

clip_image006

clip_image008

Ahora ya está claro lo que significa la propagación o burbujeo. Bien, podemos seguir.

Para cancelar la propagación ya no podemos simplemente retornar false, sino que debemos llamar a un método del objeto event. Y aquí de nuevo surgen los problemas porque no es el mismo nombre de método en IE que en el resto de navegadores (me estoy empezando a cansar…). Si quieres ver las “millones” de diferencias que hay entre ambos modelos, puedes visitar http://www.javascriptkit.com/jsref/event.shtml

En IE hay que establecer la propiedad cancelBubble = true, mientras que en el resto hay que llamar al método stopPropagation().

     function ClickHijo(e) {

                var cancelar = confirm("¿Cancelar propagación?");

                if (cancelar) {

                    if (window.event) {

                        window.event.cancelBubble = true;

                    }

                    else {

                        e.stopPropagation();

                    }

                }

            }

 

Ya por último te comentaré que todo esto está muy bien pero es un poco de locos tener dos objetos completamente distintos de evento. Para solucionar esto, la solución definitiva no es otra sino utilizar un framework de javascript, y para mí no hay otra que utilizar jQuery.

Lo único que hablaremos sobre jQuery será en utilizarlo para, estableciendo los manejadores de eventos tal y como hemos visto en este documento (que también ahora con jQuery se podrían establecer de otra forma pero no queremos entrar en detalle) como “estandarizar” o “agrupar” el objeto evento para manejar un solo objeto con las mismas propiedades en todos los navegadores. El código para hacer esto es el siguiente:

     function ClickHijo(e) {

       e = window.event || e; //coger el objeto evento según modelo IE o resto

       e = $.event.fix(e); //estandarizar el objeto event a través de jQuery

       var cancelar = confirm("¿Cancelar propagación?");

       if (cancelar) {

             e.stopPropagation(); //método del objeto evento de jQuery

}

     }

 

Por último, otra construcción que podemos encontrar y que es necesario entender es aquella donde en un enlace en vez de utilizar el evento onclick se utiliza el atributo href para ejecutar un código javascript. Por ejemplo:

<a href="void(alert('Sergio')">Sergio</a>

 

Un saludo!

No hay comentarios:

Publicar un comentario