Universidad de Costa Rica
Escuela de Ciencias de la Computación e Informática
CI-2413 Desarrollo de aplicaciones web
Profesor: Jeisson Hidalgo Céspedes
23-Octubre-2011
Este laboratorio continúa el aprendizaje autodidáctico del lenguage JavaScript.
JavaScript es un lenguaje genérico que cualquier software puede implementar para permitir al usuario programarlo. Aunque el número de sistemas que implementan JavaScript va en crecimiento, han sido los navegadores web el ejemplo histórico más evidente. El navegador web utiliza el lenguaje de programación JavaScript para exponer cierta funcionalidad al autor de un sitio web, quien debe aprovecharla sólo para mejorar la experiencia del usuario con su obra, haciéndole más fácil obtener o transmitir información. Por ejemplo, creando efectos visuales que guíen al usuario, ordenando los valores de una tabla para ayudarle a encontrar lo que necesita, ocultado o mostrando información que le es de interés, o intercambiando información con el servidor de tal forma que el usuario no se confunda con una recarga completa de la página.
Ha ido ganando popularidad una corriente llamada Unobtrusive JavaScript (JavaScript no impertinente). Este paradigma sustenta que JavaScript no debería tratar de llamar la atención a sí mismo. No consiste en una variación del lenguaje, sino en un conjunto de prácticas recomendadas, por ejemplo:
<script src="program.js">
. Esto ahorra ancho de banda, permite reutilizar el comportamiento a lo largo del sitio y facilita el mantenimiento del código.Utilizando JavaScript, el navegador pone a disposición del autor un conjunto de objetos que permiten manipular ventanas, cambiar el URL, modificar el contenido o estilo del documento, comunicarse con el servidor web, y otras operaciones útiles.
El objetivo primordial de un navegador web es desplegar al usuario documentos (X)HTML en una ventana. De ahí nacen los dos objetos más importantes para el programador: la ventana del navegador (window
), la cual contiene al documento web (document
) del autor.
El objeto window
es quizá el más importante, porque no sólo es un simple objeto, es el objeto global. Todas las propiedades del objeto global son variables globales, y viceversa: las variables globales que el programador defina se crearán como propiedades del objeto global window
. Así, las dos siguientes declaraciones hacen esencialmente lo mismo:
var answer = 722; window.answer = 722;
De lo anterior se obtiene que da lo mismo escribir window.document
que simplemente document
. Dicho de otra forma, el objeto window
es el más importante porque todos los demás objetos que existen se acceden a através de él, como se ve en la siguiente jerarquía parcial:
window parent top navigator location history screen document anchors[] links[] images[] applets[] forms[] elements[]
La jerarquía que nace del objeto document
ha sido estandarizada por el Consorcio Web (W3C) en lo que se llama Document Object Model (DOM), de tal forma que permite a los programadores JavaScript escribir código portable entre navegadores para manipular el documento y sus estilos.
Como se indicó al iniciar este capítulo, el código JavaScript puede correr en cuatro lugares: en un elemento script
, en un archivo .js
externo, en atributos manejadores de eventos y en el pseudoprotocolo javascript:
. Todos comparten el mismo objeto global window
y su descendencia; por ende, cualquier propiedad definida en uno de estos lugares, es accesible en todos los demás. En el siguiente ejemplo, la función global genRandom()
es definida en el primer script y usada en el segundo script.
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Global properties</title> <script type="text/javascript"> // Global property, same as window.genRandom = function() {...} function genRandom(maxValue) { return Math.floor(Math.random() * maxValue); } </script> </head> <body> <p>Su número de la suerte es: <script type="text/javascript"> // genRandom() is defined in another script. It can be called here // because both scripts share the same global object: window var num = genRandom(100); // Same as window.num = window.genRandom(100); document.write( num == 13 ? '(no juegue lotería esta semana)' : window.num ); </script> .</p> </body> </html>
window
. Correr este ejemplo.El código almacenado en los elementos script
se ejecuta sólo una única vez: cuando el documento (X)HTML es cargado en el navegador. Una vez que se ha completado la carga, la interacción se realiza mediante eventos. Cuando ocurre un evento, normalmente generado por el usuario, el navegador puede invocar código JavaScript del autor que reaccione al evento, el cual se asocia al objeto que genere eventos a través de atributos intrínsecos. En el siguiente ejemplo, cuando el usuario hace click en la imagen se invoca el método next()
del objeto window.player
, y mientras el mismo botón se mantiene presionado, se invoca el método forward
del mismo objeto.
<img onclick="player.next();" onmousedown="player.forward();" src="img/button_next.svg">
El modelo de ejecución de código JavaScript sigue esta regla. El código que aparece en los elementos script
es ejecutado en el mismo orden en que aparecen, durante la carga (parsing) el documento web. Cuando un elemento script
termina de ejecutarse, es reemplazado por su salida, la cual es el resultado de las posibles invocaciones a document.write()
que se hayan hecho. Inmediatamente el navegador continúa analizando esta salida como cualquier otro código (X)HTML, y posteriormente el resto del documento web.
Una vez que el documento ha terminado de analizarse, todos los elementos script
se habrán ejecutado y el contenido externo, como imágenes o sonidos, habrá sido cargado; el navegador dispara el primer evento: onload
. El código manejador de este evento se escribe como valor del atributo onload
del elemento body
. El código ejecutado en este punto, puede hacer modificaciones libremente sobre la estructura del documento, ya que éste se encuentra totalmente cargado y analizado (parsed); pero no debe invocar a document.write()
, ya que hará algo inesperado, como reemplazar el documento por completo.
Después de cargar el documento, correr los elementos script
y atender el evento onload
, el navegador entra en la fase de manejo de eventos (event-driven phase), ejecutando el código JavaScript que se haya asociado a los atributos intrínsecos según los vaya disparando el usuario. Estos manejadores de eventos tampoco deben invocar a document.write()
, ya que construirán un nuevo documento, remplazando el anterior.
Finalmente, cuando el usuario abandona la página, el browser dispara el evento onunload
también de body
, dando una oportunidad final al código JavaScript de correr. Este debe ser eficiente y silencioso, por ejemplo para hacer alguna limpieza necesaria, evitando demoras que confundan al usuario. La siguiente tabla resume los eventos intrínsecos que el autor dispone para mejorar la comunicación con el lector.
Evento | Descripción |
---|---|
onclick |
onclick se dispara cuando el usuario hace click sobre el elemento. Si onclick retorna false, el browser no efectúa la operación por defecto asociada al elemento, por ejemplo, no seguirá el hiperlink en caso de un enlace (a ), o no enviaría un formulario en caso de un botón submit. |
onmousedown , onmouseup |
onmousedown es disparado cuando el usuario presiona el ratón sobre un elemento y onmouseup cuando suelta el botón del ratón. Si ambos eventos ocurren sobre el mismo lugar de un elemento, se generará un onclick . La mayoría de elementos (X)HTML implementan estos dos eventos. |
onmouseover , onmouseout |
onmouseover se dispara mientras el cursor del ratón está sobre un elemento (X)HTML y onmouseout cuando sale de él. |
onchange |
El manejador onchange se dispara en los elementos que permiten ingresar texto, como input , select y textarea cuando el usuario cambia el valor desplegado del elemento y después mueve el foco hacia otro elemento. |
onload |
El evento onload sólo se puede asociar a body y es lanzado cuando el documento y su contenido externo, incluyendo imágenes, han sido completamente cargados. |
Supóngase que en una ventana del navegador el documento actual es reemplazado por otro, por ejemplo, cuando el usuario escribe un nuevo URL. El nuevo documento no puede acceder a las propiedades que el documento previo creó en la ventana, ya que el objeto window
es reestablecido (reset) por el navegador. Esto implica que todas las funciones y propiedades que los scripts definan, durarán mientras el documento actual se mantenga activo.
El hecho de que código ajeno sea ejecutado en su máquina a través de un navegador, da oportunidad a que intenciones malignas puedan llevarse a cabo. Por esto, los navegadores simplemente no soportan ciertas funcionalidades que en otros lenguajes son naturales, como la capacidad de leer, escribir o eliminar archivos en la computadora cliente.
Otras funcionalidades están limitadas. Por ejemplo, JavaScript puede emplear el protocolo HTTP para intercambiar información con el servidor pero no puede abrir un socket o utilizar alguna primitiva de programación de red. Algunas de estas restricciones son controlables por el usuario, ajustando la política de seguridad del navegador. De acuerdo a esta configuración su programa JavaScript:
La política del mismo origen (same-origin policy) indica que un script puede acceder únicamente a contenido que tiene el mismo origen que el documento que contiene el script. Es decir, que su código no puede acceder a otro documento que tenga cargado su navegador en otra ventana obtenido de otro sitio web. También dicta que el código JavaScript puede comunicarse a través del objeto XMLHttpRequest
sólo con el servidor web de donde se obtuvo el documento.
window
El objeto global window
provee varios métodos y otras propiedades para manipular el navegador, ventanas, direcciones, el historial y similares.
El método window.setTimeout(code, delay)
ejecuta el código JavaScript enviado en el parámetro code
, delay
milisegundos después de que se hace la invocación. Retorna un identificador opaco que necesitará el programador si desea cancelar dicho temporizador con el método window.clearTimeout()
. El código es invocado sólo una vez. Si se quiere que ocurra repetitivamente, el código en code
debe volver a establecer un setTimeout()
o bien usar el método timer = window.setInterval(code, period)
, el cual ejecuta el código code
repetitivamente en intervalos de period
milisegundos, hasta que se invoque window.clearInterval(timer)
. Esto es muy útil para realizar animaciones, como el siguiente reloj digital.
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Digital clock</title> <style type="text/css"> body { background: black; margin: 4cm 0; text-align: center; } #clock { font-size: 6em; color: #151520; text-shadow: 2px 2px 7px lightgray; } </style> </head> <body> <div id="clock">HH:MM:SS</div> <script type="text/javascript"> window.setInterval( 'updateClock()', 1000 ); function updateClock() { var date = new Date(); document.getElementById('clock').innerHTML = date.toLocaleTimeString(); } </script> </body> </html>
En el documento anterior, el elemento con identificador clock
de la línea 11 será utilizado para desplegar la hora local del navegador. Cuando el navegador carga el cuerpo de este documento, mostrará el div
como de costumbre, aplicándole los estilos que se declararon en la parte superior. Luego encontrará un elemento script
y lo ejecutará como es sabido. La invocación a setInterval()
en la línea 13 le indica al navegador que debe ejecutar el código updateClock()
dentro de 1 segundo, cada segundo. Al ser invocada, la función updateClock()
debe cambiar el contenido del <div id="clock">...</div>
con el valor actual de la hora. JavaScript requiere una referencia al objeto div
que tiene el identificador clock
, y la consigue invocando el método document.getElementById()
que se estudiará luego. Una vez obtenida la referencia al elemento div
, se cambia su contenido con la hora actual en el navegador obtenida en forma de string.
Ejercicio 1. Modifique el ejemplo del reloj digital para que muestre los milisegundos, y se actualice al menos cuatro veces cada segundo.
El siguiente ejemplo muestra un texto moverse de izquierda a derecha, en un ancho de 320 pixeles. Esta vez no se modifica el contenido de un elemento particular, sino su posición, lo cual es responsabilidad de la presentación, por lo que el código JavaScript debe modificar el estilo CSS del elemento con identificador text
dinámicamente. El estilo de un elemento se accede en JavaScript a través de su propiedad style
, la cual es un objeto cuyas propiedades tienen el mismo nombre que las propiedades CSS en notación "camelCase" (margin
, marginTop
, etc.), y sus valores son siempre un string -esto se estudiará con más detalle luego-. En este ejemplo, cada vez que se invoca, animateText()
calcula en la variable global textLeft
la nueva posición del texto (líneas 18 y 19); y en la línea 21 asigna el resultado de este cálculo a la propiedad style.left
, cuyo efecto es el mismo a haber escrito #text { left: textLeft; }
en CSS. Para que esto funcione, el elemento #text
debe "flotar" sobre el documento, lo cual se hizo en la línea 6 indicando que su posición es absoluta.
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Texto animado</title> <style type="text/css"> body { margin: 0; background: #ccc; color: #C8C8C8; font-size: 3em; } #text { position: absolute; top: 2em; text-shadow: 1px 1px white, -1px -1px #444; } </style> </head> <body> <div id="text">Su nombre aquí</div> <script type="text/javascript"> <!-- window.setInterval( 'animateText()', 50 ); var textLeft = 0; function animateText() { textLeft += 5; if ( textLeft > 320 ) textLeft = -320; var element = document.getElementById('text'); element.style.left = Math.abs(textLeft) + 'px'; } --> </script> </body>
Ejercicio 2. Modifique el ejemplo del texto animado para que cuando el usuario haga click en el texto, la animación se detenga, y si se vuelve a hacer click, la animación se reanude.
El objeto global window
tiene varias propiedades de sólo lectura que informan el tamaño de la ventana, el tamaño del documento y la posición del navegador en el escritorio:
Propiedad | Descripción |
---|---|
window.outerWidth , window.outerHeight |
Ancho y alto de la ventana del navegador. |
window.screenX , window.screenY |
Posición del navegador en el escritorio del usuario. |
window.innerWidth , window.innerHeight |
Tamaño del área donde el documento se despliega (también llamada área cliente o viewport. Equivale al tamaño de la ventana del navegador sin las barras de menú, herramientas, scrollbars, etc. |
window.pageXOffset , window.pageYOffset |
El segmento del documento actualmente desplegado en la ventana del navegador, expresado como la posición de las barras de desplazamiento (scrollbars). |
Ejercicio 3. Modifique su solución del texto animado para que el texto no esté limitado a un ancho de 320 pixeles, sino que se ajuste dinámicamente al ancho del navegador.
Ejercicio 4. En su solución del texto animado, reemplace el texto por una imagen, como una esfera o algún gráfico de su agrado. Haga que el gráfico se mueva aleatoriamente por el área de la ventana sin salirse de esta. Es decir, que el gráfico siempre es visible aún cuando se acerque a los extremos de la ventana.
El objeto window.screen
provee información sobre la pantalla del usuario, por ejemplo: las dimensiones en pixeles del escritorio (screen.width
y screen.height
), la profundidad de color en bits por píxel (screen.pixelDepth
), el área usable del escritorio sin incluir la barra de tareas (screen.availWidth
y screen.availHeight
). Con estas propiedades el programador puede escoger qué imágenes presentar, centrar una venana en el escritorio o tareas similares.
El objeto window.navigator
contiene información sobre el navegador mismo, como su nombre (navigator.appName
), versión (navigator.appVersion
), el nombre codificado (navigator.appCodeName
, el sistema operativo para el que se compiló el navegador (navigator.platform
), si tiene habilitados los cookies (navigator.cookieEnabled
), y la identificación que el browser envía en sus encabezados HTTP (navigator.userAgent
). No es recomendable utilizar esta información para escribir funcionalidad dependiente del navegador, sino para propósitos de estadísticas o bitácoras.
Ejercicio 5. Escriba un programa JavaScript que genere una o varias tablas con la información del navegador, la ventana y la pantalla citada anteriormente. No utilice document.write()
, sino que su programa debe llenar un elemento div
del documento. Provea un botón "Actualizar" que cada vez que se presiona, refresca la información anterior. Recuerde aprovechar el operador for-in.
JavaScript permite al autor crear nuevas ventanas de navegador en las que puede cargar documentos existentes de su servidor web o generar un nuevo documento dinámicamente. Sin embargo, el abuso que ha cometido la publicidad y otros fines, ha obligado a los fabricantes de navegadores a restringir esta funcionalidad de acuerdo a la política de seguridad, por ejemplo, a permitir abrir ventanas sólo en respuesta a un evento del usuario.
Una invocación al método window.open(url, name, features, repHistory)
crea y retorna una referencia hacia una nueva ventana, y por ende, un nuevo objeto window
con toda su descendencia de objetos. La nueva ventana cargará un documento si se especifica un url
, de lo contrario, tendrá un documento vacío. El parámetro name
le da un nombre a la ventana, si ya existe una con ese nombre simplemente se reutiliza, y en tal caso, si repHistory
es true
el historial de navegación en esa ventana es reemplazado; si es false
o se omite, el nuevo documento es simplemente agregado al historial. El siguiente ejemplo crea una pequeña ventana emergente de ayuda cuando se presiona el botón, y la cierra cuando se vuelve a presionar con el método window.close()
que cierra gráficamente la ventana pero no destruye el objeto window
.
<button id="helpButton" onclick="toggleHelp()">Ayuda</button> <script type="text/javascript"> var helpWindow; function toggleHelp() { if ( helpWindow ) { helpWindow.close(); helpWindow = undefined; document.getElementById('helpButton').innerHTML = 'Ayuda'; } else { helpWindow = window.open('help.html', 'help_window', 'width=400,height=350,status=yes,resizable=yes'); document.getElementById('helpButton').innerHTML = 'Cerrar ayuda'; } } </script>
El parámetro features
permite especificar el tamaño de la nueva ventana y otras decoraciones gráficas. Si se omite, se le dará tamaño estándar y todas las decoraciones existentes (barra de menú, barra de estado, ...). Algunas combinaciones son restringidas por razones de seguridad, por ejemplo, una ventana muy pequeña o situada en una zona no visible.
El objeto retornado por window.open()
se puede manipular como cualquier otro objeto window
. El siguiente ejemplo, genera un documento dinámicamente en una nueva ventana.
var subwindow = window.open(); // Create a new window with no content var subdoc = subwindow.document; // Get its Document object subdoc.open(); // Start a new document (optional) subdoc.write("<h1>Hello world!</h1>"); // Output document content subdoc.close(); // End the document
Nótese que tanto document
como window
tinen métodos open()
y close()
. El document.open(url)
carga un documento a partir del url
o genera uno vacío si se omite url
. Invocar a document.write()
es seguro en una ventana nueva ya que el documento se está cargando (parsing). El método document.close()
indica al navegador que el documento ha terminado de cargarse (parsing), el cual responde deteniendo la animación de carga. Sino nunca se llama a document.close()
, el navegador creerá que está ante un documento incompleto.
Ejercicio 6. Modifique el ejemplo de la ventana de ayuda. Cuando se presiona el botón, cree una nueva ventana con un documento vacío, es decir, el parámetro url
de window.open()
debe estar vacío. Haga que su programa JavaScript genere un documento dinámicamente en la nueva ventana, cuyo contenido sea la misma del ejercicio 5. Puede utilizar subwindow.document.write()
si lo desea.
El objeto window
tiene métodos para mover y redimensionar la ventana, lo cual es una pobre práctica, ya que esto debe ser un privilegio del usuario. Son los siguientes: window.moveTo(x, y)
mueve la esquina superior izquierda a las coordenadas dadas; window.moveBy(despX, despY)
mueve la ventana tantos pixeles relativos a la posición actual; window.resizeTo(width,height)
y window.resizeBy(addToWidth, addToHeight)
cambian el tamaño de la ventana.
Ejercicio 7. Haga que la ventana emergente del ejercicio anterior aparezca en el mejor lugar. Para esto, defina un ancho mínimo de la ventana emergente. Si en el escritorio hay campo a la izquierda o a la derecha del navegador, use el que tenga mayor espacio y ocupe ese ancho por completo, de tal forma que la nueva ventana aparecerá al lado del navegador y tendrá su misma altura. Si no hay espacio, sitúe la ventana emergente centrada en el navegador.
El método window.focus()
hace que la ventana sea la activa, y window.blur()
hace que renuncie a serlo. Es común llamar a focus()
después de hacer un window.open()
, ya que las ventanas recién creadas no lo tienen por defecto.
Es común querer abrir un documento existente en una nueva ventana y luego hacer que ésta se desplace a diferentes fragmentos del documento dinámicamente. Si los fragmentos están identificados con el atributo id
(como en <div id="frag1">...</div>
) o con anclas (<a name="frag2">...</a>
), se puede cambiar la ubicación del documento con un una de las siguientes instrucciones:
var subwindow = window.open('help.html', 'help_window'); subwindow.location.hash = "#frag1"; // crea una entrada en el History de subwindow subwindow.location.replace("#frag1"); // no crea una entrada en el History de subwindow
La propiedad window.location
de una ventana representa el URL del documento que está desplegado en dicha ventana. Es un objeto que tiene las siguientes propiedades:
Propiedad | Descripción |
---|---|
href |
Es un string con el texto completo del URL. |
protocol , host , pathname , search |
Representan partes individuales del URL. |
reload() |
Recarga la página como si el usuario lo hiciera en el navegador. |
replace(url) |
Reemplaza el documento actual en la ventana por uno nuevo cuyo URL es dado por parámetro. No genera una entrada en el historial de la ventana, de modo que el usuario no tiene forma de regresar al documento previo. |
Al objeto mismo window.location
se le puede asignar un string, de la forma window.location = url
, que causa el mismo efecto que llamar su método window.location.replace(url)
, con la diferencia de que se agrega una entrada al historial de navegación de la ventana, de tal forma que el usuario puede retornar al documento previo.
El objeto window
provee tres métodos para desplegar cuadros de diálogo. window.alert(msg)
despliega un mensaje y espera que el usuario lo acepte. window.confirm(msg)
presenta un mensaje y solicita al usuario que decida si lo acepta o cancela, lo cual se retorna como un boolean. window.prompt(msg, defaultValue)
presenta el mensaje msg
y espera que el usuario ingrese un string el cual es retornado o null
si cancela. Obligatoriamente.
Deben usarse con moderación, o mejor aún, nunca. La mayoría de usuarios se sentirá molesta al experimentarlos y recuerde que ellos tienen el poder de cerrar el navegador. Así que estos métodos son de ligera utilidad para el programador durante el proceso de desarrollo. Hace varios años, el navegador reportaba errores de JavaScript utilizando diálogos, lo cual era impertinente: le reclamaba al usuario errores que no eran de él. Los navegadores han cambiado a ocultar los errores y el programador debe buscarlos en alguna bitácora o similar.
document
En esencia un navegador es un programa que despliega un documento web en una ventana. La sección anterior explica cómo manipular con JavaScript esa ventana. Esta sección explica cómo manipular dinámicamente el documento en ella.
Las propiedades más conocidas del objeto document
, son los métodos write()
y writeln()
. Ambos reciben una cantidad arbitraria de parámetros y los escriben en el documento para que sean analizados (parsed) por el navegador posteriormente. Por eso, estos métodos sólo se deben invocar mientras se está cargando (parsing) el documento. Como es de esperar, writeln()
después de imprimir todos sus parámetros, agrega un carácter de cambio de línea. Otras propiedades útiles de document
son:
Propiedad | Descripción |
---|---|
document.title |
Accede al título del documento que se encuentra en el encabezado del mismo. |
document.lastModified |
Contiene un string con la fecha de última modificación del documento. |
document.URL |
Es un string con el URL completo de donde se obtuvo el documento. |
document.referrer |
Es un string con el URL del recurso a través del cual el usuario llegó al documento actual. |
document.domain |
Indica el dominio al cual pertenece el documento, y es usado por la política del mismo origen (same origin policy). |
Las propiedades anteriores permiten, por ejemplo, escribir un pie de página dinámico, como el siguiente:
document.writeln('<address>'); document.write('<strong><a href="', document.URL, '">', document.title,'</a></strong>'); document.writeln('. Última actualización: ', document.lastModified, '.<br/>'); document.write('©', new Date().getFullYear()); document.write(' <strong><a href="http://', document.domain,'">Mi Sitio</a></strong>'); document.writeln('. Todos los derechos reservados.'); document.writeln('</address>');
document
. Correr este ejemplo.Cuando el navegador carga un documento (X)HTML crea un objeto JavaScript por cada elemento, comentario, o trozo de texto que encuentra en el archivo. Estos objetos se asocian entre sí en una jerarquía llamada modelo de objetos del documento (DOM: Document Object Model), la cual es accesible y modificable a través de JavaScript. Es decir, el DOM es una representación interna del documento utilizando objetos. Un cambio en el DOM se refleja de inmediato en el navegador, lo que permite al autor ajustar el documento dinámicamente para mejorar la comunicación con sus lectores. Es quizá la funcionalidad más importante que un autor busca de JavaScript.
Los objetos en la jerarquía del DOM reciben el nombre genérico de nodos, construidos con la pseudoclase Node
. El nodo raíz es siempre el documento, el cual contiene nodos opcionales (declaración XML, declaración de tipo de documento, comentarios) y el nodo del elemento html
. Este último tiene dos hijos: el nodo del encabezado (head
) y el nodo del cuerpo del documento (body
). Los hijos del nodo body
varían de acuerdo al documento. Por ejemplo, la siguiente ilustración muestra un documento sencillo y el árbol de nodos que el navegador genera a partir de él.
Las relaciones jerárquicas entre nodos implican que un nodo puede tener cero o más nodos hijos (child nodes), nodos hermanos (siblings), un nodo padre (parent), nodos descendientes y nodos ancestros. Estas relaciones se implementan como propiedades de la pseudoclase Node
. Dado un nodo se puede acceder a los otros nodos con los que está relacionado. Estas y otras propiedades se resumen en la siguiente tabla.
Propiedad | Descripción |
---|---|
Node.childNodes[] |
Un arreglo que contiene los hijos del nodo actual en el mismo orden en que fueron definidos en el documento. Si el nodo actual no tiene hijos, este arreglo estará vacío. |
Node.firstChild |
El primer nodo hijo del nodo actual, o null si no tiene hijos. |
Node.lastChild |
El último nodo hijo del nodo actual, o null si no tiene hijos. |
Node.nextSibling |
El nodo hermano que continúa al actual o null si es el último hijo del nodo padre (parentNode ). Equivale al nodo que sigue al actual en el arreglo parentNode.childNodes[] . |
Node.previousSibling |
El nodo hermano que precede al actual o null si es el primer hijo del nodo padre (parentNode ). Equivale al nodo que precede al actual en el arreglo parentNode.childNodes[] . |
Node.parentNode |
El nodo padre o contenedor del nodo actual. Es null en el caso del nodo raíz, nodos que han sido separados (removidos) del documento, o nodos creados libremente que aún no han sido insertados en el documento. |
Node.nodeType |
Un entero que indica el tipo de nodo, por ejemplo: elemento (1), texto (3), comentarios (8). |
Node.nodeName |
Un string con un posible nombre para el nodo. Si el nodo es un elemento se utiliza el nombre del elemento ("body" , "div" , "p" , etc.). Para los demás tipos de nodos se utiliza un texto generado de concatenar el símbolo de número con el tipo de nodo, por ejemplo "#text" , "#comment" , "#document" , etc. |
Node.nodeValue |
Si el nodo es de texto (nodeType == 3 ) o comentario (nodeType == 8 ), esta propiedad tendrá un string con dicho texto; de lo contrario el valor null . |
Node.textContent |
Contiene el texto si el nodo es de texto (nodeType == 3 ) o comentario (nodeType == 8 ), en la misma forma que nodeValue . Para un elemento (nodeType == 1 ) contiene el texto resultado de concatenar todos los nodos de texto descendientes. |
Node.ownerDocument |
Una referencia hacia el documento que contiene este nodo. Sirve, por ejemplo, para saber si dos nodos son parte del mismo documento. |
El siguiente código recorre recursivamente todos los nodos del documento e imprime algunas de las propiedades heredadas de la pseudoclase Node
. La cantidad de nodos está fuertemente influenciada por el espacio en blanco que se utilice para indentar el marcado, ya que el parser lo interpretará como datos de carácter y los almacenará en nodos de texto. [Para este ejemplo, la impresión con document.write()
debe hacerse en un documento distinto, ya que si se hace sobre el mismo, generará una recursión infinita].
function printNodes(node, doc) { printProperties(node, doc); for ( var i = 0; i < node.childNodes.length; ++i ) printNodes( node.childNodes[i], doc ); } function printProperties(node, doc) { var properties = ['firstChild', 'lastChild', 'previousSibling', 'nextSibling', 'nodeName', 'nodeType', 'nodeValue', 'textContent' ]; for ( var i = 0; i < properties.length; ++i ) doc.writeln(properties[i], ': ', node[properties[i]]); }
Node
. Correr este ejemplo con indentación, o sin espacios en blanco.Además de las propiedades para acceder a nodos relacionados, la pseudoclase Node
provee métodos para modificarlos:
Propiedad | Descripción |
---|---|
Node.appendChild(newChild) |
Agrega el nodo newChild al final del arreglo childNodes[] . El cambio se ve reflejado de inmediato en el documento cargado en el navegador. Si newChild ya forma parte del documento, será removido de su posición actual y reinsertado en la nueva posición. |
Node.insertBefore(newChild, referenceChild) |
Inserta el nodo newChild en el arreglo childNodes[] justo antes que el nodo referenceChild , el cual obligatoriamente debe existir y ser hijo del nodo al cual se le invocó este método. Si referenceChild es null , newChild se insertará al final. Si newChild ya es parte del documento, será removido de su posición actual y reinsertado en la nueva posición. |
Node.replaceChild(newChild, oldChild) |
Reemplaza oldChild con newChild . Si newChild ya es parte del documento, será removido de su posición actual y reinsertado en la nueva posición. oldChild es separado del documento y el cambio se refleja en el navegador. El objeto oldChild no es eliminado de la memoria, sino que sigue vigente y puede ser reinsertado en el documento posteriormente. |
Node.removeChild(child) |
Quita al nodo child del arreglo childNodes[] y del documento (el cambio se refleja visualmente en el navegador). El objeto nodo child no es eliminado de la memoria, y puede ser reinsertado en el documento posteriormente. |
Node.cloneNode(recursively) |
Retorna una copia del nodo al cual este método es invocado. No clona los hijos y descendientes del nodo a menos que se envíe true en el parámetro recursively . El nodo retornado no es parte del documento, por lo que debe ser insertado posteriormente si se quiere que sea visible al lector. |
Node.isEqualNode(other) |
Retorna true si el nodo es idéntico al enviado por parámetro, es decir, tienen los mismos valores para sus propiedades de datos (como el nombre y tipo), y así recursivamente para todos sus hijos. |
Los métodos anteriores permiten mover nodos existentes de un lugar a otro, o clonarlos. El siguiente ejemplo ordena los nodos existentes de una lista alfabéticamente.
<body> <h1>Reubicar nodos en el documento</h1> <ol id="sortable_list"> <!-- Note: comments and text nodes will be ignored --> <li>Los</li> <li>12</li> <li>elementos</li> <li>de</li> <li>esta</li> <li>lista</li> <li>serán</li> <li>ordenados</li> <li>alfabéticamente</li> <li>sin contar</li> <li>Mayúsculas</li> <li><p>Incluyendo <strong>este párrafo</strong>.</p></li> </ol> <p><button id="sort_button" onclick="sort_list()">Ordenar</button></p> <script type="text/javascript"> <!-- function sort_list() { // Get the parent node whose children will be sorted var list_element = document.getElementById('sortable_list'); // Do not sort list_element.childNodes[] in place to avoid flickering // Get an array of the elements to be processed and work with this array var elements = []; // Fill the working array with only the desired nodes to be sorted for ( var child = list_element.firstChild; child; child = child.nextSibling ) if ( child.nodeName.toLowerCase() == 'li' ) elements.push(child); // Function used to sort the array function sort_elements(nodeA, nodeB) { var textA = nodeA.textContent.toLowerCase(); var textB = nodeB.textContent.toLowerCase(); return textA < textB ? -1 : textA > textB ? 1 : 0; } // Sort the elements alphabetically elements.sort(sort_elements); // Now elements are sorted in the array, not in the document, reinsert them // in the desired order. Note, each node will be dettached from its current // position and reinserted in the desired position automatically for ( var i = 0; i < elements.length; ++i ) list_element.appendChild( elements[i] ); } --> </script> </body>
Nótese que en el ejemplo anterior se construyó un arreglo temporal (llamado elements[]
), el cual se llenará únicamente con los elementos que deben ser ordenados, y luego serán reinsertados en el documento. ¿Por qué no se hizo este cambio directamente en list_element.childNodes[]
? Hay varias razones. La más importante es porque childNodes[]
no es realmente un arreglo, sino un objeto que se comporta como tal definiendo la propiedad length
y una propiedad con nombre numérico para cada elemento que se inserte en él. A estos objetos se les llama "objetos que aparentan ser arreglos" (array-like objects), y su propósito es proveer un arreglo controlado, casi de sólo lectura. Por ejemplo, el programador no puede invocar a list_element.childNodes.sort()
porque el array-like object no implementa este método.
La segunda razón es que el arreglo list_element.childNodes[]
muy probablemente contiene otros hijos que no son de importancia ordenar, como nodos de texto (surgidos de los espacios en blanco usados para indentar el código fuente) o comentarios. Por esto, es una práctica común construir un nuevo arreglo escogiendo los nodos que realmente se quieren modificar, afectar al arreglo y cuando el procesamiento esté listo, actualizar el documento. Esto reduce además un desagradable efecto de parpadeo (flickering) que confunde al lector al ver partes del documento cambiar arbitrariamente.
Ejercicio. Modifique el ejemplo de ordenar elementos de la lista para que cuando se presiona el botón, ordene intercaladamente en forma ascendente o descendente la lista de elementos.
Cuando se debe crear un nuevo nodo en el documento, no se debe hacer con el operador new
, sino utilizar los métodos document.createXxx(data)
de la pseudoclase Document
. Por ejemplo, document.createElement(tagName)
crea un nodo elemento con la etiqueta tagName
; document.createTextNode(string)
crea un nodo de texto con el string como contenido; document.createComment(string)
crea un comentario. Todos ellos crean un nuevo nodo el cual no está asociado al documento. El programador debe insertarlo posteriormente en la jerarquía de nodos para que sea visible al lector, usando alguno de los métodos de la pseudoclase Node
como appendChild()
, insertBefore()
o replaceChild()
. El siguiente ejemplo inserta nodos en una lista ordenada.
<body> <h1>Crear e insertar nodos en el documento</h1> <p><button onclick="create_node()">Agregar nodo</button></p> <ol id="dynamic_list"> <li>Presione el botón para agregarle elementos a la lista</li> </ol> <script type="text/javascript"> var counter = 0; function create_node() { var list_item = document.createElement('li'); var text_node = document.createTextNode('Elemento autogenerado ' + ++counter); list_item.appendChild( text_node ); document.getElementById('dynamic_list').appendChild(list_item); } </script> </body>
Ejercicio 8. Escriba un documento XHTML con un poema, y un botón que al ser presionado invierte el orden de las estrofas (no de los versos), utilizando los métodos de la pseudoclase Node
. Imprima los números de las estrofas y resáltelos con estilos CSS para que el cambio sea visualmente evidente.
Los nodos pueden ser de distinta naturaleza: elementos, atributos, comentarios, texto, etc. La propiedad Node.nodeType
indica el tipo de un nodo particular; es un entero que puede tomar el valor de una de las siguientes constantes:
Valor | Constante | Pseudoclase XML | Pseudoclase HTML |
---|---|---|---|
1 | Node.ELEMENT_NODE |
Element |
HTMLElement |
2 | Node.ATTRIBUTE_NODE |
Attr |
|
3 | Node.TEXT_NODE |
Text |
|
4 | Node.CDATA_SECTION_NODE |
CharacterData |
|
5 | Node.ENTITY_REFERENCE_NODE |
EntityReference |
|
6 | Node.ENTITY_NODE |
Entity |
|
7 | Node.PROCESSING_INSTRUCTION_NODE |
-- |
|
8 | Node.COMMENT_NODE |
Comment |
|
9 | Node.DOCUMENT_NODE |
Document |
|
10 | Node.DOCUMENT_TYPE_NODE |
-- |
|
11 | Node.DOCUMENT_FRAGMENT_NODE |
DocumentFragment |
|
12 | Node.NOTATION_NODE |
-- |
|
El siguiente ejemplo muestra una función que recorre recursivamente todos los nodos del documento contando cuántos de ellos son elementos:
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Conteo de elementos</title> <script type="text/javascript"> <!-- function countElements(node) { var count = 0; if ( node.nodeType == Node.ELEMENT_NODE ) ++count; for ( var i = 0; i < node.childNodes.length; ++i ) count += countElements(node.childNodes[i]); return count; } function fillCount() { document.getElementById('count').innerHTML = countElements(document); } --> </script> </head> <body onload="fillCount()"> <h1>Conteo de elementos</h1> <p>El <em>número de elementos</em> presentes en este documento es: <strong id="count"></strong></p> </body> </html>
Ejercicio 9. Escriba una función que recorre el documento en que fue invocada y retorna un string con el texto de todo el documento. Imprima este string en un elemento pre
al final del documento. Puede usar este enunciado de laboratorio para probar.
Por cada tipo de nodo hay una pseudoclase hija de Node
, que aparece en la tercera columna de la tabla anterior. Por ejemplo, cuando el navegador carga el documento y encuentra un comentario, construye un objeto de la pseudoclase Comment
, la cual es hija de Node
, y cuando encuentra un párrafo creará un objeto Element
que es también hijo de Node
. Esta jerarquía de pseudoclases se ilustra a continuación.
Cada subclase agrega propiedades a la pseudoclase Node
. Por ejemplo, la pseudoclase Element
define las siguientes propiedades útiles:
Propiedad | Descripción |
---|---|
Element.tagName |
String con el nombre del elemento, por ejemplo: "body" , "p" , etc. |
Element.attributes[] |
Accede a los atributos del elemento en el orden en que fueron definidos. |
Element.getAttribute(name) |
Retorna el valor del atributo name como un string, o null si el elemento no tiene este atributo. |
Element.setAttribute(name, value) |
Asigna el string value al atributo con nombre name del elemento. Si el atributo no existe, lo crea. |
Element.removeAttribute(name) |
Elimina el atributo con nombre name del elemento. No produce ningún error si el atributo no existe. |
Element.getAttributeNode(name) |
Retorna un objeto de tipo Attr que permite por ejemplo saber si el atributo fue definido o se está usando su valor por defecto. |
Element.id |
El valor del atributo id="value" , si fue definido para este elemento. |
Element.className |
El valor del atributo class="value" , si fue definido para este elemento. |
Element.children[] |
Un arreglo que contiene únicamente los elementos hijos de este elemento, en lugar de todos los nodos como ocurre con el arreglo childNodes heredado de Node . Quizá childElements hubiese sido un nombre más apropiado. |
Element.childElementCount |
La cantidad de elementos hijos. Equivale a children.length . |
Element.innerHTML |
Un string que con el marcado HTML o XML que representa el contenido del elemento. Si se asigna otro string, será "parseado" por el navegador, el resultado reemplazará a los nodos hijos del elemento, y el efecto será inmediatamente visible en el documento. |
Element.outerHTML |
Un string que con el marcado HTML o XML que representa el elemento mismo más su contenido. Si se asigna otro string, será "parseado" por el navegador, el resultado reemplazará al elemento y sus nodos hijos. El efecto será inmediatamente visible en el documento. |
Element.style |
Un objeto CSSStyleDeclaration con los estilos definidos en el documento para este elemento. Se puede modificar dinámicamente. No contiene los estilos computados. |
El objeto Attr
representa un atributo de un elemento particular, aunque casi nunca se utiliza, ya que el objeto Element
tiene propiedades y métodos para manipular sus atributos. Quizá su propiedad más útil es Attr.specified
que permite determinar si el valor del atributo fue explícitamente escrito en el documento, sino se está tomando el valor por omisión.
Los navegadores realmente implementan dos modelos de objetos del documento (DOM), uno para XML y otro especializado para HTML. Dado que JavaScript utiliza XML para transferir datos entre el servidor y el cliente, requiere poder manipular estos documentos a través de una jerarquía de objetos. El DOM que se ha presentado hasta el momento se llama XML DOM, compuesto de objetos bastante genéricos como lo es también XML.
Sin embargo, el navegador provee un conjunto de pseudoclases especializadas para HTML, que componen lo que se llama HTML DOM y que heredan de las pseudoclases presentes en el XML DOM. Algunas de estas pseudoclases se presentan en verde en la figura {ver arriba}. El principal distintivo del HTML DOM es que define la pseudoclase HTMLElement
para todos los elementos que sólo tienen los atributos comunes (em
, span
, dt
, etc.) y una pseudoclase HTMLXxxElement
para cada elemento Xxx que tiene atributos propios, como HTMLDocument
, body
(HTMLBodyElement
), p
(HTMLParagraphElement
), ul
(HTMLUListElement
), etc.
Las pseudoclases HTMLXxxElement
definen propiedades homónimas a los atributos (X)HTML que son de lectura y escritura. Por ejemplo HTMLImageElement
define el string src
que puede cambiarse dinámicamente con JavaScript. Los nombres son homónimos a XHTML, en minúsculas, excepto el atributo class=""
que en JavaScript se renombra className
debido a que class
es una palabra reservada. El tipo de datos de cada atributo es el adecuado, por ejemplo, HTMLImageElement.src
es un string, HTMLImageElement.width
es un entero y HTMLBodyElement.onload
es una función de JavaScript.
El autor puede utilizar estos atributos definidos por las pseudoclases HTMLXxxElement
directamente con el operador punto o el operador corchetes, si está seguro de que el objeto es del tipo xxx adecuado. También puede emplear los métodos getAttribute(name)
y setAttributeValue(name, value)
heredados de Element
en el XML DOM; que a diferencia de los atributos de HTMLXxxElement
, sólo retornan y reciben valores string.
Ejercicio 10. Reimplemente el ejercicio 7 del laboratorio 7 usando la jerarquía de Node
, en lugar de usar una jerarquía de clases propias. Provea una función que al ser invocada obtiene una referencia al elemento body
y construye un documento aleatorio.
De todos los tipos de nodos el elemento es incuestionablemente el más importante para el autor, tanto que el objeto Document
del XML DOM y HTMLDocument
del HTML DOM, proveen varios métodos útiles para localizar elementos por algún criterio. Estos se resumen en la siguiente tabla.
Método | Descripción |
---|---|
document.getElementById(id) |
Localiza el único elemento que tiene el identificador id en su atributo homónimo. Retorna el objeto HTMLElement que tiene este identificador o null si ningún elemento en el documento está identificado con el string id . |
document.getElementsByTagName(tagName) |
Selecciona todos los elementos en el documento que fueron creados con la etiqueta tagName , y los retorna en un arreglo que tiene el mismo orden en que aparecen en el documento. Por ejemplo, document.getElementsByTagName('table') retorna un arreglo con todas las tablas del documento. La búsqueda se hace sin importar mayúsculas y minúsculas por compatibilidad con HTML, y si se envía un asterisco '*' en tagName retorna todos los elementos del documento. |
document.getElementsByClassName(classValue) |
El atributo class="c1 c2 ... cN" indica que el elemento pertenece a una o más clases separadas por espacios (y no por comas). Una clase es una agrupación de elementos que comparten características comunes; por ejemplo, una clase ejercicio podría usarse para distinguir todos los párrafos que representen ejercicios en un libro (<p class="ejercicio">...</p> ) y aplicarles estilos diferenciados. En JavaScript se puede tener un arreglo con todos los elementos del documento que pertenecen a una o a varias clases con el método document.getElementsByClassName() , que recibe un string con la lista de clases separadas por espacios sin importar el orden. |
document.querySelectorAll(selector) |
CSS tiene una notación estándar para seleccionar elementos a los cuales aplicarles reglas de estilo; por ejemplo, el selector ".ejemplo" recupera todos los elementos que tienen el atributo class="ejemplo" y "#cap01 a[rel='external']" selecciona a todos los enlaces que tienen el atributo <a rel="external"> y que son descendientes del elemento identificado con id="cap01" . En JavaScript el método document.querySelectorAll(selector) retorna un arreglo con todos los elementos que satisfacen el selector enviado por parámetro. |
document.querySelector(selector) |
Hace lo mismo que querySelectorAll() , pero sólo regresa el primer elemento que satisface el selector en lugar de una lista de elementos. |
document.getElementByName(name) |
El atributo name sirve para identificar varios objetos relacionados, especialmente una familia de botones de radio (radio buttons). Ya que varios elementos pueden tener el mismo nombre, este método retorna un arreglo de elementos que comparten dicho nombre. |
La pseudoclase Element
también define los métodos Element.getElementsByTagName(tagName)
y Element.getElementsByClassName(classValue)
, que hacen lo mismo que sus contrapartes de Document
, con la diferencia de que en lugar de buscar en todo el documento, buscan únicamente en el subárbol del elemento al cual el método es invocado.
Por conveniencia la pseudoclase HTMLDocument
define las propiedades document.head
, document.body
y document.documentElement
, que hacen referencia a sus respectivos elementos, como es de esperar:
document.head == document.getElementsByTagName('head')[0]; document.body == document.getElementsByTagName('body')[0]; document.documentElement == document.getElementsByTagName('html')[0];
El manejo de eventos estático consiste en que el autor asocie una función o código JavaScript a un atributo mientras escribe el documento. Por ejemplo:
<body onload="fixExternalLinks(); insertIndex();">
Esto es una mala práctica, ya que el documento (X)HTML sólo debe especificar contenido y no estilo o comportamiento. Así que la asignación debería hacerse con JavaScript, lo cual es muy simple, por ejemplo:
document.body.onload = function() { fixExternalLinks(); insertIndex(); }
Aunque la asignación anterior se hizo con JavaScript y no en el documento (X)HTML, el esquema anterior tiene varios problemas. Supóngase que la asignación anterior se hizo por un archivo1.js
que es parte de una biblioteca 1. Un programador que estuviese creando una biblioteca 2 en JavaScript que es independiente de la anterior, probablemente necesitará invocar una función suya cuando el documento (X)HTML se ha cargado. En nuestro ejemplo esto implicaría tener que agregar una tercera función al evento onload
de body
¿Cómo hacer para agregar esta tercera función y mantener las otras funciones que pueda ya tener asociadas?. Es deseable disponer de algún mecamismo que diga simplemente "agregue este manejador al evento onload
" sin importar cuántos eventos tiene ya asociados. De esto se encarga el método addEventListener()
.
El método element.addEventListener(event, function, phase)
hace que la función function
sea invocada cuando el elemento element
produce el evento event
. El nombre del evento en el primer parámetro es un string sin el prefijo 'on'
, por ejemplo "mousedown"
. El segundo parámetro es una función que debe recibir sólo un parámetro: un objeto de tipo Event
y debe retornar nada. El tercer parámetro es un booleano, con true
indica que el evento puede ser atendido por uno de sus ancestros, mientras que con false
se le da primero oportunidad al elemento antes de que a sus ancestros (el por defecto). Esto se explicará más adelante. El siguiente ejemplo muestra un archivo .js
que al ser incluido por un documento (X)HTML, invoca dos funciones cuando éste termina de cargarse.
<!-- onload_event_dom2.html --> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Eventos del DOM 2</title> <script src="onload_event_dom2.js"></script> </head> <body> <h1>Manejo de eventos del DOM level 2</h1> </body> </html>
// onload_event_dom2.js function showInfo() { window.alert('Esta función JavaScript fue llamada sin que el documento hiciere' + ' nada especial, mas que incluir este archivo .js'); } function addInfo() { var text = 'Este párrafo es producto de un segundo manejador de eventos.'; var paragraph = document.createElement('p'); paragraph.appendChild( document.createTextNode(text) ); document.body.appendChild( paragraph ); } window.addEventListener('load', showInfo, false); window.addEventListener('load', addInfo, false);
.js
que al ser incluido por un documento, agrega dos manejadores de eventos. Correr este ejemplo.El método Element.addEventListener()
es parte de lo que se conoce como DOM Level 2, el establece que cuando un evento ocurre en un elemento, éste puede antenderlo o alguno de sus elementos padres, es decir, el evento se propaga por la jerarquía de elementos y puede ser atendido llama en una de las siguientes tres fases:
El tercer parámetro del método element.addEventListener()
controla la fase en la que será atendido del evento. Si es true
se atenderá en la fase de captura, si es false
en la fase objetivo o fase de ascenso (bubbling).
Otra ventaja de usar addEventListener()
en lugar de asignar manejadores de eventos directamente a los atributos intrínsecos (DOM Level 0), es que una misma función puede menejar eventos generados en distintos lugares del documento. Por ejemplo, si quiere hacer algo cuando el cursor del ratón pase sobre cualquier párrafo del documento, en el DOM Level 0 tendría que recorrer todos los elementos p
y asignarles la función manejadora; mientras que en el DOM Level 2, simplemente registra la función en el elemento document.body
con addEventListener()
.
Tanto en DOM Level 0 como DOM Level 1, la función que se asigna al atributo intrínseco o que se pasa en el segundo parámetro de element.addEventListener()
se convierte en una propiedad del elemento al cual se hizo la asignación o invocación. Es decir, esa función se convierte en un método del elemento. Cuando éste se invoca por el navegador, se hará como una invocación a un método normal del elemento, así la palabra reservada this
hará referencia a dicho elemento, por lo que se puede usar dentro del código del manejador. El siguiente ejemplo muestra una mínima implementación de un juego de estallar una bolsa de burbujas. Nótese cómo la función popBubble
es asignada a cada imagen de burbuja al evento onclick
, lo que la convierte en un método, el cual utiliza el parámetro this
para cambiar el origen de la imagen presionada por una burbuja estallada.
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Estallar burbujas</title> <style type="text/css"> body { margin: 2cm; } #wrap { width: 600px; height: 216px; margin: 0 auto; border: 5px solid #64859e; background: #a4c5de; position: relative; } #wrap img { position: absolute; border: 0; } </style> <script type="text/javascript" src="bubble_wrap_min.js"></script> </head> <body> <div id="wrap"></div> </body> </html>
// bubble_wrap_min.js // Fills the game area with bubble images function loadGame() { // Get the game area var wrap = document.getElementById('wrap'); // Assume the bubble image dimensions and area dimensions var imgWidth = 75; var imgHeight = 72; var rows = 3; var cols = 8; // Fill the game area creating img elements with onclick attribute set for ( var i = 0; i < rows; ++i ) { for ( var j = 0; j < cols; ++j ) { // Create the image as a new node var img = document.createElement('img'); img.src = 'air.jpg'; // Position the image, CSS must specify 'position:absolute' for img img.style.top = (i * imgHeight) + 'px'; img.style.left = (j * imgWidth) + 'px'; // When user clicks this image, replace it for a popped image img.onclick = popBubble; // Insert the new image as child of the game area wrap.appendChild(img); } } } // Called when a image is clicked function popBubble() { // Replace the current image for a popped one this.src = 'pop.jpg'; // This image cannot be popped again this.onclick = null; // Play an auditive feedback var snd = new Audio('pop.mp3'); snd.play(); } // Initializes the game when the document is loaded window.addEventListener('load', loadGame, false);
La función que se envía en el segundo parámetro de element.addEventListener()
recibe un objeto Event
por parámetro. En el ejemplo del juego de la bolsa de burbujas, el método popBubble()
ignora este parámetro. El objeto Event
tiene varias propiedades útiles, entre las que sobresalen currentTarget
que es una referencia al objeto que generó el evento (equivalente a this
); el método stopPropagation()
que al ser invocado evita que otros nodos sigan procesando el evento; y preventDefault()
que cancela el efecto que tendría normalmente el evento en el navegador, como por ejemplo seguir un enlace si el elemento fuera un hipervínculo. En algunos eventos el navegador envía un objeto MouseEvent
que contiene información sobre el puntero del ratón, como su posición (clientX
, clientY
), el botón usado (button
), si hubo teclas modificadoras presionadas en el teclado y otros.
Ejercicio 11. Escriba un programa JavaScript en un archivo .js
que al ser incluido en un documento, busca todas las tablas de la clase class="sortable"
y convierte sus encabezados en elementos que reaccionan al clic del ratón. Cuando se presiona un encabezado de columna, se ordenan las filas alfabéticamente por los valores de esa columna.
Ejercicio 12. Modifique su solución del ejercicio anterior para que el ordenamiento ascendente y descendente se alternativo. Es decir, cuando se presiona el encabezado de una columna se ordena ascendentemente, pero si se vuelve a presionar el encabezado de dicha columna, se ordenan los datos de la tabla en forma descendente. Una tercera vez en orden ascendente, y así sucesivamente.
Ejercicio 13. Escriba un programa JavaScript en un archivo .js
que al ser incluido en un documento, busca un elemento identificado con id="word_index"
y crea en él un índice de materias, obtenido de las definiciones (elemento dfn
) dispersas por el documento. Cada entrada en su índice de materias debe estar compuesto de enlaces, de tal forma que al hacer click en una de ellas, lleva a la definición en el documento. Puede tomar el libro del curso para sus pruebas.
Comprima los archivos XHTML, CSS y JavaScript que haya creado durante el laboratorio. Súbalos a la plataforma educativa (Moodle) de la ECCI, en la asignación con título Tarea10
.