Archivo

Posts Tagged ‘eventos javascript’

Eventos de javascript

agosto 30, 2010 Deja un comentario

Mientras que antes los sitios web solo usaban javascript para desempeñar tareas simples, las aplicaciones Web de hoy en día usan una gran cantidad de código Javascript, para programar interfaces de usuario complejas, ejecutando muchas líneas de código cada vez que el usuario interactúa con la aplicación.

Conocer acerca de los eventos de javascript es de gran ayuda para un desarrollador Web que tiene como objetivo crear aplicaciones que respondan a las acciones que realice el usuario de manera eficiente y tan rápido como sea posible.

Cuando un usuario interactúa con el navegador, el sistema operativo recibe entradas desde varios dispositivos como el teclado o el mouse y es tarea del navegador procesar los eventos individuales que se van generando y decidir qué hacer con cada evento, por ejemplo desplegar un menú ó ejecutar javascript cuando el usuario da un clic a un elemento.

Sin eventos no hay aplicaciones que reaccionen a las necesidades del usuario, todos los navegadores proveen API’s sencillas para su manejo (aunque con diferentes implementaciones).

Existen 3 API´s para controlar eventos:
La original de Netscape que soportan todos los navegadores,  la cual es la que se agrega como atributo de un elemento (como el onclick)  y solo se le asigna una función.
La  de Microsoft la cual tiene un método llamado attachEvent.
La del W3C que es la que se usa el método addEventListener y agrega un parámetro extra el cual generalmente se especifica como falso (false).

El modelo de evento de los navegadores, permite que un evento se vaya “pasando” a través de elementos.

El evento empieza en el nodo donde es generado, si el nodo no tiene un manejador del evento, entonces se pasa a su ancestro y si el ancestro tampoco tiene manejador del evento, se pasa de nuevo al ancestro siguiente y así sucesivamente hasta el nodo raíz.

A esto se le conoce como «event bubbling» (IE) o «event capturing» (otros navegadores), y significa que más de un elemento puede responder a la misma acción. La diferencia de event bubbling y event capturing radica en el orden,  «event bubbling» va del nodo hacia sus ancestros y el orden de «event capturing» va del nodo raiz hacia el último hijo que contiene.
Por ejemplo supongamos que tenemos un div (div2) que tiene asignado un evento click, este div tiene como ancestro otro div (div1) que también tiene asignado un evento click, cuando se le da click al div2, se dispara el evento y ejecuta la función asignada, sin embargo el evento no se detiene ahí después de ejecutar la función, dispara también el evento click del div1.


<div id="div1" onclick="alert('Click en div uno');">
<div id="div2" onclick="alert(''Click en div dos');"></div>
</div>

«Event bubbling» y «Event capturing» se pueden evitar  con los métodos: event.cancelBubble = trueevent.stopPropagation (), event.preventDefault (), dependiendo del navegador, ó cancelando el evento por medio de la instrucción return false.

Vale la pena leer el artículo de @ppk con una explicación más detallada acerca de este concepto, en la siguiente liga: http://www.quirksmode.org/js/events_order.html

Cuando un navegador dispara un evento, genera un parámetro extra de tipo evento, el cual describe que es el evento y todo lo relacionado a él,  se llama «Event Object«, este objeto es el que mantiene el rastro de los eventos que ocurren en la página y guarda información relacionada al evento, por ejemplo las coordenadas de “x” y “y” de el mouse, el elemento en el cual ocurrió el evento o cual tecla se presionó. Generalmente el manejador de evento necesita esa información para saber cómo procesar el evento.

Se puede acceder a esta información directamente del Objeto Event, pero desafortunadamente la lista de propiedades varía de navegador en navegador, algunas propiedades son:

  • pageX, pageY ó screenX, screenY: la distancia en píxeles de X y Y de la esquina superior izquierda a el puntero del mouse.
  • wich : que se usa con el evento keypress para determinar el código numérico de la tecla que se presionó.
  • target ó srcElement:que es el elemento de donde se originó el evento por ejemplo el elemento al que se le dio click cuando se usa el evento click.

