Cuando se produce un evento, se dice que se **dispara** el evento. El código que se encarga de reacciónar al evento, le llamamos **manejador del evento**.
Tenemos que conocer los eventos disponibles y asignarles manejadores.
En el HTML anterior, lo que queremos es gestionar el evento de que se haga click en el botón. El evento es ''click'' y según el BOM, podemos escribirlo como un atributo que será ''onclick'':
(...)
(...)
Como vemos, dentro del atributo ''onclick'' añadimos código JavaScript. Lo normal es que pongamos alguna función que se encargará de gestionar el evento
Para intentar separar todo lo posible el JavaScript del HTML, podríamos conseguir lo mismo que antes buscando el botón y luego asociarle el manejador a su evento ''onclick'':
Podríamos también usar una función anónima directamente para el evento ''click'':
==== DEMO: desasignar eventos en el BOM ====
Si queremos dejar de gestionar un evento, podemos asignarle ''null'' y así quedará sin ningún manejador de eventos asignado:
Eventos BOM
==== DEMO: anular eventos del BOM ====
Algo que puede ser importante es la posibilidad de poder cancelar un evento, es decir, una vez que se haya producido un evento, evitar que la acción por defecto de ese evento se produzca.
En el siguiente HTML tenemos un enlace que al pulsarlo no hará nada, no llevará a su destino:
Ir a campusMVP.es
Al devolver ''false'', quedará anulada la acción por defecto que realizaría un click en ese enlace.
Vamos a hacerlo un poco más elaborado. Le preguntaremos al usuario si realmente quiere acceder a la página que le llevaría el enlace:
Ir a campusMVP.es
Si pulsa **Cancelar**, devolverá ''false'' y entonces el enlace no funcionará.
==== DEMO: subclasificación de eventos ====
Veremos cómo solucionar el problema que tenemos en el modelo de eventos del BOM de que solo se pueda asignar por defecto 1 manejador de eventos a un determinado evento.
Partimos del siguiente HTML:
Queremos que una vez que se ha pulsado el botón de enviar, el usuario no pueda volver a darle al botón. Crearemos un script con una función que se encargue de buscar el elemento ''submit'' del formulario:
Supongamos que tenemos otro formulario y queremos asignarle a un mismo evento dos manejadores, por ejemplo el de deshabilitar el botón de envío y la validación de un campo:
El contenido de ''DeshabilitarSubmit.js'' tiene el siguiente contenido:
// Cada 50 milisegundos se llamará a la función 'isPageFullyLoad'
var tmrReady = setInterval(isPageFullyLoaded, 50);
// Detectamos si la página si ha cargado correctamente
function isPageFullyLoaded() {
if (document.readyState == "loaded" || document.readyState == "complete") {
subclassForms();
clearInterval(tmrReady);
}
}
// Comprueba si existe un manejador para el evento 'onsubmit'
// En caso de que lo haya, añade la nueva acción para ese evento
function submitDisabled(_form, currSubmit) {
return function () {
var mustSubmit = true;
if (currSubmit != null)
mustSubmit = currSubmit();
var els = _form.elements;
for (var i = 0; i < els.length; i++) {
if (els[i].type == "submit")
if (mustSubmit)
els[i].disabled = true;
}
return mustSubmit;
}
}
// Recorre todos los formularios de una página
// para comprobar si existe un 'onsubmit' y lo deshabilita
function subclassForms() {
for (var f = 0; f < document.forms.length; f++) {
var frm = document.forms[f];
frm.onsubmit = submitDisabled(frm, frm.onsubmit);
}
}
===== Eventos según el DOM level 2 =====
Hasta ahora hemos visto la manera clásica (y perfectamente válida todavía) de gestionar los eventos. Aunque está bien para desarrollos sencillos, aparte de sus limitaciones obvias, si nos esmeramos en que nuestro **código sea reutilizable en forma de bibliotecas** y queremos conseguir una **mínima interferencia con el código de los demás**, no son el mejor sistema.
Para evitar estos problemas y conseguir un modelo unificado sobre cómo gestionar eventos, el W3C propuso en el año 2000, dentro de DOM Level 2, un sistema general para la captura y ejecución de eventos, el cual vamos a estudiar ahora con sus detalles y salvedades.
Pero antes de verlo necesitamos aclarar otro concepto importante acerca de cómo se capturan los eventos en una página Web.
==== ¿Dónde se captura de verdad un evento? ====
Esta es una pregunta que tiene bastante más profundidad de la que parece a simple vista. Imaginemos una página con el siguiente código HTML:
Púlsame
Como vemos es un simple ''%%%%'' que contiene a un enlace. Dado que todos los elementos del DOM forman una jerarquía, es decir, están contenidos unos en otros, **cuando generamos un evento** en uno de ellos (por ejemplo, pulsar el enlace), **el evento se propaga por toda la jerarquía del DOM** como si fueran las ondas que deja una piedra al tirarla a un estanque:
{{ :informatica:programacion:cursos:programacion_avanzada_javascript:07-flujo-eventos-dom.png |}}
Si pulsamos en el enlace, al estar contenido dentro del ''div'' es como si pulsásemos también en el ''div'', y a su vez en el cuerpo de la página, el elemento '''' (''documentElement'') y finalmente en el propio documento que siempre lo contiene a todo.
Es decir, la mayoría de **los eventos convergen desde donde se producen hasta el elemento más exterior**.
Los anglosajones a este efecto le llaman "//event bubbling//" o lo que es lo mismo "burbujeo de eventos", pero a mí me gusta más, en español, utilizar la denominación "convergencia de eventos", algo que (modestamente 😉) yo mismo acuñé en mi primer libro, en el año 1997, cuando los lenguajes de script comenzaban a ponerse interesantes.
Lo cierto es que **es algo más complicado que esto**, y un poco antiintuitivo a la vez. En realidad, cuando se produce un evento en un elemento de la página **se producen tres fases diferenciadas a la hora de gestionar y lanzar el evento**. Estas tres fases se ilustran bien en la figura siguiente:
{{ :informatica:programacion:cursos:programacion_avanzada_javascript:07-fase-eventos-dom.png |}}
* **Fase de captura**: en esta fase se comienza a detectar el evento desde el elemento más alto en la jerarquía (el documento), hacia abajo, hasta llegar justo antes del elemento donde realmente se ha generado el evento. En nuestro símil con el estanque es como si las ondas comenzasen a formarse desde fuera hacia adentro, cerrándose sobre el punto de impacto de la piedra en lugar de al contrario. Bastante poco intuitivo, su origen se remonta a la guerra de los navegadores, como veremos enseguida.
* **Fase de objetivo**: es el punto 4 del diagrama. Es cuando se detecta el evento en el elemento que lo ha provocado originalmente. Se puede considerar el primer paso de la siguiente fase de convergencia, por eso he pintado la flecha terminando hacia el otro lado.
* **Fase de convergencia**: o como dicen los anglosajones "bubbling phase". Se comienza a detectar el evento de nuevo en todos los elementos de la jerarquía, pero esta vez de abajo a arriba, empezando en el elemento original y llegando hasta la raíz del documento. Es la forma de propagación más lógica e intuitiva en mi opinión.
De este modo, el navegador va verificando en cada elemento de la jerarquía, si se ha definido o no un manejador para el evento que está gestionando, y si es así lo ejecuta, saltando al paso siguiente en caso contrario.
**¿Por qué se producen todos estos pasos?** ¿Cuál es el sentido de todo esto?
Lo que se pretende conseguir con este recorrido por el DOM es **poder capturar los eventos**, no en el elemento que los produce, sino **en cualquier otro punto intermedio de la jerarquía o incluso en varios puntos diferentes** separadamente (podemos gestionar el mismo evento en varios puntos).
De este modo, por ejemplo, se podrían capturar todos los eventos de todos los enlaces contenidos en el ''%%%%'' de nuestro ejemplo desde un único manejador que los gestione a la vez, en lugar de tener que definir un manejador para cada enlace. O gestionar de manera independiente lo que queremos hacer cuando se pulse un enlace y cuando se pulse el contenedor de éste.
Además, disponiendo de las fases de captura y de convergencia **podemos elegir si queremos que se capture antes en los elementos superiores** (ahorrando tiempo) **o en los inferiores** (que como he dicho antes, me parece más intuitivo y coherente).
Dado que además, en general, **podremos cancelar el flujo en cualquier punto** (subiendo o bajando) podemos evitar que se recoja la acción en puntos inferiores o superiores del proceso, dando lugar a técnicas de optimización avanzadas.
Existe además un detalle histórico a tener en cuenta. Cuando los dos navegadores de los años '90 se decidieron a incorporar el modelo de objetos, Internet Explorer optó por el modelo de convergencia (bubbling) mientras que Netscape lo hizo por el de captura (de arriba a abajo). Así que en ambos navegadores el flujo de lanzamiento de eventos era diferente. El W3C, para no eliminar ninguna de las dos opciones de su modelo y buscar una solución salomónica, decidió meter ambos. De ahí sale todo este embrollo.
Enseguida veremos exactamente cómo funcionan estas fases y cómo entran en juego en los diferentes navegadores.
==== Gestión de eventos según el DOM level 2 ====
El DOM define dos métodos especializados en la gestión de eventos: ''addEventListener()'' y ''removeEventListener()''. Los dos están presentes en todos los nodos del DOM para poder gestionar cualquier evento en cualquier elemento.
Ambos toman tres parámetros obligatorios como argumentos:
* **Nombre** del evento a gestionar
* **Referencia** a la función que lo gestiona (puedes ser una anónima)
* Un booleano para indicar en qué **fase del flujo de eventos** queremos capturarlo. Si le pasamos un valor ''true'' entonces los eventos se evalúan en la fase de captura del flujo de eventos visto en la lección anterior. Si se le pasa ''false'' (o no se pasa nada, lo más habitual) entonces los eventos se gestionan en la fase de convergencia (de abajo a arriba). Si por algún motivo extraño quisiésemos gestionar el evento en las dos fases del flujo de eventos tendríamos que registrarlo dos veces.
La función ''addEventListener()'', como su propio nombre indica **agrega un nuevo manejador de eventos al elemento**, colocándolo al final de la cola para ser llamado cuando se produzca.
Esto elimina de un plumazo el problema que teníamos en el BOM sobre qué ocurría si ya existía un manejador de eventos asignado, y que nos llevó a la técnica de subclasificación. En el modelo de eventos del DOM, al contrario que en el de BOM, **un mismo evento puede tener registrados varios manejadores**. Cuando se produzca el evento, éstos se llamarán en el mismo orden en el que fueron agregados mediante invocaciones de ''addEventListener''.
Ejemplo:
a.addEventListener("click", manejadorEnlace, false);
Importante: con esta función el nombre de los eventos no lleva el prefijo "on" como hasta ahora. Es decir, el evento no es "onclick" sino simplemente "click" y lo mismo ocurre con los demás ("load", "beforeunload", etc…). ¡Mucho cuidado con esto!.
Otro detalle a tener en cuenta es que cuando el DOM genera un evento y llama al manejador correspondiente, éste se ejecuta bajo el contexto del elemento que está capturando el evento. Es decir, **dentro del manejador la palabra clave ''this'' representa el elemento en el que se está capturando el evento**, que debido a las fases de flujo y de convergencia no tiene por qué ser el evento en el que se ha producido el evento. Es algo importante que nos puede ahorrar mucho trabajo.
El método ''removeEventListener'' funciona exactamente igual y toma los mismos parámetros, sólo que ahora le indicamos qué manejador retiramos y de qué fase. Esto último tiene sentido si pensamos que para un mismo evento podríamos haber añadido varios manejadores en fases distintas del flujo de eventos, así que tenemos que definir ambas cosas también para retirarlos:
a.removeEventListener("click", manejadorEnlace, false);
Como segundo argumento del método ''addEventListener'' podemos usar directamente una función anónima si el código es sencillo:
a.addEventListener("click", function(){alert("Hola");}, false);
Pero no podemos hacerlo así, si más tarde pretendemos retirar el manejador del evento. El motivo es que si llamamos a ''removeEventListener'' no tendremos una referencia a la función original ya que era anónima. Así que ojo.
==== DEMO: Manejadores de eventos y convergencia ====
Veamos cómo funciona la convergencia de eventos, es decir, cómo al producirse un evento en un determinado elemento este se transmite por toda la jerarquía del DOM de ese elemento (de él a sus padres o de sus padres hasta él, depende del modelo que sigamos).
Cada vez que se pulse el enlace, vamos a gestionar el evento click del enlace y también el evento click del contenedor, porque también se detectará este evento en el contenedor. Con la propiedad ''innerHTML'' vamos a visualizar si el evento se ha detectado en el ''%%%%'' o en el ''''.
Cuando ponemos a false el tercer parámetro de ''addEventListener'':
c.addEventListener("click", manejadorDiv, false);
indicamos que el modelo de convergencia de eventos sea de hijo a padre.
Si lo ponemos a ''true'':
c.addEventListener("click", manejadorDiv, true);
indicaríamos que usase el modelo de convergencia de eventos original, el que tenía Netscape. Con este último cambio, cada vez que pulsásemos el enlace, el evento //click// se detectará primero en el padre (el ''div'' contenedor) y luego en el propio enlace.
===== Modelo unificado de eventos =====
Nuevamente si quieres obviar esta parte puedes hacerlo en caso de no necesitar dar soporte a Internet Explorer. De todos modos, puede servirte como ejemplo de cómo crear en general código que se degrade paulatinamente en función del soporte existente de características de un navegador. Por otro lado, ten en cuenta que **en próximos ejemplos del curso usaremos el manejador universal desarrollado aquí** para establecer nuestros eventos. Tú puedes usar ''addEventListener()'' sin problema.
Por regla general vamos a utilizar siempre ''addEventListener()'' a la hora de gestionar eventos en nuestros desarrollos web.
No obstante, si queremos ofrecer soporte para todos los navegadores, incluso los más antiguos, debido a las diferencias existentes entre los tres modelos para gestión de eventos (BOM, DOM, IE), hay que tener en cuenta muchas cosas diferentes a la hora de declarar un simple evento.
Si se trata de un evento sencillo en una página aislada no hay problema: podemos usar el modelo BOM que si bien es simple y tiene limitaciones, funciona bien siempre y es fácil de utilizar.
Sin embargo, si queremos crear **código verdaderamente reutilizable** deberíamos elegir siempre el modelo de eventos más apropiado según el navegador que estemos utilizando:
* Si es una versión moderna de cualquier navegador, entonces el DOM.
* Si es Internet Explorer 8 o anterior deberíamos usar el modelo IE.
* Si es un navegador realmente antiguo (hay de todo por ahí accediendo a Internet), entonces el modelo clásico del DOM (o sea, el BOM) será la única opción.
==== Detectar capacidades del navegador ====
Tradicionalmente los programadores solían **averiguar a través del agente de usuario qué navegador**, versión y sistema operativo utilizaban sus usuarios para acceder a las páginas. En función de ello adaptaban su código para usar una u otra funcionalidad. **Hoy en día eso no sirve de nada**. Aunque averigües esa información no te servirá de mucho ya que las versiones de los navegadores y sus capacidades cambian a gran velocidad (Chrome lanzó 37 versiones en 6 años, de septiembre de 2008 a septiembre de 2014, por ejemplo) y además existe el modo //quirks// y diversos modos de compatibilidad que hacen que la versión signifique cada vez menos, por no mencionar que directamente muchos navegadores "mienten" en su cadena de agente de usuario y pueden parecer navegadores diferentes.
El **agente de usuario** es una cadena identificativa que poseen todos los navegadores en la que se refleja el tipo y versión del navegador además de otras informaciones relacionadas, como por ejemplo el sistema operativo bajo el que se ejecuta o las versiones de ciertas bibliotecas auxiliares, como .NET o Java.
El modo **quirks** es un modo especial de renderizado de algunos navegadores en el que se trata de renderizar la página con el mayor nivel de compatibilidad con navegadores antiguos, y por lo tanto sin soporte para la mayoría de características de HTML5, CSS3 o JavaScript de los navegadores modernos.
Debido a estas particularidades, **la estrategia apropiada a seguir para adaptar nuestro código a todas las circunstancias es la de detectar capacidades** y no navegadores.
En el caso concreto del **modelo de eventos**, lo que tenemos que hacer es **ir detectando uno a uno los diferentes modelos**. Si el primero (DOM) falla ir a por el segundo (IE) y si este tampoco está presente ir a por el tercero (BOM). Con esta estrategia es muy sencillo escribir una clase auxiliar que nos permita trabajar siempre de la manera correcta con los eventos, sea cual sea el navegador:
var EventHandlerHelper =
{
addEventListener: function (elt, nomEvnt, handler) {
if (elt.addEventListener)
elt.addEventListener(nomEvnt, handler, false);
else {
if (elt.attachEvent)
elt.attachEvent("on" + nomEvnt, handler);
else
elt["on" + nomEvnt] = handler;
}
},
removeEventListener: function (elt, nomEvnt, handler) {
if (elt.removeEventListener)
elt.removeEventListener(nomEvnt, handler, false);
else {
if (elt.detachEvent)
elt.detachEvent("on" + nomEvnt, handler);
else
elt["on" + nomEvnt] = null;
}
}
};
El código está en el archivo "''EventHandlerHelper.js''" de las descargas de ejemplo.
Lo que hacemos es crear un nuevo objeto llamado ''EventHandlerHelper'' en el que definimos dos métodos con el mismo nombre que los que ofrece el DOM: ''addEventListener'' y ''removeEventListener''. Con estos sustituiremos a todos los demás, ya que a través de ellos se comprueba cuál es el modelo disponible y se aplica siempre el correcto, sea cual sea el navegador
El método ''removeEventListener'' hace el proceso análogo pero inverso.
Con esta nueva clase podemos agregar eventos a cualquier elemento de manera confiable con tan solo escribir algo similar a:
var c = document.getElementById("contenedor");
EventHandlerHelper.addEventListener(c, "click", manejadorDiv);
Un poco más adelante veremos en un vídeo práctico cómo usarlos en un ejemplo. A partir de ahora usaremos siempre este modelo de gestión de eventos para facilitar el trabajo.
===== Información sobre eventos =====
Hasta ahora hemos visto algunos eventos muy sencillos, como la pulsación de un botón o el fin de la carga de la página. En éstos, por regla general, no es necesaria información extra, salvo quizá en algunos casos, cuál fue el elemento que lanzó realmente el evento en cuestión (si gestionamos varios desde un mismo manejador).
Lo habitual, sin embargo, es que **los eventos necesiten información contextual sobre el proceso que los ha generado**. Por ejemplo, si movemos el ratón sobre un elemento podemos necesitar las coordenadas del ratón, o si se pulsa una tecla querremos saber qué tecla ha sido.
Toda esta información se puede conocer a partir de un objeto especial llamado ''event'', que en realidad son muchas clases diferentes que [[https://developer.mozilla.org/en-US/docs/Web/API/Event|implementan la interfaz Event]].
==== El objeto event en el DOM ====
En el modelo de eventos sugerido por el DOM todos los manejadores de eventos que declaremos mediante ''addEventlistener'' pueden recibir un argumento opcional con una referencia a un objeto ''event'', con información sobre el evento en cuestión.
De este modo podríamos modificar un poco el ejemplo del apartado anterior para hacer algo así:
Fíjate en que ahora hemos usado el mismo manejador para el evento ''click'' tanto del ''div'' como del enlace que éste contiene (he omitido el cambio de color para simplificarlo). Además este manejador ahora toma como argumento una referencia a un objeto de nombre event que utilizamos para averiguar en qué elemento se está capturando el evento en cada momento, usando la propiedad ''currentTarget'' de éste.
Este objeto ''event'' ofrece mucha información sobre el evento actual, la cual dependerá además del tipo de evento que se esté produciendo. Los miembros base de un evento en el DOM se pueden ver en la tabla siguiente y puedes consultar [[https://developer.mozilla.org/en-US/docs/Web/API/Event|la referencia completa en la MDN]]:
^ Nombre ^ Tipo ^ Descripción ^
| ''bubbles'' | booleano | Indica si el evento se transmite por la jerarquía del DOM o no. No todos los eventos convergen. Por ejemplo, el evento "focus" sólo se lanza en el evento que toma el foco, y no se transmite a los demás de la jerarquía, es decir, no hay fase de captura ni de convergencia. Se debe consultar para cada evento. |
| ''cancelable'' | booleano | Nos sirve para averiguar si la acción por defecto del evento se puede cancelar. Se debe consultar para cada evento. |
| ''currentTarget'' | elemento | Una referencia al elemento del DOM que está capturando el evento actualmente. OJO: no tiene que ser el mismo elemento que lo ha lanzado, ya que podemos capturarlo en cualquier nivel de la jerarquía, como ya sabemos. En el modelo del DOM la palabra clave ''this'' siempre apunta al mismo elemento que esta propiedad, así que es equivalente escribir ''event.currentTarget.tagName'' que ''this.tagName'' |
| ''detail'' | numérico | Proporciona información extra sobre el evento. Depende del tipo de evento. Por ejemplo, en un clic nos dice el número de veces que se ha pulsado, en eventos de rueda de ratón la distancia que se ha girado la rueda… No se suele utilizar demasiado, pero siempre está presente en eventos de interfaz de usuario ([[https://developer.mozilla.org/en-US/docs/Web/API/UIEvent|UIEvent]]) o en eventos personalizados. |
| ''[[https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase|eventPhase]]'' | numérico | Vale 1 si el evento se ha lanzado en la fase de captura, 2 en el elemento que lo ha generado, y 3 si es en la fase de convergencia. |
| ''preventDefault()'' | función | Si la propiedad ''cancelable'' es ''true'' se puede llamar a este método para cancelar la acción por defecto. Es muy interesante para, por ejemplo, evitar que se navegue a través de un enlace y éste actúe solamente como un botón, o para evitar que se envíe un formulario, aunque en estos casos, devolver ''false'', o establecer la propiedad ''returnValue'' del evento como ''false'', tiene el mismo efecto. La propiedad ''defaultPrevented'' del evento sirve para comprobar si se ha cancelado la acción por defecto o no con este método. |
| ''stopPropagation()'' | función | Si la propiedad ''bubbles'' es ''true'', entonces llamando a este evento se cancela la transmisión a la jerarquía del DOM, es decir, no sigue la fase de captura ni el "burbujeo" de la fase de convergencia y no se detecta en los siguientes nodos del DOM. Por ejemplo, si capturamos el clic en un enlace y en su div contenedor, pero no queremos capturarlo en ambos a la vez, podemos cancelar la propagación en el manejador del enlace y así no se detectará también en el ''div''. |
| ''stopImmediatePropagation()'' | function | Como sabemos, cuando un mismo elemento tiene más de un manejador asociado para el mismo evento, éstos se ejecutan en el orden en el que fueron especificados. Si desde alguno de ellos llamamos a este método, los demás manejadores posteriores que no se hayan ejecutado todavía, no se ejecutarán, pues cancela toda llamada posterior. |
| ''target'' | elemento | El elemento de la página que realmente ha producido el evento. En nuestro ejemplo, si pulsamos el enlace pero capturamos en el ''div'', esta propiedad apuntaría al enlace que es el que ha provocado el evento. Es una de las propiedades más importantes de un evento. |
| ''type'' | texto | El tipo de evento que se ha lanzado. Es la misma cadena que usamos para declarar el evento: "click", "load", focus"… Nos sirve para distinguir el tipo de evento cuando varios comparten el mismo manejador. |
| ''view'' | objeto | Es un objeto de tipo ''AbstractView'' definido por la W3C y que es de donde derivan todas las posibles vistas del documento (se refiere a documentos XML genéricos, que pueden visualizarse de modo diferente según su XSLT). En este caso se refiere a la vista actual del documento, lo cual implica la página actual. Es decir, en la práctica con ella lo que obtenemos es una referencia al objeto ''window'' actual. Más información [[http://www.w3.org/TR/DOM-Level-2-Views/views.html|aquí]]. Solo se ofrece en eventos de interfaz de usuario, o sea, de tipo UIEvent. |
Todas las **propiedades son de solo lectura**, es decir, podemos leer su valor y no cambiarlo, lo cual es lógico pues simplemente nos proporcionan información.
Además de las propiedades que hemos visto en la tabla anterior, el objeto ''event'' posee otras que solamente aparecen cuando tienen sentido para el evento que estamos capturando y nos informan, por ejemplo, de las teclas que se han pulsado en el teclado, o de las las coordenadas del ratón, las vueltas de la rueda del ratón, etc… Más adelante veremos los eventos más importantes y conoceremos estas propiedades particulares.
==== El objeto event en Internet Explorer 8 o anterior ====
**Contenido opcional**: esto es historia antigua y, como todo el contenido relativo a IE y versiones antiguas, si no quieres hacer esta lección puedes dejarla. De todos modos lo hemos dejado porque puedes encontrarte de todo por ahí y, entonces, te alegrarás de conocerlo 😉
Como ya hemos dicho antes, a partir de la versión 9 de Internet Explorer soporta el modelo estándar del W3C para eventos (el DOM), por lo que no hay problema en ese caso. Sin embargo, en IE8 y anteriores, como ya hemos estudiado, el modelo cambia bastante.
Lo que más cambia es la forma de acceder al objeto ''event'', ya que hay dos casos bien diferenciados:
- En el caso de que usemos ''attachEvent'' para adjuntar los eventos, el objeto ''event'' se pasa al manejador como primer argumento, exactamente igual a como acabamos de ver para el DOM.
- Si usamos el estilo del BOM para asignar los eventos, entonces **los manejadores no reciben información alguna**, y debemos acceder a la información sobre el evento **a través de la propiedad** ''event'' de la ventana (''window.event'').
El resto de los navegadores, excepto Firefox, soportan también acceder al objeto ''event'' desde la ventana en este caso, para tener compatibilidad con este modo de actuar.
Incluso en el caso 1, aunque el funcionamiento es igual al del DOM, el problema es que las propiedades del objeto ''event'' de Internet Explorer tienen nombres diferentes a las que tienen en el DOM. Eso sí, aunque son menos, la funcionalidad que se puede conseguir es muy parecida.
En la tabla siguiente podemos ver las propiedades base de un evento de Internet Explorer:
^ Propiedad ^ Tipo ^ Descripción ^
| ''cancelBubble'' | booleano | Permite averiguar o fijar si el evento convergerá o no. Como en IE no hay fase de captura no tiene sentido la propiedad ''eventPhase''. Si queremos hacer, lo equivalente al método ''stopPropagation'' del DOM sólo tenemos que asignar ''true'' a esta propiedad. |
| ''returnvalue'' | booleano | Es el valor de retorno que se enviará como resultado del evento. Por defecto siempre vale ''true''. Si le ponemos ''false'' como valor a esta propiedad es equivalente a llamar a ''preventDefault()'' en el DOM o a devolver ''false'' en el manejador: cancela la acción por defecto. |
| ''srcElement'' | elemento | El elemento que ha provocado el evento. Es igual a la propiedad ''target'' en el evento del DOM. |
| ''type'' | texto | Es idéntica a la propiedad del mismo nombre en el evento del DOM. |
La única dificultad real que existe en este modelo es que **no hay forma de averiguar cuál es el elemento actual que está gestionando el evento**, es decir, no hay equivalente a la propiedad ''currentTarget'' del DOM. Más allá de la diferencia de nombres y comportamientos, éste es el principal motivo para tratar de evitar dicho modelo (si podemos).
Una última diferencia importante es que en versiones antiguas de Internet Explorer, en función de cómo declaremos el evento tendremos un valor diferente de ámbito para el manejador, es decir, la palabra clave ''this'' tendrá un valor diferente dentro del manejador. Así, si asignamos el evento usando el BOM (ejemplo ''elto.onclick=manejador;'') entonces ''this'' apuntará al objeto que ha lanzado el evento, es decir lo mismo que la propiedad ''srcElement'' del evento. Sin embargo, si lo adjuntamos con ''attachEvent'' entonces ''this'' apuntará al objeto global (la ventana).
==== Un objeto event unificado ====
**Contenido opcional**: como todo el contenido relativo a IE y versiones antiguas, si no quieres hacer esta lección puedes dejarla.
Para tratar de paliar la dificultad de lidiar con todos los detalles de trabajar con los eventos y asegurarnos de que éstos van a funcionar bien en todos los navegadores, vamos a ampliar la funcionalidad de nuestro objeto ''EventHandlerHelper'' para que añada un buen soporte para los objetos ''event'' también.
Si le añadimos el siguiente método:
fixEvent: function (event) {
var evt = event ? event : window.event;
if (!evt.bubbles) evt.bubbles = evt.cancelBubble;
if (!evt.cancelable) evt.cancelable = evt.returnValue;
if (!evt.currentTarget) evt.currentTarget = evt.srcElement;
if (!evt.preventDefault) evt.preventDefault = function () { evt.returnValue = false; };
if (!evt.stopPropagation) evt.stopPropagation = function () { evt.cancelBubble = true; };
if (!evt.target) evt.target = evt.srcElement;
if (!evt.view) evt.view = window;
if (!evt.relatedTarget) evt.relatedTarget = evt.fromElement ? evt.fromElement : evt.toElement;
if (!evt.which) evt.which = evt.keyCode != 0 ? evt.keyCode: evt.charCode;
if (!evt.key) evt.key = String.fromCharCode(evt.which);
return evt;
}
Lo que hacemos es obtener el objeto evento desde donde esté disponible, del argumento en caso de que exista o del objeto ''window'' si no es así. Una vez que lo tenemos, comprobamos si tiene las propiedades y métodos del DOM, añadiéndoselas desde las correspondientes de Internet Explorer si no es así.
Las únicas propiedades que quedan fuera son ''eventPhase'' y ''detail'', por imposibilidad de obtenerlas. La propiedad ''currentTarget'' se define para que no falle pero no nos es muy útil ya que en el modelo de Internet Explorer siempre apuntará al elemento que lanza el evento, no al que lo captura, por lo que no conviene usarla (Por ejemplo jQuery la incluye también –le llama ''currentTarget''- aunque tiene la misma limitación).
A partir de ahora, si necesitamos código compatible con todos los navegadores, bastará con declarar todos los manejadores al estilo DOM (con el evento como argumento) y llamar a esta función para asegurarnos de tener un objeto ''event'' compatible con el DOM en gran medida. Por ejemplo, el código anterior quedaría para Internet Explorer así:
var m = document.getElementById("mensaje");
var manejador = function (event) {
event = EventHandlerHelper.fixEvent(event);
m.innerHTML += event.srcElement.tagName + " ";
event.stopPropagation();
event.preventDefault();
};
var c = document.getElementById("contenedor");
c.attachEvent("onclick", manejador);
var a = document.getElementById("enlace");
a.attachEvent("onclick", manejador);
En este fragmento (que sólo funcionará en IE) adjuntamos sendos manejadores usando el método específico de IE llamado ''attachEvent''. En el evento llamamos a ''fixEvent'' y obtenemos un objeto evento que podemos usar de la manera habitual, como si fuera un evento del DOM, ya que le hemos añadido las propiedades y métodos que le faltaban. He metido llamadas a ''stopPropagation'' y ''preventDefault'' para verificar que funcionan bien.
El método ''fixEvent'' nos servirá siempre, sea cual sea el modelo de eventos y el navegador utilizado. En los ejemplos posteriores de este módulo lo utilizaremos siempre, pero tú utiliza simplemente ''addEventListener'' y el modelo del DOM si lo prefieres.
==== DEMO: Modelo unificado, funcionamiento y uso ====
Veremos el mismo que ejemplo que en apartado **DEMO: Manejadores de eventos y convergencia**, pero adaptado a este modelo unificado.
===== Eventos más habituales =====
Hasta llegar hasta aquí, hemos recorrido un largo camino para poder entender cómo funcionan los eventos en los navegadores y cómo podemos sacarles partido desde JavaScript. Ahora nos queda **lo más fácil y entretenido**: saber qué eventos tenemos a nuestra disposición y qué particularidades tienen para poder gestionarlos.
Hay decenas de eventos posibles y varían de un elemento a otro, e incluso de un navegador a otro, ya que no cualquier evento está soportado en todos ellos. Sin embargo, hay un subconjunto que está ampliamente soportado y que además es el que usaremos de manera más habitual. Será el que repasaremos en esta parte del módulo.
Mi recomendación es [[https://developer.mozilla.org/en-US/docs/Web/Events|tener siempre a mano una buena referencia on-line]] que nos informe de las propiedades y particularidades de cada evento. Existen infinidad de eventos, algunos atados a APIs concretas y otros personalizados, pero también podemos definir una [[https://developer.mozilla.org/en-US/docs/Web/Events#Standard_events|lista de eventos estándar]] que se definen en las especificaciones web, y que son comunes a todos los navegadores.
Además, otra página interesante para tener como referencia, aunque ya tiene unos años, es [[http://help.dottoro.com/|Dottoro]]. Esta página nos permite localizar cualquier elemento, bien mediante la búsqueda integrada, bien recurriendo al índice, y nos proporciona información detallada y ejemplos de cada uno, indicando además los atributos y propiedades de cada uno, los eventos que soporta... y además muestra mediante iconos el soporte que ofrece cada navegador para el miembro consultado (si bien no la actualizan a menudo y puede que haya algún dato erróneo, pero aún así merece mucho la pena como primer filtro):
{{ :informatica:programacion:cursos:programacion_avanzada_javascript:07-dottoro.gif |}}
Las principales herramientas de escritura de código ofrecen también ayuda contextual mientras escribimos, por lo que nos facilitarán mucho asignar eventos, propiedades y saber qué parámetros utilizar. Aprovecha su potencia.
A continuación **vamos a repasar** de manera somera **los eventos más importantes comunes a todos los navegadores**, aunque algunos ya los hemos ido viendo mientras avanzábamos en la explicación de los modelos de eventos.
==== Eventos de ciclo de vida de la página ====
Desde que se solicita un URL, el navegador descarga sus contenidos, los procesa y los visualiza, ocurren varias cosas y se producen diversos **cambios de estado en la página**. El navegador nos proporciona eventos que lanza en ciertos puntos de su ciclo de vida para poder llevar a cabo tareas de inicialización o responder a esos cambios. Los principales son:
* ''load'': nos informa de cuándo la página ha terminado de cargar y de ser procesada. Esto incluye no solamente la carga y el procesado del HTML sino también la carga de cualquier recurso externo que haya, incluidas las imágenes. Ya lo hemos mencionado en algún ejemplo. Se define en el [[https://www.w3.org/TR/DOM-Level-3-Events/#event-type-load|nivel 3 del DOM]]. Es interesante para saber cuándo todo el procesamiento inicial de la misma por parte del navegador ha terminado y contrasta con el evento ''DOMContentLoaded'' que veremos en detalle en breve que salta antes, en cuanto el DOM ha sido procesado. Las imágenes y los marcos también disponen de este evento. Se consideran eventos de interfaz de usuario y no hay que confundirlo con el [[https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/load_event|evento load del objeto XHR para AJAX]].
* ''unload'': se produce cuando el navegador descarga el documento actual, bien porque cambiamos de página o porque el usuario cierra la ventana. Si hay marcos se produce antes en la ventana "madre" que en los marcos que la contienen. Está definida en [[https://www.w3.org/TR/DOM-Level-3-Events/#event-type-unload|el nivel 3 del DOM]]. Dentro de este evento no se pueden mostrar ventanas con ''alert'' o similar ni ejecutar procesos largos o se cancelarán por parte del navegador. Por ejemplo, si necesitamos hacer una llamada al servidor durante la descarga de la página, desde luego no puede hacerse con una llamada AJAX síncrona (eso no se recomienda nunca en ningún lado) y es mejor utilizar la API de [[https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon|sendBeacon()]] para hacerlo, que realiza la llamada y no espera respuesta. No es cancelable ni converge.
* ''beforeunload'': este evento es mucho más interesante que el anterior ya que nos notifica **antes** de que se vaya a descargar la página y nos da la oportunidad de evitarlo. No está soportado en versiones viejas de Opera. Enseguida lo veremos con más detalle. Está definido en [[https://www.w3.org/TR/html5/browsers.html#unloading-documents|HTML5]]. Es cancelable por el usuario, como veremos enseguida, impidiendo que la página se descargue si es necesario.
* ''pageshow'': este evento se lanza cuando la página actual se hace visible debido a acciones relacionadas con la navegación, ya que es un [[https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent|evento de transición de página]] definido en [[https://html.spec.whatwg.org/multipage/indices.html#event-pageshow|HTML5]]. Es decir, se notifica al cargar inicialmente la página (después del evento ''load''), al navegar a ella desde otra página o al regresar a la misma mediante el uso del historial. Dispone de una propiedad para el evento llamada ''persisted'' que indica mediante un booleano si la página va a ser almacenada o no en una caché para mostrarla de nuevo (lo habitual cuando navegamos a otra y luego regresamos: la página no carga de nuevo al darle para atrás al navegador, solo se muestra y por lo tanto no salta el evento ''load'' sino ''pageshow''.
* ''pagehide'': ídem que la anterior, pero para cuando la página deja de ser visible debido a navegación. Salta antes que ''beforeunload'' y que ''unload''.
* ''hashchange'': el navegador notifica este evento para la ventana actual cuando cambia el valor que hay detrás del símbolo almohadilla (''#'') en el URL de la página. Es sumamente importante porque es la base en la que se apoyan las modernas aplicaciones de tipo "Single Page".
* ''readyStateChange'': este evento se notifica cada vez que hay un cambio en el estado de carga de la página. Lo detallaremos en la próxima lección.
Los navegadores móviles, por el propio funcionamiento de estos dispositivos no siempre notifican de los eventos ''pagehide'', ''beforeunload'' o ''unload''. En estos navegadores el usuario puede mandar el navegador a segundo plano simplemente pulsando el botón de inicio, y luego el sistema operativo puede reclamar la memoria utilizada "matando" el navegador sin más, por lo que no saltará ninguno de los eventos. Incluso algunos navegadores (por ejemplo el que viene nativamente con los móviles Xioami o el conocido navegador móvil UCBrowser) directamente no los implementan y no saltan jamás. Es decir, en móviles **debemos considerar estos eventos como una excepción cuando saltan, y no una norma**. La única forma de determinar en estos casos si una página se oculta o se muestra es usando una API especial de HTML5: la [[https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API|Visibility API]]. Tenlo en cuenta para estos casos.
Son todos eventos de uso sencillo y directo que se suelen capturar en la ventana actual o en la ventana de los marcos internos. Veamos unos cuantos ejemplos prácticos de uso.
==== Ejemplo: Detectando que la página está lista ====
Como hemos visto, el objeto ''window'' dispone del evento ''load'' que se llama cuando la página ha sido cargada por completo. Que esté completamente cargada quiere decir que el HTML ha sido procesado, el DOM se ha creado y todos los recursos externos (hojas de estilo, scripts, imágenes…) están cargados, procesados y visualizados. Podemos responder a este evento asignándolo como un atributo del nodo '''' (como ya hemos comentado en un vídeo anterior) o bien usando JavaScript en el propio objeto ''window'':
window.onload = mi_window_onload;
Aunque la forma más habitual de hacerlo es mediante ''addEventListener'', así:
window.addEventListener("load", mi_window_onload);
El evento es muy útil y es fácil de usar y nos asegura que cuando se llame a su manejador, todos los elementos de la página estarán cargados y listos para su uso, incluyendo los que hay que cargar en una segunda fase tras el procesamiento del HTML, como imágenes y hojas de estilo.
Sin embargo, en algunas ocasiones nos hará falta **detectar una fase anterior a la carga** en el ciclo de vida de la página: **cuando la jerarquía de objetos del DOM está lista para ser utilizada** desde nuestro código JavaScript. Esto implica que se ha interpretado todo el HTML, se han cargado los scripts y por lo tanto se puede manipular el contenido del documento sin problemas. En este momento sin embargo puede que todavía no se hayan cargado las imágenes y otros elementos, pero ya podemos manipular sin problemas el DOM.
Se trata de **un estado anterior al de la carga completa de la página, y es el momento ideal para asignar eventos**, puesto que hay ciertos eventos que se lanzarán incluso antes que el ''load'' de la página. Por ejemplo, las imágenes lanzan su propio evento ''load'' cuando cargan su contenido, y si queremos responder a éste entonces debemos hacerlo antes del ''load'' de la página completa.
La biblioteca jQuery nos ofrece [[https://api.jquery.com/ready/|el método ready()]] para detectar esta circunstancia, facilitándonos mucho la vida, pues basta con definir una función dentro de éste para que sea llamada en cuanto la página esté lista para ser manipulada.
En este apartado veremos cómo conseguir esta misma funcionalidad directamente con JavaScript y para todos los navegadores, sin utilizar ninguna biblioteca externa.
Todos los navegadores actuales disponen de una propiedad denominada ''readyState'', del objeto ''document''. Ésta nos devuelve una cadena de texto que indica el estado actual en el ciclo de carga de la página. Los valores que puede tener son los siguientes, ordenados por el momento en el que se producen dentro del ciclo de vida de la página:
^ Estado ^ Descripción ^
| ''loading'' | Se están empezando a cargar los datos de la página, e interpretando el HTML |
| ''interactive'' | El usuario ya puede interactuar con los elementos de la página aunque todavía no están cargados todos (por ejemplo, faltan las imágenes). |
| ''complete'' | Se ha terminado de cargar la página completamente |
A partir de esta propiedad, por lo tanto, es sencillo determinar en qué estado se encuentra nuestra página, y si está en estado ''interactive'' o superior, entonces sabemos que podemos ya interactuar con el DOM desde nuestros scripts. Pero: ¿cómo detectamos el cambio de estado?
Existe un evento estándar muy útil llamado ''onreadystatechange''. Basta con interceptarlo y saltará en cada uno de los cambios de estado (o sea, dos veces: de ''loading'' a ''interactive'' y de éste a ''complete''), por lo que es fácil determinar mediante ''document.readyState'' en qué estado nos encontramos:
document.addEventListener('readystatechange', function() {
console.log(document.readyState);
});
Y de este modo, cuando llegue a interactivo, podemos determinar que al menos el DOM ya está cargado. Fíjate en que este evento se intercepta en el documento, no en la ventana.
De todos modos existe un evento más apropiado denominado [[https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event|DOMContentLoaded]], que nos permite determinar esto de manera automática. Basta con interceptarlo para saber cuándo está la página lista para poder interactuar con el DOM:
window.addEventListener('DOMContentLoaded', function() {
console.log('El DOM ya está interactivo en este momento!!');
});
Este evento salta para la ventana, no para el documento.
==== DEMO: Salida de la página ====
Veremos cómo se gestionan los eventos de ciclo de vide de la página, en concreto los que permiten detectar cuándo una página se descarga.
Partimos de este HTML:
Prueba de abandono de página
Ejemplo de abandono de página
Ir a otra página
Para detectar que la página se está descargando de memoria para dar paso a una nueva carga de sí misma o de otra página, podemos usar el evento ''unload'':
Prueba de abandono de página
Ejemplo de abandono de página
Ir a otra página
Con ese ''unload'' lograremos que cada vez que se cargue actualice la página o pinchemos en el enlace para salir de ella, nos saltará ese ''alert''.
Esto tiene algunas limitaciones como que si cerramos el navegador, no salta ese ''alert''.
Existe un evento específico llamada ''onbeforeunload'' que se dispara justo antes de que se vaya a descargar la página, permitiéndonos reaccionar antes de que la página se descarga y también preguntar al usuario si quiere descargar la página. Esto se suele ver en aplicaciones web de correo para que no cerremos la ventana antes de enviar un e-mail, por ejemplo. También cuando tenemos algún cambio pendiente de confirmar.
Prueba de abandono de página
Ejemplo de abandono de página
Por defecto preguntará hasta que pulses el botón de "No preguntar"
Ir a otra página
==== Eventos del ratón ====
Esta es la manera más obvia de interactuar con el usuario. Ya hemos visto el más importante, ''click'', que no tiene mucho misterio, pero hay más. A continuación tienes los más importantes:
* ''click'': se produce cuando el usuario pulsa un elemento con el botón principal del ratón (generalmente el izquierdo). Es importante señalar que si un elemento tiene el foco y el usuario pulsa la tecla ENTER entonces salta también este evento, algo importante para usuarios con discapacidad.
* ''contextmenu'': salta cuando el usuario pulsa con el botón secundario del ratón (generalmente el derecho) y aparece el menú contextual del elemento.
* ''dblclick'': detecta el hacer doble clic sobre un elemento.
* ''mousedown'': se lanza cuando el usuario pulsa un botón del ratón sobre algún elemento, pero antes de que lo suelte. Es decir, es como medio clic.
* ''mouseup'': es el complementario del anterior. Salta cuando el usuario suelta el botón del ratón tras haberlo pulsado. Una secuencia completa de ''mousedown'' + ''mouseup'' genera un ''click'' si no se cancela el evento antes.
* ''mousemove'': se genera cuando el cursor se mueve por encima de un elemento.
* ''mouseover'': se lanza cuando el cursor **entra** en el área de un elemento. Por ejemplo, si tenemos un ''div'' en forma de cuadrado en la página, cuando el cursor entra sobre el cuadrado se lanza este evento y cuando ya está sobre éste y se mueve se generaría el ''mousemove''.
* ''mouseenter'': es igual que el anterior pero difiere en que no converge, es decir, no se notifica en otros niveles del DOM. Sin embargo se notifica para todos los elementos que contienen a otro a medida que se sale de la superficie de cada uno de los interiores hacia el exterior. Esto puede dar lugar a muchos eventos en caso de que se capture en todos ellos. En general se recomienda mejor utilizar el anterior.
* ''mouseout'': es el contrario del anterior y se da cuando el cursor **sale** de encima de un elemento. Cuando el ratón se mueve de un elemento a otro, en el que abandona salta el mouseout y en el que entra lanza un ''mouseover''.
* ''mouseleave'': como el anterior pero sin converger. Se recomienda utilizar ''mouseout''.
* ''scroll'': este evento salta cuando la ventana sobre la que se captura mueve su contenido internamente. Por ejemplo en una ventana o en un ''iframe'' cuyos contenidos excedan sus dimensiones y tenga por lo tanto barras de scroll. Se puede leer la cantidad de píxeles que se ha movido el contenido usando las propiedades ''scrollX'' y ''scrollY'' del objeto evento. Más adelante hacemos un ejemplo muy completo sobre cómo detectar la visibilidad de un objeto dentro de una página.
* ''wheel'': permite conocer cuándo el usuario mueve la típica rueda que hay en el medio de los botones de la mayoría de ratones del mercado. Las propiedades ''deltaX'', ''deltaY'' y ''deltaZ'' del evento nos indican las cantidades que se ha movido la rueda respecto a la posición anterior. Estas unidades pueden ser píxeles, líneas o páginas, y podemos averiguarlo mediante la propiedad ''deltaMode'' del evento, que puede tomar los valores 0, 1 o 2 respectivamente para indicarlo. Si el número es negativo es que la rueda se ha movido hacia atrás. En versiones antiguas de Internet Explorer se definía un método similar llamado ''[[https://developer.mozilla.org/en-US/docs/Web/API/MouseWheelEvent|OnMouseWheel]]'' y que fue el pionero de este tipo de eventos.
* ''select'': salta cuando se ha seleccionado algún contenido en la página, con el ratón, con los dedos (en pantallas táctiles) o con cualquier otro método. Es posible obtener el contenido seleccionado usando las propiedades ''selectionStart'' y ''selectionEnd'' del elemento seleccionado. Puedes ver un ejemplo [[https://developer.mozilla.org/en-US/docs/Web/API/Element/select_event|aquí]].
Existen algunos eventos de ratón adicionales relacionados con las operaciones de arrastrar y soltar (''drag'', ''drop'', ''dragstart'', ''dragend'', ''dragenter'', ''dragleave'' y ''dragover'') pero no los estudiaremos en este curso por estar más relacionados con la API de HTML5 específica para arrastrar y soltar.
Un evento ''click'' siempre va precedido por un evento ''mousedown'' y otro ''mouseup''. Y un ''dblclick'' duplica ese patrón, ya que obviamente para conseguir un doble clic son necesarios dos clics y para cada clic es necesario bajar y subir el botón.
**Todos los eventos del ratón se propagan** por el DOM pero todos **pueden cancelar esta propagación** (con ''stopPropagation'') y todos **pueden cancelar su acción por defecto** (con ''preventDefault'').
Hay cierta información que podemos necesitar a la hora de trabajar con los eventos de ratón, como las coordenadas en las que se produjo el evento o si había algún botón pulsado. Todas ellas las podemos obtener a partir de [[https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent#Properties|propiedades del objeto evento]], en concreto:
* ''clientX'', ''clientY'': son las propiedades horizontal y vertical que tenía el ratón dentro del área cliente del navegador (el [[https://www.campusmvp.es/recursos/post/desarrollo-web-movil-que-diferencia-existe-entre-el-viewport-y-la-resolucion-de-pantalla-de-un-dispositivo.aspx|viewport]]) cuando se produjo el evento. Están referidas a la esquina superior izquierda de esta área. También existen unos alias más abreviados que son simplemente x e y, por lo que podemos escribir tan solo ''event.x'' o ''event.y'' y obtenemos lo mismo. Aunque las propiedades originales son las otras y estas no aparecen en el estándar, si bien funcionan incluso con IE6.
* ''offsetX'', ''offsetY'': las coordenadas del ratón respecto al borde del elemento sobre el que se detecta el evento, considerando el relleno también (es decir, considera el padding pero no el borde). Útil si solo nos interesan las coordenadas dentro del elemento.
* ''pageX'', ''pageY'': las coordenadas del ratón respecto al total de la página, no del área visible. Si no hay scroll entonces coinciden con ''clientX'' y ''clientY'', pero en general no será así y usaremos estas últimas.
* ''screenX'', ''screenY'': las mismas coordenadas pero esta vez referidas a la pantalla completa del ordenador, es decir, a la esquina superior izquierda de la pantalla.
* ''movementX'', ''movementY'': coordenadas relativas a la última ve que se lanzó el evento ''mouseMove''. Poca aplicación práctica, aunque puede ser útil para casos especiales.
* ''button'': esta propiedad nos indica qué botón estaba pulsado durante los eventos ''mousedown'' o ''mouseup''. Su valor es 0 para el botón izquierdo, 1 para el del medio y 2 para el derecho o secundario. En ratones especiales que tienen botones para otros propósitos (como navegar hacia delante o atrás con el navegador) puede devolver también los valores 3 y 4 para dichos botones.
* ''Shiftkey'', ''ctrlKey'', ''altKey'', ''metaKey'': es el estado de pulsación (''true'' si están pulsadas), cuando se produjo el evento, de las distintas teclas de control que tienen los ordenadores: la tecla de mayúsculas, la de control, la ALT y la tecla meta (presente en algunos sistema UNIX, no soportada por IE). Útil si queremos combinar el evento del ratón con tener alguna de estas teclas pulsada.
* ''relatedTarget'': esta propiedad solo se establece durante los eventos ''mouseout'' y ''mouseover'' (y ''mouseenter'' y ''mouseleave'' que son casi equivalentes) y sirve para indicar qué elemento sigue o precede respectivamente al elemento actual sobre el que actúa el evento. Es decir, si muevo el cursor del elemento A al B, salta el elemento ''mouseout'' en A con ''relatedTarget'' apuntando a B y acto seguido salta ''mouseover'' en B con ''relatedTarget'' apuntando a A. De este modo es fácil conocer el camino que ha seguido el ratón. En el modelo de eventos de Internet Explorer no existe y tendremos que recurrir a dos propiedades relacionadas llamadas ''toElement'' y ''fromElement''. Se puede añadir una línea más a nuestra función de compatibilidad (creada anteriormente, ''fixEvent'') para solucionarlo, así:
if (!evt.relatedTarget) evt.relatedTarget = evt.fromElement ? evt.fromElement : evt.toElement;
==== DEMO: Perseguir al ratón ====
La siguiente página HTML (con su código JavaScript) dibujará un rectángulo y sus coordenadas que irán persiguiendo el puntero del ratón.
¡Por qué me persigues!
El archivo ''EventHandlerHelper.js'' contiene funciones para compatibilidad con navegadores antiguos:
var EventHandlerHelper =
{
addEventListener: function (elt, nomEvnt, handler) {
if (elt.addEventListener)
elt.addEventListener(nomEvnt, handler, false);
else {
if (elt.attachEvent)
elt.attachEvent("on" + nomEvnt, handler);
else
elt["on" + nomEvnt] = handler;
}
},
removeEventListener: function (elt, nomEvnt, handler) {
if (elt.removeEventListener)
elt.removeEventListener(nomEvnt, handler, false);
else {
if (elt.detachEvent)
elt.detachEvent("on" + nomEvnt, handler);
else
elt["on" + nomEvnt] = null;
}
},
fixEvent: function (event) {
var evt = event ? event : window.event;
if (!evt.bubbles) evt.bubbles = evt.cancelBubble;
if (!evt.cancelable) evt.cancelable = evt.returnValue;
if (!evt.currentTarget) evt.currentTarget = evt.srcElement;
if (!evt.preventDefault) evt.preventDefault = function () { evt.returnValue = false; };
if (!evt.stopPropagation) evt.stopPropagation = function () { evt.cancelBubble = true; };
if (!evt.target) evt.target = evt.srcElement;
if (!evt.view) evt.view = window;
if (!evt.relatedTarget) evt.relatedTarget = evt.fromElement ? evt.fromElement : evt.toElement;
if (!evt.which) evt.which = evt.keyCode != 0 ? evt.keyCode: evt.charCode;
if (!evt.key) evt.key = String.fromCharCode(evt.which);
return evt;
}
};
==== DEMO: Anular menú contextual ====
En la siguiente página web, pulsando en el primer título con el botón derecho, se abrirá el menú contextual, pero no lo hará si lo hacemos en el segundo título (el que tiene ''oncontextmenu'').
En esta línea SÍ se puede mostrar el menú contextual
En esta linea sin embargo NO se mostrará
==== DEMO: Detección de scroll ====
En la siguiente página, mientras hacemos scroll, la imagen de la izquierda se mantendrá en su posición.
El ingenioso hidalgo Don Quixote de La Mancha
Capítulo primero
Que trata de la condición y ejercicio del famoso hidalgo D. Quijote de la Mancha
En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor. Una olla de algo más vaca que carnero, salpicón las más noches, duelos y quebrantos los sábados, lentejas los viernes, algún palomino de añadidura los domingos, consumían las tres partes de su hacienda. El resto della concluían sayo de velarte, calzas de velludo para las fiestas con sus pantuflos de lo mismo, los días de entre semana se honraba con su vellori de lo más fino. Tenía en su casa una ama que pasaba de los cuarenta, y una sobrina que no llegaba a los veinte, y un mozo de campo y plaza, que así ensillaba el rocín como tomaba la podadera. Frisaba la edad de nuestro hidalgo con los cincuenta años, era de complexión recia, seco de carnes, enjuto de rostro; gran madrugador y amigo de la caza. Quieren decir que tenía el sobrenombre de Quijada o Quesada (que en esto hay alguna diferencia en los autores que deste caso escriben), aunque por conjeturas verosímiles se deja entender que se llama Quijana; pero esto importa poco a nuestro cuento; basta que en la narración dél no se salga un punto de la verdad.
Es, pues, de saber, que este sobredicho hidalgo, los ratos que estaba ocioso (que eran los más del año) se daba a leer libros de caballerías con tanta afición y gusto, que olvidó casi de todo punto el ejercicio de la caza, y aun la administración de su hacienda; y llegó a tanto su curiosidad y desatino en esto, que vendió muchas hanegas de tierra de sembradura, para comprar libros de caballerías en que leer; y así llevó a su casa todos cuantos pudo haber dellos; y de todos ningunos le parecían tan bien como los que compuso el famoso Feliciano de Silva: porque la claridad de su prosa, y aquellas intrincadas razones suyas, le parecían de perlas; y más cuando llegaba a leer aquellos requiebros y cartas de desafío, donde en muchas partes hallaba escrito: la razón de la sinrazón que a mi razón se hace, de tal manera mi razón enflaquece, que con razón me quejo de la vuestra fermosura, y también cuando leía: los altos cielos que de vuestra divinidad divinamente con las estrellas se fortifican, y os hacen merecedora del merecimiento que merece la vuestra grandeza. Con estas y semejantes razones perdía el pobre caballero el juicio, y desvelábase por entenderlas, y desentrañarles el sentido, que no se lo sacara, ni las entendiera el mismo Aristóteles, si resucitara para sólo ello. No estaba muy bien con las heridas que don Belianis daba y recibía, porque se imaginaba que por grandes maestros que le hubiesen curado, no dejaría de tener el rostro y todo el cuerpo lleno de cicatrices y señales; pero con todo alababa en su autor aquel acabar su libro con la promesa de aquella inacabable aventura, y muchas veces le vino deseo de tomar la pluma, y darle fin al pie de la letra como allí se promete; y sin duda alguna lo hiciera, y aun saliera con ello, si otros mayores y continuos pensamientos no se lo estorbaran.
Tuvo muchas veces competencia con el cura de su lugar (que era hombre docto graduado en Sigüenza), sobre cuál había sido mejor caballero, Palmerín de Inglaterra o Amadís de Gaula; mas maese Nicolás, barbero del mismo pueblo, decía que ninguno llegaba al caballero del Febo, y que si alguno se le podía comparar, era don Galaor, hermano de Amadís de Gaula, porque tenía muy acomodada condición para todo; que no era caballero melindroso, ni tan llorón como su hermano, y que en lo de la valentía no le iba en zaga.
En resolución, él se enfrascó tanto en su lectura, que se le pasaban las noches leyendo de claro en claro, y los días de turbio en turbio, y así, del poco dormir y del mucho leer, se le secó el cerebro, de manera que vino a perder el juicio. Llenósele la fantasía de todo aquello que leía en los libros, así de encantamientos, como de pendencias, batallas, desafíos, heridas, requiebros, amores, tormentas y disparates imposibles, y asentósele de tal modo en la imaginación que era verdad toda aquella máquina de aquellas soñadas invenciones que leía, que para él no había otra historia más cierta en el mundo.
Decía él, que el Cid Ruy Díaz había sido muy buen caballero; pero que no tenía que ver con el caballero de la ardiente espada, que de sólo un revés había partido por medio dos fieros y descomunales gigantes. Mejor estaba con Bernardo del Carpio, porque en Roncesvalle había muerto a Roldán el encantado, valiéndose de la industria de Hércules, cuando ahogó a Anteo, el hijo de la Tierra, entre los brazos. Decía mucho bien del gigante Morgante, porque con ser de aquella generación gigantesca, que todos son soberbios y descomedidos, él solo era afable y bien criado; pero sobre todos estaba bien con Reinaldos de Montalbán, y más cuando le veía salir de su castillo y robar cuantos topaba, y cuando en Allende robó aquel ídolo de Mahoma, que era todo de oro, según dice su historia. Diera él, por dar una mano de coces al traidor de Galalón, al ama que tenía y aun a su sobrina de añadidura.
En efecto, rematado ya su juicio, vino a dar en el más extraño pensamiento que jamás dio loco en el mundo, y fue que le pareció convenible y necesario, así para el aumento de su honra, como para el servicio de su república, hacerse caballero andante, e irse por todo el mundo con sus armas y caballo a buscar las aventuras, y a ejercitarse en todo aquello que él había leído, que los caballeros andantes se ejercitaban, deshaciendo todo género de agravio, y poniéndose en ocasiones y peligros, donde acabándolos, cobrase eterno nombre y fama.
Imaginábase el pobre ya coronado por el valor de su brazo por lo menos del imperio de Trapisonda: y así con estos tan agradables pensamientos, llevado del estraño gusto que en ellos sentía, se dió priesa a poner en efecto lo que deseaba. Y lo primero que hizo, fue limpiar unas armas, que habían sido de sus bisabuelos, que, tomadas de orín y llenas de moho, luengos siglos había que estaban puestas y olvidadas en un rincón. Limpiólas y aderezólas lo mejor que pudo; pero vió que tenían una gran falta, y era que no tenía celada de encaje, sino morrión simple; mas a esto suplió su industria, porque de cartones hizo un modo de media celada, que encajada con el morrión, hacía una apariencia de celada entera. Es verdad que para probar si era fuerte, y podía estar al riesgo de una cuchillada, sacó su espada, y le dió dos golpes, y con el primero y en un punto deshizo lo que había hecho en una semana: y no dejó de parecerle mal la facilidad con que la había hecho pedazos, y por asegurarse de este peligro, lo tornó a hacer de nuevo, poniéndole unas barras de hierro por de dentro de tal manera, que él quedó satisfecho de su fortaleza; y, sin querer hacer nueva experiencia de ella, la diputó y tuvo por celada finísima de encaje. Fue luego a ver a su rocín, y aunque tenía más cuartos que un real, y más tachas que el caballo de Gonela, que tantum pellis, et ossa fuit, le pareció que ni el Bucéfalo de Alejandro, ni Babieca el del Cid con él se igualaban. Cuatro días se le pasaron en imaginar qué nombre le podría: porque, según se decía él a sí mismo, no era razón que caballo de caballero tan famoso, y tan bueno él por sí, estuviese sin nombre conocido; y así procuraba acomodársele, de manera que declarase quien había sido, antes que fuese de caballero andante, y lo que era entones: pues estaba muy puesto en razón, que mudando su señor estado, mudase él también el nombre; y le cobrase famoso y de estruendo, como convenía a la nueva orden y al nuevo ejercicio que ya profesaba: y así después de muchos nombres que formó, borró y quitó, añadió, deshizo y tornó a hacer en su memoria e imaginación, al fin le vino a llamar ROCINANTE, nombre a su parecer alto, sonoro y significativo de lo que había sido cuando fue rocín, antes de lo que ahora era, que era antes y primero de todos los rocines del mundo. Puesto nombre y tan a su gusto a su caballo, quiso ponérsele a sí mismo, y en este pensamiento, duró otros ocho días, y al cabo se vino a llamar DON QUIJOTE, de donde como queda dicho, tomaron ocasión los autores de esta tan verdadera historia, que sin duda se debía llamar Quijada, y no Quesada como otros quisieron decir. Pero acordándose que el valeroso Amadís, no sólo se había contentado con llamarse Amadís a secas, sino que añadió el nombre de su reino y patria, por hacerla famosa, y se llamó Amadís de Gaula, así quiso, como buen caballero, añadir al suyo el nombre de la suya, y llamarse DON QUIJOTE DE LA MANCHA, con que a su parecer declaraba muy al vivo su linaje y patria, y la honraba con tomar el sobrenombre della.
Limpias, pues, sus armas, hecho del morrión celada, puesto nombre a su rocín, y confirmándose a sí mismo, se dió a entender que no le faltaba otra cosa, sino buscar una dama de quien enamorarse, porque el caballero andante sin amores, era árbol sin hojas y sin fruto, y cuerpo sin alma. Decíase él: si yo por malos de mis pecados, por por mi buena suerte, me encuentro por ahí con algún gigante, como de ordinario les acontece a los caballeros andantes, y le derribo de un encuentro, o le parto por mitad del cuerpo, o finalmente, le venzo y le rindo, ¿no será bien tener a quién enviarle presentado, y que entre y se hinque de rodillas ante mi dulce señora, y diga con voz humilde y rendida: yo señora, soy el gigante Caraculiambro, señor de la ínsula Malindrania, a quien venció en singular batalla el jamás como se debe alabado caballero D. Quijote de la Mancha, el cual me mandó que me presentase ante la vuestra merced, para que la vuestra grandeza disponga de mí a su talante? ¡Oh, cómo se holgó nuestro buen caballero, cuando hubo hecho este discurso, y más cuando halló a quién dar nombre de su dama! Y fue, a lo que se cree, que en un lugar cerca del suyo había una moza labradora de muy buen parecer, de quien él un tiempo anduvo enamorado, aunque según se entiende, ella jamás lo supo ni se dió cata de ello. Llamábase Aldonza Lorenzo, y a esta le pareció ser bien darle título de señora de sus pensamientos; y buscándole nombre que no desdijese mucho del suyo, y que tirase y se encaminase al de princesa y gran señora, vino a llamarla DULCINEA DEL TOBOSO, porque era natural del Toboso, nombre a su parecer músico y peregrino y significativo, como todos los demás que a él y a sus cosas había puesto.
Para navegadores antiguos, tendríamos que hacerlo de otra manera:
El ingenioso hidalgo Don Quixote de La Mancha
Capítulo primero
Que trata de la condición y ejercicio del famoso hidalgo D. Quijote de la Mancha
En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor. Una olla de algo más vaca que carnero, salpicón las más noches, duelos y quebrantos los sábados, lentejas los viernes, algún palomino de añadidura los domingos, consumían las tres partes de su hacienda. El resto della concluían sayo de velarte, calzas de velludo para las fiestas con sus pantuflos de lo mismo, los días de entre semana se honraba con su vellori de lo más fino. Tenía en su casa una ama que pasaba de los cuarenta, y una sobrina que no llegaba a los veinte, y un mozo de campo y plaza, que así ensillaba el rocín como tomaba la podadera. Frisaba la edad de nuestro hidalgo con los cincuenta años, era de complexión recia, seco de carnes, enjuto de rostro; gran madrugador y amigo de la caza. Quieren decir que tenía el sobrenombre de Quijada o Quesada (que en esto hay alguna diferencia en los autores que deste caso escriben), aunque por conjeturas verosímiles se deja entender que se llama Quijana; pero esto importa poco a nuestro cuento; basta que en la narración dél no se salga un punto de la verdad.
Es, pues, de saber, que este sobredicho hidalgo, los ratos que estaba ocioso (que eran los más del año) se daba a leer libros de caballerías con tanta afición y gusto, que olvidó casi de todo punto el ejercicio de la caza, y aun la administración de su hacienda; y llegó a tanto su curiosidad y desatino en esto, que vendió muchas hanegas de tierra de sembradura, para comprar libros de caballerías en que leer; y así llevó a su casa todos cuantos pudo haber dellos; y de todos ningunos le parecían tan bien como los que compuso el famoso Feliciano de Silva: porque la claridad de su prosa, y aquellas intrincadas razones suyas, le parecían de perlas; y más cuando llegaba a leer aquellos requiebros y cartas de desafío, donde en muchas partes hallaba escrito: la razón de la sinrazón que a mi razón se hace, de tal manera mi razón enflaquece, que con razón me quejo de la vuestra fermosura, y también cuando leía: los altos cielos que de vuestra divinidad divinamente con las estrellas se fortifican, y os hacen merecedora del merecimiento que merece la vuestra grandeza. Con estas y semejantes razones perdía el pobre caballero el juicio, y desvelábase por entenderlas, y desentrañarles el sentido, que no se lo sacara, ni las entendiera el mismo Aristóteles, si resucitara para sólo ello. No estaba muy bien con las heridas que don Belianis daba y recibía, porque se imaginaba que por grandes maestros que le hubiesen curado, no dejaría de tener el rostro y todo el cuerpo lleno de cicatrices y señales; pero con todo alababa en su autor aquel acabar su libro con la promesa de aquella inacabable aventura, y muchas veces le vino deseo de tomar la pluma, y darle fin al pie de la letra como allí se promete; y sin duda alguna lo hiciera, y aun saliera con ello, si otros mayores y continuos pensamientos no se lo estorbaran.
Tuvo muchas veces competencia con el cura de su lugar (que era hombre docto graduado en Sigüenza), sobre cuál había sido mejor caballero, Palmerín de Inglaterra o Amadís de Gaula; mas maese Nicolás, barbero del mismo pueblo, decía que ninguno llegaba al caballero del Febo, y que si alguno se le podía comparar, era don Galaor, hermano de Amadís de Gaula, porque tenía muy acomodada condición para todo; que no era caballero melindroso, ni tan llorón como su hermano, y que en lo de la valentía no le iba en zaga.
En resolución, él se enfrascó tanto en su lectura, que se le pasaban las noches leyendo de claro en claro, y los días de turbio en turbio, y así, del poco dormir y del mucho leer, se le secó el cerebro, de manera que vino a perder el juicio. Llenósele la fantasía de todo aquello que leía en los libros, así de encantamientos, como de pendencias, batallas, desafíos, heridas, requiebros, amores, tormentas y disparates imposibles, y asentósele de tal modo en la imaginación que era verdad toda aquella máquina de aquellas soñadas invenciones que leía, que para él no había otra historia más cierta en el mundo.
Decía él, que el Cid Ruy Díaz había sido muy buen caballero; pero que no tenía que ver con el caballero de la ardiente espada, que de sólo un revés había partido por medio dos fieros y descomunales gigantes. Mejor estaba con Bernardo del Carpio, porque en Roncesvalle había muerto a Roldán el encantado, valiéndose de la industria de Hércules, cuando ahogó a Anteo, el hijo de la Tierra, entre los brazos. Decía mucho bien del gigante Morgante, porque con ser de aquella generación gigantesca, que todos son soberbios y descomedidos, él solo era afable y bien criado; pero sobre todos estaba bien con Reinaldos de Montalbán, y más cuando le veía salir de su castillo y robar cuantos topaba, y cuando en Allende robó aquel ídolo de Mahoma, que era todo de oro, según dice su historia. Diera él, por dar una mano de coces al traidor de Galalón, al ama que tenía y aun a su sobrina de añadidura.
En efecto, rematado ya su juicio, vino a dar en el más extraño pensamiento que jamás dio loco en el mundo, y fue que le pareció convenible y necesario, así para el aumento de su honra, como para el servicio de su república, hacerse caballero andante, e irse por todo el mundo con sus armas y caballo a buscar las aventuras, y a ejercitarse en todo aquello que él había leído, que los caballeros andantes se ejercitaban, deshaciendo todo género de agravio, y poniéndose en ocasiones y peligros, donde acabándolos, cobrase eterno nombre y fama.
Imaginábase el pobre ya coronado por el valor de su brazo por lo menos del imperio de Trapisonda: y así con estos tan agradables pensamientos, llevado del estraño gusto que en ellos sentía, se dió priesa a poner en efecto lo que deseaba. Y lo primero que hizo, fue limpiar unas armas, que habían sido de sus bisabuelos, que, tomadas de orín y llenas de moho, luengos siglos había que estaban puestas y olvidadas en un rincón. Limpiólas y aderezólas lo mejor que pudo; pero vió que tenían una gran falta, y era que no tenía celada de encaje, sino morrión simple; mas a esto suplió su industria, porque de cartones hizo un modo de media celada, que encajada con el morrión, hacía una apariencia de celada entera. Es verdad que para probar si era fuerte, y podía estar al riesgo de una cuchillada, sacó su espada, y le dió dos golpes, y con el primero y en un punto deshizo lo que había hecho en una semana: y no dejó de parecerle mal la facilidad con que la había hecho pedazos, y por asegurarse de este peligro, lo tornó a hacer de nuevo, poniéndole unas barras de hierro por de dentro de tal manera, que él quedó satisfecho de su fortaleza; y, sin querer hacer nueva experiencia de ella, la diputó y tuvo por celada finísima de encaje. Fue luego a ver a su rocín, y aunque tenía más cuartos que un real, y más tachas que el caballo de Gonela, que tantum pellis, et ossa fuit, le pareció que ni el Bucéfalo de Alejandro, ni Babieca el del Cid con él se igualaban. Cuatro días se le pasaron en imaginar qué nombre le podría: porque, según se decía él a sí mismo, no era razón que caballo de caballero tan famoso, y tan bueno él por sí, estuviese sin nombre conocido; y así procuraba acomodársele, de manera que declarase quien había sido, antes que fuese de caballero andante, y lo que era entones: pues estaba muy puesto en razón, que mudando su señor estado, mudase él también el nombre; y le cobrase famoso y de estruendo, como convenía a la nueva orden y al nuevo ejercicio que ya profesaba: y así después de muchos nombres que formó, borró y quitó, añadió, deshizo y tornó a hacer en su memoria e imaginación, al fin le vino a llamar ROCINANTE, nombre a su parecer alto, sonoro y significativo de lo que había sido cuando fue rocín, antes de lo que ahora era, que era antes y primero de todos los rocines del mundo. Puesto nombre y tan a su gusto a su caballo, quiso ponérsele a sí mismo, y en este pensamiento, duró otros ocho días, y al cabo se vino a llamar DON QUIJOTE, de donde como queda dicho, tomaron ocasión los autores de esta tan verdadera historia, que sin duda se debía llamar Quijada, y no Quesada como otros quisieron decir. Pero acordándose que el valeroso Amadís, no sólo se había contentado con llamarse Amadís a secas, sino que añadió el nombre de su reino y patria, por hacerla famosa, y se llamó Amadís de Gaula, así quiso, como buen caballero, añadir al suyo el nombre de la suya, y llamarse DON QUIJOTE DE LA MANCHA, con que a su parecer declaraba muy al vivo su linaje y patria, y la honraba con tomar el sobrenombre della.
Limpias, pues, sus armas, hecho del morrión celada, puesto nombre a su rocín, y confirmándose a sí mismo, se dió a entender que no le faltaba otra cosa, sino buscar una dama de quien enamorarse, porque el caballero andante sin amores, era árbol sin hojas y sin fruto, y cuerpo sin alma. Decíase él: si yo por malos de mis pecados, por por mi buena suerte, me encuentro por ahí con algún gigante, como de ordinario les acontece a los caballeros andantes, y le derribo de un encuentro, o le parto por mitad del cuerpo, o finalmente, le venzo y le rindo, ¿no será bien tener a quién enviarle presentado, y que entre y se hinque de rodillas ante mi dulce señora, y diga con voz humilde y rendida: yo señora, soy el gigante Caraculiambro, señor de la ínsula Malindrania, a quien venció en singular batalla el jamás como se debe alabado caballero D. Quijote de la Mancha, el cual me mandó que me presentase ante la vuestra merced, para que la vuestra grandeza disponga de mí a su talante? ¡Oh, cómo se holgó nuestro buen caballero, cuando hubo hecho este discurso, y más cuando halló a quién dar nombre de su dama! Y fue, a lo que se cree, que en un lugar cerca del suyo había una moza labradora de muy buen parecer, de quien él un tiempo anduvo enamorado, aunque según se entiende, ella jamás lo supo ni se dió cata de ello. Llamábase Aldonza Lorenzo, y a esta le pareció ser bien darle título de señora de sus pensamientos; y buscándole nombre que no desdijese mucho del suyo, y que tirase y se encaminase al de princesa y gran señora, vino a llamarla DULCINEA DEL TOBOSO, porque era natural del Toboso, nombre a su parecer músico y peregrino y significativo, como todos los demás que a él y a sus cosas había puesto.
El contenido de ''EventHandlerHelper.js'' está en una sección anterior.
==== DEMO: Desactivar un botón de radio ====
En este apartado tratamos de solucionar un problema bastante común con los botones 'radio': una vez que seleccionamos una opción, no podemos pulsar de nuevo para "deseleccionarla".
Partimos de la siguiente página:
Primer grupo de opciones:
Segundo grupo de opciones:
Si probamos, al marcar una opción, no podremos desmarcarla. Siempre quedará marcada una hasta que volvamos a abrir la página.
Posible solución:
Primer grupo de opciones:
Segundo grupo de opciones:
==== Eventos del teclado ====
Otra gran necesidad que tienen los programas es la de **interactuar con los usuarios a través del teclado** del ordenador. Cuando un usuario rellena un campo en un formulario, podemos determinar qué teclas está pulsando y permitirle únicamente que utilice números, por ejemplo.
Existen cuatro eventos principales relacionados con el teclado que nos puede notificar el navegador:
* ''keydown'': se genera en el instante en el que el usuario pulsa una tecla y mientras la mantiene pulsada. Se notifica para el elemento que tenga el foco en ese momento (generalmente cajas de texto, pero puede ser cualquier elemento en realidad). Este evento también salta cuando se pulsan teclas de control o caracteres no imprimibles.
* ''keypress'': es igual al anterior pero solamente para caracteres imprimibles. Se genera continuamente mientras no se suelte la tecla, una vez por cada carácter que se va a mostrar en una caja de texto. Algunas excepciones son la pulsación de las teclas ESC y ENTER que aunque no son visibles se notifican también. Por ejemplo, con este evento no detectaríamos la pulsación de las flechas del cursor, mientras que con ''keydown'' sí. **En la actualidad se considera un evento obsoleto**, pero lo cierto es que se sigue utilizando mucho y no va a desaparecer de los navegadores.
* ''input'': se notifica cuando el texto que contiene un control editable (como una caja de texto) ha cambiado. También existe un evento ''[[https://developer.mozilla.org/en-US/docs/Web/Events/beforeinput|beforeInput]]'' que salta justo antes de que cambie el contenido, pero tiene una utilidad limitada y lo verás usado en pocas ocasiones.
* ''keyup'': se notifica cuando el usuario suelta la tecla que tenía presionada.
El orden en el que saltan los eventos es el mismo en el que aparecen en la lista anterior. Si el usuario deja una tecla pulsada saltarán los eventos ''keydown'', ''keypress'' e ''input'' repetidamente hasta que la suelte, momento en el que se notificará el ''keyup''. Si lo que se pulsa es un carácter no imprimible o una tecla de control el evento ''keypress'' no saltará.
Cuando se publicó la versión 2 del DOM y apareció el primer modelo de eventos, los eventos relacionados con el teclado quedaron fuera, y se siguió usando el modelo antiguo heredado del BOM. Los tres eventos ''keyX'' de la lista son, por tanto, tan antiguos como los primeros navegadores y han funcionado igual desde siempre, por lo que son un estándar de facto aunque no esté refrendado por el W3C. Más adelante, cuando se presentó el DOM Level 3, le añadieron eventos del teclado estandarizados, y más específicamente un nuevo evento denominado ''textInput''. Este evento sólo lo implementaron Chrome, Safari y las versiones de Internet Explorer superiores a la 9. Más adelante decidieron eliminarlo y dejar tan solo los eventos ''input'' y ''beforeInput'', que hemos visto más arriba.
Una vez que se notifica cualquiera de estos eventos obtendremos información detallada sobre la acción del usuario a través del objeto ''event'' y sus propiedades. Existen cuatro propiedades muy parecidas en el evento que nos proporcionan información sobre la tecla pulsada: ''keyCode'', ''charCode'', ''which'', y ''key''. Las tres primeras devuelven el código Unicode de la tecla pulsada, pero son dependientes del sistema operativo y navegador por lo que podrían cambiar, mientras que ''key'' devuelve siempre una cadena de texto que representa la tecla pulsada, incluyendo [[https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values|valores no imprimibles]]. Por ejemplo, si se pulsa la tecla "a" al mismo tiempo que se pulsa mayúsculas, la tecla resultante es "A", que será lo que devuelva la propiedad ''key''. De este modo no tendremos que comprobar si se está pulsando o no la tecla de mayúsculas.
os tres primeros se consideran obsoletos y **en navegadores modernos deberíamos utilizar ''key'' siempre**.
Todavía quedan un par de particularidades adicionales que debemos tener en cuenta (y que pueden cambiar con el tiempo, puesto que estas cosas no son estáticas, asi que ojo):
* No se notifica ''keypress'' al pulsar la tecla ESC ni otros caracteres imprimibles, a excepción de ENTER, CTRL + ENTER y SHIFT + ENTER. De todos modos, debes comprobarlo siempre si lo necesitas, puesto que puede variar de un navegador a otro o entre versiones.
* En el evento ''keydown'', independientemente de si la tecla se pulsa con mayúsculas o minúsculas activado, se devuelve el identificador Unicode de la tecla mayúscula. Sin embargo en ''keypress'' se devuelve el código de la letra que va a visualizarse. Así, por ejemplo si pulsamos la letra "a" minúscula, en ''keydown'' se notificará el número 65 (código ASCII de la "a" mayúscula en realidad), mientras que en ''keypress'' sería el 97 (el verdadero para esa letra). Esto tiene lógica porque se supone que con ''keydown'' estamos tratando de identificar la tecla que se pulsa mientras que en ''keypress'' el carácter que se va a introducir.
OJO: esto tiene como implicación que deberíamos separar adecuadamente entre los dos eventos el control de las teclas, dejando el ''keydown'' para teclas de control (por ejemplo, detectar que se ha pulsado ENTER) y el ''keypress'' (o mejor ''input'') para cuando necesitemos controlar la entrada de datos del usuario. Si es necesario utilizaremos los dos, ocupándose cada uno de la parte que le corresponde.
Estos códigos de tecla se pueden combinar con las propiedades ''Shiftkey'', ''ctrlKey'', ''altKey'' y ''metaKey'', ya vistas para los eventos de ratón, para saber si además estaban pulsadas esas teclas especiales durante el evento (que también se detectarán en ''keydown'').
La siguiente tabla muestra los códigos de las teclas más útiles que podemos detectar en ''keydown'' y ''keyup'':
^ Tecla ^ Código ^ Tecla ^ Código ^
| Borrado | 8 | Tabulador | 9 |
| ENTER | 13 | Mayúsculas | 16 |
| Control | 17 | Alt | 18 |
| Pause | 19 | Bloqueo Mays | 20 |
| ESC | 27 | Página Arriba | 33 |
| Página abajo | 34 | Fin | 35 |
| Inicio | 36 | ← | 37 |
| ↑ | 38 | → | 39 |
| ↓ | 40 | Insertar | 45 |
| Suprimir | 46 | Windows Izq. | 91 |
| Windows Der. | 92 | Menú Context. | 93 |
| 0-9 (tecl. num.) | 96-105 | * (tecl. numérico) | 106 |
| + (tec. num.) | 107 | - | 109 |
| . (tec. num.) | 110 | / (tec. num.) | 111 |
| F1-F12 | 112-123 | Bloq. Números | 144 |
| Bloqueo Despl. | 145 | | |
Existe una propiedad adicional llamada ''repeat'' que contiene un valor booleano para indicar si el usuario ha dejado pulsada o no la tecla que estamos detectando (pero no funciona ni en IE ni en versiones pre-Chromium de Edge).
==== DEMO: Permitir solo valores numéricos ====
En este ejemplo uso la propiedad ''which'' porque estoy empleando nuestro manejador universal, pero en un ejemplo real deberías utilizar la propiedad ''key''. De hecho, intenta recrear este ejemplo y el siguiente con la propiedad ''key'' del evento y sin ''EventHandlerHelper.js'' y te servirá como un buen ejercicio para practicar.
La siguiente página tiene dos cuadros de texto donde solo se podrán introducir números, hasta un máximo de 5 cifras en uno, y 4 en el segundo.
También veremos cómo controlar el pegado mediante Ctrl + V o menú contextual.
==== DEMO: Control básico de un marcianito ====
En la siguiente página HTML veremos cómo usando los eventos de teclado, en este caso los cursores y la tecla Ctrl, podremos mover una imagen por la pantalla.
Pulsa las flechas para mover al marciano.
Pulsa CTRL para acelerar el movimiento.
El anterior código JavaScript no permite detectar dos teclas a la vez, por si queremos hacer desplazamientos suaves en diagonal. Esto no se puede resolver solo con eventos, son necesarias técnicas más avanzadas.
==== Otros eventos ====
Existen muchos eventos que pueden producirse en una página, aunque ya hemos visto la mayoría de los interesantes para el día a día. Muchos son exclusivos de ciertos navegadores o de ciertos dispositivos, como por ejemplo los [[https://developer.mozilla.org/en-US/docs/Web/API/Touch_events|eventos relacionados con gestos multi-touch]] o con [[https://developer.mozilla.org/en-US/docs/Web/API/Window/orientationchange_event|cambios de orientación de pantalla]], exclusivos para móviles y tabletas (no los estudiaremos en este curso, pero son muy sencillos y puedes verlos en los enlaces anteriores). Otros tienen aplicaciones muy especializadas que raramente usarás.
En la MDN tienes [[https://developer.mozilla.org/en-US/docs/Web/Events|una referencia sensacional de todos ellos]] y te recomiendo que visites la página cuando tengas alguna duda o quieras conocer el soporte que ofrecen los navegadores de cada uno de ellos.
En este último apartado vamos a señalar algunos de los más interesantes que debes tener en cuenta.
==== Eventos de formularios y controles de entrada ====
{{ :informatica:programacion:cursos:programacion_avanzada_javascript:07-formulario-icono.jpg |}}
Para el caso del formulario ya hemos visto el más importante de ellos, ''submit'', que salta cuando se intenta enviar el formulario al servidor (y que ya hemos visto en algún vídeo práctico). También dispone de otro relacionado llamado ''reset'' que se notifica cuando se inicializa el formulario pulsando sobre un botón de tipo ''reset''.
Los controles contenidos en el formulario tiene sus propios eventos también, aparte de los que ya hemos visto relacionados con el ratón y el teclado:
* ''focus'': se notifica cuando el elemento coge el foco del teclado, bien porque pulsamos encima o porque nos movemos sobre él pulsando el tabulador.
* ''blur'': el contrario al anterior. Se lanza cuando el elemento pierde el foco.
* ''change'': se notifica en una caja de texto (tanto ''%%%%'' o en ''