Para conocer más a detalle las propiedades del objeto Event desde diferentes perspectivas visitar las siguientes ligas:

De Mozilla : https://developer.mozilla.org/en/DOM/event
De MS: http://msdn.microsoft.com/en-us/library/ms535863%28VS.85%29.aspx
De Opera: http://dev.opera.com/articles/view/handling-events-with-javascript/
Eventos de IPhone http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/handlingevents/handlingevents.html
API de eventos de Blackberry: http://docs.blackberry.com/en/developers/deliverables/18446/DOM_Events_Objects_1180979_11.jsp

En IE se puede acceder al objeto Event por medio del objeto explicito window.event, mientras que en los otros navegadores, se accede pasando un parámetro a la función del controlador del evento, por ejemplo:

document.onclick=function(e)
{
var e = window.event || e;
}

En el código anterior se pasa un parámetro (e) en la función, el cual será un objeto de tipo evento en la mayoría de los navegadores (pero no en IE),   el parámetro  se reemplaza con el mismo en caso de que exista ó  con el objeto global en caso de que no (IE). Después necesitaríamos saber cuál es el objeto que está llamando el evento, esta información se guarda en el evento pero de distinta manera target o srcElement.
Aun cuando se detecte el objeto Event entre los navegadores (con el script anterior), existen diferencias porque cada navegador soporta diferentes propiedades y métodos, existen varias tablas de compatibilidad que pueden ver en las siguientes direcciones:

http://www.javascriptkit.com/jsref/event.shtml
http://www.quirksmode.org/dom/events/index.html

Comúnmente varios elementos en una página requieren del mismo controlador de evento, por ejemplo en un catalogo de productos cada elemento requiere de un controlador para el evento onclick, para que despliegue información del producto y guarde un historial de los productos visitados cuando el usuario realice el click.

<div>
<a href="http://www.dominio.com/despliegaProducto/1"
onclick="return despliegaProducto(this, event, function()
{
guardaHistorial("prod_id",1); return false;
});"> Producto 1
</a>
</div>

Si tuviéramos 50 productos   tendríamos que repetir el mismo código en cada elemento lo que incrementaría algunos KB de basura transferida a los usuarios, la información que es distinta es la clave del producto y en caso de que tuviéramos diferentes plantillas para el despliegue del producto seria la liga.

Delegación de eventos (Event delegation), es el nombre que se le da comúnmente a la técnica de enlazar (bind) un solo controlador de evento a un elemento raiz que contiene todos los elementos que necesitan responder a ese evento, cuando el evento es disparado en un elemento hijo, este se va al elemento raíz donde es controlado.

Se agrega  un conjunto de controladores a un elemento que sea el ancestro de varios elementos, cuando se ejecute el evento en alguno de sus «hijos» el elemento raíz pregunta cuál de ellos  es el que disparo el evento y después ejecuta el código, después cancela el paso del evento a su ancestro por medio de los eventos para cancelar el event bubbling.

El controlador distingue cual elemento hijo es el que ejecuto el evento y recibe parámetros adicionales por medio de algún atributo en ese elemento, por ejemplo para aplicar la delegación de eventos en el ejemplo anterior:

<div
onclick=»return despliegaProducto(event);»>>
:
<a href="http://www.dominio.com/despliegaProducto/" id="1" >
Producto 25
</a>
:
</div>

El nuevo controlador de evento obtiene el id del producto, el cual, es previamente pasado como un parámetro del atributo id del elemento (el <a> al que se le dio click):

<script>
function despliegaProducto(evt)
{
evt = evt || window.event; //Obtenemos el objeto event
var elemento = e.target || e.srcElement; //Obtenemos el elemento que se le dio el click
var idProducto = elemento.id; // se obtiene el id del producto
...
se procesa para el despliegue del producto y se guarda el historial
...
return false; // se cancela la acción del href de la liga.
}
</script>

La poca cantidad de código adicional que se agrega para que funcione la delegación es insignificante, además, que puede guardarse en un archivo js que se guarde en el cache, y nó  en el documento principal, por lo cual no se tiene que descargar cada vez que el usuario visita la página.

Otro concepto importante acerca de los eventos son los contextos de ejecución, los navegadores tienen un manejo de eventos de un solo hilo, con un modelo de programación asíncrono, está dirigido a un elemento  específico y generan  la ejecución de una función que controle el evento.

Esta función se ejecutará en un contexto de ejecución, cuando se termine, empieza a ejecutarse el siguiente evento creando otro contexto de ejecución, cuando se carga una página se crea un contexto global de ejecución, cuando se ejecutan funciones se van creando contextos adicionales de ejecución y se crea además una pila de contextos de ejecución donde el ultimo contexto creado es el activo.

Cuando se enlazan (binding) y desenlazan (unbinding) controladores de eventos, se usan las formas nativas de los navegadores: attachEvent y detachEvent en Internet Explorer, addEventListener y removeEventListener para los demás.
Las dos técnicas funcionan de manera muy similar, por ejemplo el siguiente código agrega un manejador de evento genérico:

function agregaManejador(elemento, tipo, manejador)
{
if (elemento.addEventListener) // todos los navegadores excepto IE
{
elemento.addEventListener(tipo, manejador, false);
}
else //Internet Explorer
{
elemento.attachEvent(«on»+tipo, manejador); // se le agrega el prefijo on para IE
}
}// agrega manejador
function quitaManejador(elemento, tipo, manejador)
{
if (elemento.removeEventListener) //todos los navegadores excepto IE
{
elemento.removeEventListener(tipo, manejador, false);
}
else if (elemento.detachEvent)
{
elemento.detachEvent(«on»+tipo, manejador);
}
}//quita manejador

//elemento = document.getElementById(«Id_del_elemento»);
//agregaManejador(elemento,»click», funciondeClick);

El código anterior revisa si existe soporte para los eventos addEventListener () y removeEventListener (), los cuales son soportados por todos los navegadores excepto IE, si estos métodos no existen en el objeto, entonces se asume que el navegador es IE y se utilizan los métodos específicos para IE (attachEvent () y detachEvent ()).

El código funciona bien pero se puede optimizar, cada vez que se ejecutan las funciones agregaManejador () y quitaManejador () se realiza la misma comprobación para ver si un determinado método esta presente, entonces estas comprobaciones se repiten cada vez que se ejecutan las funciones. Tomando en cuenta que si addEventListener () estuvo presente en la primer ejecución, entonces estará presente en las consecuentes llamadas.
Repetir el mismo trabajo cada vez que se llama a una función afecta al rendimiento de las aplicaciones.

Una manera para eliminar el  trabajo repetitivo en funciones es por medio de un concepto que se llama «lazy loading«, que significa que no se hace trabajo hasta que la información es necesaria, en este ejemplo no hay necesidad para determinar la manera para crear controladores de evento hasta que alguien realiza una llamada a la función, la versión de «lazy loading» para la función anterior es:

function agregaManejador(elemento, tipo, manejador)
{
if (elemento.addEventListener) // todos los navegadores excepto IE
{
agregaManejador = function (elemento, tipo, manejador)
{
elemento.addEventListener(tipo, manejador, false);
}
}
else //Internet Explorer
{
agregaManejador = function (elemento, tipo, manejador)
{
elemento.attachEvent(«on»+tipo, manejador); // se le agrega el prefijo on para IE

}
}
agregaManejador(elemento, tipo, manejador);
}

function quitaManejador(elemento, tipo, manejador)
{
if (elemento.removeEventListener) //todos los navegadores excepto IE
{
quitaManejador = function (elemento, tipo, manejador)
{
elemento.removeEventListener(tipo, manejador, false);
}
}
else if (elemento.detachEvent)
{
quitaManejador = function (elemento, tipo, manejador)
{
elemento.detachEvent(«on»+tipo, manejador);
}
}
quitaManejador(elemento, tipo, manejador)
}

La primera vez que cualquiera de los dos métodos es ejecutado, se hace una revisión para determinar la manera correcta de hacer el trabajo, luego la función original es reescrita como una nueva función que contiene solo la manera correcta y el último paso en la primer llamada de la función es ejecutar de nuevo la función con los argumentos originales, cada llamada posterior a las funciones evitan la detección porque el código de detección fue sobrescrito por una nueva función.

La primer llamada a las funciones será siempre la que dure mas tiempo porque tiene que ejecutar el código de detección y después hacer la llamada a la nueva función para completar la tarea, las llamadas posteriores a la misma función son mas rápidas porque no tienen la lógica de ejecución.

«Lazy loading» se utiliza cuando la función no se ejecuta inmediatamente en la página.

La segunda alternativa para mejorar el rendimiento de la función es por medio del patrón «Conditional Advance Loading» (carga condicional anticipada), que realiza la detección por anticipado o sea mientras se esta cargando el script, en lugar de esperar a que se llame la función, la detección se sigue haciendo solo una vez pero se hace anticipadamente:

var agregaManejador = document.body.addEventListener ?

function (elemento, tipo, manejador)
{
elemento.addEventListener(tipo, manejador, false);
} :
function (elemento, tipo, manejador)
{
elemento.attachEvent(«on»+tipo, manejador);
};

var quitaManejador = document.body.removeEventListener ?
function (elemento, tipo, manejador)
{
elemento.removeEventListener(tipo, manejador, false);
} :
function (elemento, tipo, manejador)
{
elemento.detachEvent(«on»+tipo, manejador);
};

En el código anterior se revisa si addEventListener () y removeEventListener () están presentes y usa la información para asignar la función mas apropiada, el resultado es que todas las llamadas a las funciones serán igualmente rápidas porque la detección se hace desde antes de la llamada.

Este patrón asegura que todas las llamadas a la función tomen la misma cantidad de tiempo, y se debe de usar cuando una función va a ser llamada inmediatamente y muy frecuente a través del tiempo de vida de la página.

Debemos ser cuidadosos en probar las aplicaciones en los diferentes navegadores, hay situaciones en que los eventos se comportan muy diferentes aún y cuando realicemos las detecciones del navegador.

A continuación listo los eventos que incluyen ya los navegadores mas modernos.

Eventos de mouse:

onClick ondragleave onmousemove onscroll
ondblClick ondragover onmouseout
ondrag ondragstart onmouseover
ondragend ondrop onmouseup
ondragenter onmousedown onmousewheel


Eventos de la ventana

onafterprint onfocus ononline onresize
onbeforeprint onhaschange onpagehide onstorage
onbeforeonload onload onpageshow onundo
onblur onmessage onpopstate onunload
onerror onoffline onredo


Eventos del teclado

onkeydown onkeypress onkeyup


Eventos de media

onabort onended onloadstart onwaiting
oncanplay onreadystatechange onpause onratechange
oncanplaythrough onvolumechange onplay onerror
ondurationchange onloadesdata onplaying onseeked
onemptied onloadedmetadata onprogress onseeking
onstalled onsuspend ontimeupdate


Fuentes:

Eventos del dom http://en.wikipedia.org/wiki/DOM_events
Podcast Javascript Events: http://www.yuiblog.com/blog/2009/04/27/video-ppk-jsevents/
Eventos: http://www.javascriptkit.com/jsref/event.shtml
Manual de ecmascript: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf
Libro: Even Faster Web Sites Capítulo 7.
Libro: JavaScript The Good Parts Capítulo 4
Libro: High Performance JavaScript Capítulo 8
Libro: JavaScript the Missing Manual Capítulo Capitulo 6.