DOM
Cuando comenzamos en el mundo del desarrollo web, normalmente comenzamos por aprender a escribir etiquetado o marcado HTML y además, añadir estilos CSS para darle color, forma y algo de interacción. Sin embargo, a medida que avanzamos, nos damos cuenta que en cierta forma podemos estar bastante limitados.
Si únicamente utilizamos HTML/CSS, sólo podremos crear páginasestáticas, pero si añadimos Javascript, podremos crear páginasdinámicas. Cuando hablamos de páginas dinámicas, nos referimos a que podemos dotar de la potencia y flexibilidad que nos da un lenguaje de programación para crear documentos y páginas mucho más ricas, que brinden una experiencia más completa y con el que se puedan automatizar un gran abanico de tareas y acciones.
¿Qué es el DOM?
Las siglas DOM significan Document Object Model, o lo que es lo mismo, la estructura del documento HTML. Una página HTML está formada por múltiples etiquetas HTML, anidadas una dentro de otra, formando un árbol de etiquetas relacionadas entre sí, que se denomina árbol DOM.
En Javascript, cuando nos referimos al DOM nos referimos a esta estructura, que podemos modificar de forma dinámica desde Javascript, añadiendo nuevas etiquetas, modificando o eliminando otras, cambiando sus atributos HTML, añadiendo clases, cambiando el contenido de texto, etc…
Al estar “amparado” por un lenguaje de programación, todas estas tareas se pueden automatizar, incluso indicando que se realicen cuando el usuario haga acciones determinadas, como por ejemplo: pulsar un botón, mover el ratón, hacer click en una parte del documento, escribir un texto, etc…
El objeto document
En Javascript, la forma de acceder al DOM es a través de un objeto llamado document, que representa el árbol DOM de la página o pestaña del navegador donde nos encontramos. En su interior pueden existir varios tipos de elementos, pero principalmente serán Element o Node:
∙ Element no es más que la representación genérica de una etiqueta: HTMLElement. ∙ Node es una unidad más básica, la cuál puede ser o un nodo de texto.
La especificación completa de DOM define 12 tipos de nodos, aunque las páginas HTML habituales se pueden manipular manejando solamente cuatro o cinco tipos de nodos:
∙ Document, nodo raíz del que derivan todos los demás nodos del árbol. ∙ Element, representa cada una de las etiquetas HTML. Se trata del único nodo que puede contener atributos y el único del que pueden derivar otros nodos. ∙ Attr, se define un nodo de este tipo para representar cada uno de los atributos de las etiquetas HTML, es decir, uno por cada par atributo=valor.
∙ Text, nodo que contiene el texto encerrado por una etiqueta HTML. ∙ Comment, representa los comentarios incluidos en la página HTML.
getElementsByTagName()
Como sucede con todas las funciones que proporciona DOM, la
función getElementsByTagName() tiene un nombre muy largo, pero que lo hace autoexplicativo.
La función getElementsByTagName(nombreEtiqueta) obtiene todos los elementos de la página HTML cuya etiqueta sea igual que el parámetro que se le pasa a la función.
El siguiente ejemplo muestra cómo obtener todos los párrafos de una página XHTML: var parrafos = document.getElementsByTagName(“p”);
El valor que se indica delante del nombre de la función (en este caso, document) es el nodo a partir del cual se realiza la búsqueda de los elementos. En este caso, como se quieren obtener todos los párrafos de la página, se utiliza el valor document como punto de partida de la búsqueda.
El valor que devuelve la función es un array con todos los nodos que cumplen la condición de que su etiqueta coincide con el parámetro proporcionado. El valor devuelto es un array de nodos DOM, no un array de cadenas de texto o un array de objetos normales. Por lo tanto, se debe procesar cada valor del array de la forma que se muestra en las siguientes secciones.
De este modo, se puede obtener el primer párrafo de la página de la siguiente manera: var primerParrafo = parrafos[0];
De la misma forma, se podrían recorrer todos los párrafos de la página con el siguiente código:
for(var i=0; I < parrafos.length; i++) {
var parrafo = parrafos[i];
}
La función getElementsByTagName() se puede aplicar de forma recursiva sobre cada uno de los nodos devueltos por la función. En el siguiente ejemplo, se obtienen todos los enlaces del primer párrafo de la página:
var parrafos = document.getElementsByTagName(“p”);
var primerParrafo = parrafos[0];
var enlaces = primerParrafo.getElementsByTagName(“a”);
getElementsByName()
La función getElementsByName() es similar a la anterior, pero en este caso se buscan los elementos cuyo atributo name sea igual al parámetro proporcionado. En el siguiente ejemplo, se obtiene directamente el único párrafo con el nombre indicado:
var parrafoEspecial = document.getElementsByName(“especial”);
<p name=”prueba”>…</p>
<p name=”especial”>…</p>
<p>…</p>
Normalmente el atributo name es único para los elementos HTML que lo definen, por lo que es un método muy práctico para acceder directamente al nodo deseado. En el caso de los elementos HTML radiobutton, el atributo name es común a todos los radiobutton que están relacionados, por lo que la función devuelve una colección de elementos.
getElementById()
La función getElementById() es la más utilizada cuando se desarrollan aplicaciones web dinámicas. Se trata de la función preferida para acceder directamente a un nodo y poder leer o modificar sus propiedades.
La función getElementById() devuelve el elemento HTML cuyo atributo id coincide con el parámetro indicado en la función. Como el atributo id debe ser único para cada elemento de una misma página, la función devuelve únicamente el nodo deseado.
var cabecera = document.getElementById(“cabecera”);
<div id=”cabecera”>
<a href=”/” id=”logo”>…</a>
</div>
Creación de elementos HTML simples
Como se ha visto, un elemento HTML sencillo, como por ejemplo un párrafo, genera dos nodos: el primer nodo es de tipo Element y representa la etiqueta <p> y el segundo nodo es de tipo Text y representa el contenido textual de la etiqueta <p>.
Por este motivo, crear y añadir a la página un nuevo elemento HTML sencillo consta de cuatro pasos diferentes:
1. Creación de un nodo de tipo Element que represente al elemento. 2. Creación de un nodo de tipo Text que represente el contenido del elemento. 3. Añadir el nodo Text como nodo hijo del nodo Element.
4. Añadir el nodo Element a la página, en forma de nodo hijo del nodo correspondiente al lugar en el que se quiere insertar el elemento.
De este modo, si se quiere añadir un párrafo simple al final de una página HTML, es necesario incluir el siguiente código JavaScript:
// Crear nodo de tipo Element
var parrafo = document.createElement(“p”);
// Crear nodo de tipo Text
var contenido = document.createTextNode(“Hola Mundo!”);
// Añadir el nodo Text como hijo del nodo Element
parrafo.appendChild(contenido);
// Añadir el nodo Element como hijo de la pagina
document.body.appendChild(parrafo);
El proceso de creación de nuevos nodos puede llegar a ser tedioso, ya que implica la utilización de tres funciones DOM:
∙ createElement(etiqueta): crea un nodo de tipo Element que representa al elemento HTML cuya etiqueta se pasa como parámetro.
∙ createTextNode(contenido): crea un nodo de tipo Text que almacena el contenido textual de los elementos HTML.
∙ nodoPadre.appendChild(nodoHijo): añade un nodo como hijo de otro nodo. Se debe utilizar al menos dos veces con los nodos habituales: en primer lugar se añade el nodo Text como hijo del nodo Element y a continuación se añade el nodo Element como hijo de algún nodo de la página.
Eliminación de nodos
Afortunadamente, eliminar un nodo del árbol DOM de la página es mucho más sencillo que añadirlo. En este caso, solamente es necesario utilizar la función removeChild():
var parrafo = document.getElementById(“provisional”);
parrafo.parentNode.removeChild(parrafo);
<p id=”provisional”>…</p>
La función removeChild() requiere como parámetro el nodo que se va a eliminar. Además, esta función debe ser invocada desde el elemento padre de ese nodo que se quiere eliminar. La forma más segura y rápida de acceder al nodo padre de un elemento es mediante la propiedad nodoHijo.parentNode.
Así, para eliminar un nodo de una página HTML se invoca a la función removeChild() desde el valor parentNode del nodo que se quiere eliminar. Cuando se elimina un nodo, también se eliminan automáticamente todos los nodos hijos que tenga, por lo que no es necesario borrar manualmente cada nodo hijo.
Modificación del DOM
Una vez que se ha accedido a un nodo, el siguiente paso natural consiste en acceder y/o modificar sus atributos y propiedades. Mediante DOM, es posible acceder de forma sencilla a todos los atributos HTML y todas las propiedades CSS de cualquier elemento de la página.
Los atributos HTML de los elementos de la página se transforman automáticamente en propiedades de los nodos. Para acceder a su valor, simplemente se indica el nombre del atributo HTML detrás del nombre del nodo.
El siguiente ejemplo obtiene de forma directa la dirección a la que enlaza el enlace: var enlace = document.getElementById(“enlace”);
alert(enlace.href); // muestra http://www…com
<a id=”enlace” href=”http://www…com”>Enlace</a>
En el ejemplo anterior, se obtiene el nodo DOM que representa el enlace mediante la función document.getElementById(). A continuación, se obtiene el atributo href del enlace mediante enlace.href. Para obtener por ejemplo el atributo id, se utilizaría enlace.id.
Las propiedades CSS no son tan fáciles de obtener como los atributos HTML. Para obtener el valor de cualquier propiedad CSS del nodo, se debe utilizar el atributo style. El siguiente ejemplo obtiene el valor de la propiedad margin de la imagen:
var imagen = document.getElementById(“imagen”);
alert(imagen.style.margin);
<img id=”imagen” style=”margin:0; border:0;” src=”logo.png” />
Aunque el funcionamiento es homogéneo entre distintos navegadores, los resultados no son exactamente iguales, como muestran las siguientes imágenes que son el resultado de ejecutar el código anterior en distintos navegadores:
Valores que muestra Internet Explorer al acceder a las
propiedades CSS a través de JavaScript
Valores que muestra Firefox al
acceder a las propiedades CSS a
través de JavaScript
Si el nombre de una propiedad CSS es compuesto, se accede a su valor modificando ligeramente su nombre:
var parrafo = document.getElementById(“parrafo”);
alert(parrafo.style.fontWeight); // muestra “bold”
<p id=”parrafo” style=”font-weight: bold;”>…</p>
La transformación del nombre de las propiedades CSS compuestas consiste en eliminar todos los guiones medios (-) y escribir en mayúscula la letra siguiente a cada guión medio. A continuación se muestran algunos ejemplos:
∙ font-weight se transforma en fontWeight
∙ line-height se transforma en lineHeight
∙ border-top-style se transforma en borderTopStyle
∙ list-style-image se transforma en listStyleImage
Eventos
Hasta ahora, todas las aplicaciones y scripts que se han creado tienen algo en común: se ejecutan desde la primera instrucción hasta la última de forma secuencial. Gracias a las estructuras de control de flujo (if, for, while) es posible modificar ligeramente este comportamiento y repetir algunos trozos del script y saltarse otros trozos en función de algunas condiciones.
Este tipo de aplicaciones son poco útiles, ya que no interactúan con los usuarios y no pueden responder a los diferentes eventos que se producen durante la ejecución de una aplicación. Afortunadamente, las aplicaciones web creadas con el lenguaje JavaScript pueden utilizar el modelo de programación basada en eventos.
En este tipo de programación, los scripts se dedican a esperar a que el usuario “haga algo” (que pulse una tecla, que mueva el ratón, que cierre la ventana del navegador). A continuación, el script responde a la acción del usuario normalmente procesando esa información y generando un resultado.
Los eventos hacen posible que los usuarios transmitan información a los programas. JavaScript define numerosos eventos que permiten una interacción completa entre el usuario y las páginas/aplicaciones web. La pulsación de una tecla constituye un evento, así como pinchar o mover el ratón, seleccionar un elemento de un formulario, redimensionar la ventana del navegador, etc.
JavaScript permite asignar una función a cada uno de los eventos. De esta forma, cuando se produce cualquier evento, JavaScript ejecuta su función asociada. Este tipo de funciones se denominan “event handlers” en inglés y suelen traducirse por “manejadores de eventos”.
Cada elemento o etiqueta HTML define su propia lista de posibles eventos que se le pueden asignar. Un mismo tipo de evento (por ejemplo, pinchar el botón izquierdo del ratón) puede estar definido para varios elementos HTML diferentes y un mismo elemento HTML puede tener asociados varios eventos diferentes.
El nombre de cada evento se construye mediante el prefijo on, seguido del nombre en inglés de la acción asociada al evento. Así, el evento de pinchar un elemento con el ratón se denomina onclick y el evento asociado a la acción de mover el ratón se denomina onmousemove.
La siguiente tabla resume los eventos más importantes definidos por JavaScript:
Evento | Descripción | Elementos para los que está definido |
onblur | Deseleccionar el elemento | <button>, <input>, <label>, <select>, <textarea>, <body > |
onchange | Deseleccionar un elemento que se ha modificado | <input>, <select>, <textarea> |
onclick | Pinchar y soltar el ratón | Todos los elementos |
ondblclick | Pinchar dos veces seguidas con el ratón | Todos los elementos |
onfocus | Seleccionar un elemento | <button>, <input>, <label>, <select>, <textarea>, <body > |
onkeydown | Pulsar una tecla (sin soltar) | Elementos de formulario y <body> |
onkeypress | Pulsar una tecla | Elementos de formulario y <body> |
onkeyup | Soltar una tecla pulsada | Elementos de formulario y <body> |
onload | La página se ha cargado completamente | <body> |
onmousedow n | Pulsar (sin soltar) un botón del ratón | Todos los elementos |
onmousemov e | Mover el ratón | Todos los elementos |
onmouseout | El ratón “sale” del elemento (pasa por encima de | Todos los elementos |
Evento | Descripción | Elementos para los que está definido |
otro elemento) | ||
onmouseover | El ratón “entra” e n el elemento (pasa por encima del elemento) | Todos los elementos |
onmouseup | Soltar el botón que estaba pulsado en el ratón | Todos los elementos |
onreset | Inicializar el formulario (borrar todos sus datos) | <form> |
onresize | Se ha modificado el tamaño de la ventana del navegador | <body> |
onselect | Seleccionar un texto | <input>, <textarea> |
onsubmit | Enviar el formulario | <form> |
onunload | Se abandona la página (por ejemplo al cerrar el navegador) | <body> |
Los eventos más utilizados en las aplicaciones web tradicionales son onload para esperar a que se cargue la página por completo, los
eventos onclick, onmouseover, onmouseout para controlar el ratón y onsubmit para controlar el envío de los formularios.
Algunos eventos de la tabla anterior (onclick, onkeydown, onkeypress, onreset, onsubmit) permiten evitar la “acción por defecto” de ese evento. Más adelante se muestra en detalle este comportamiento, que puede resultar muy útil en algunas técnicas de programación.
Las acciones típicas que realiza un usuario en una página web pueden dar lugar a una sucesión de eventos. Al pulsar por ejemplo sobre un botón de tipo <input type=”submit”> se desencadenan los
eventos onmousedown, onclick, onmouseup y onsubmit de forma consecutiva.
Manejadores de eventos
Un evento de JavaScript por sí mismo carece de utilidad. Para que los eventos resulten útiles, se deben asociar funciones o código JavaScript a cada evento. De esta forma, cuando se produce un evento se ejecuta el código indicado, por lo que la aplicación puede responder ante cualquier evento que se produzca durante su ejecución.
Las funciones o código JavaScript que se definen para cada evento se denominan “manejador de eventos” y como JavaScript es un lenguaje muy flexible, existen varias formas diferentes de indicar los manejadores:
∙ Manejadores como atributos de los elementos XHTML.
∙ Manejadores como funciones JavaScript externas.
∙ Manejadores “semánticos”.
Manejadores de eventos como atributos HTML
Se trata del método más sencillo y a la vez menos profesional de indicar el código JavaScript que se debe ejecutar cuando se produzca un evento. En este caso, el código se incluye en un atributo del propio elemento HTML. En el siguiente ejemplo, se quiere mostrar un mensaje cuando el usuario pinche con el ratón sobre un botón:
<input type=”button” value=”Pinchame y verás” onclick=”alert(‘Gracias por pinchar’);” />
En este método, se definen atributos HTML con el mismo nombre que los eventos que se quieren manejar. El ejemplo anterior sólo quiere controlar el evento de pinchar con el ratón, cuyo nombre es onclick. Así, el elemento HTML para el que se quiere definir este evento, debe incluir un atributo llamado onclick.
El contenido del atributo es una cadena de texto que contiene todas las instrucciones JavaScript que se ejecutan cuando se produce el evento. En este caso, el código JavaScript es muy sencillo (alert(‘Gracias por pinchar’);), ya que solamente se trata de mostrar un mensaje.
En este otro ejemplo, cuando el usuario pincha sobre el elemento <div> se muestra un mensaje y cuando el usuario pasa el ratón por encima del elemento, se muestra otro mensaje:
<div onclick=”alert(‘Has pinchado con el ratón’);” onmouseover=”alert(‘Acabas de pasar el ratón por encima’);”>
Puedes pinchar sobre este elemento o simplemente pasar el ratón por encima </div>
Este otro ejemplo incluye una de las instrucciones más utilizadas en las aplicaciones JavaScript más antiguas:
<body onload=”alert(‘La página se ha cargado completamente’);”>
…
</body>
El mensaje anterior se muestra después de que la página se haya cargado completamente, es decir, después de que se haya descargado su código HTML, sus imágenes y cualquier otro objeto incluido en la página.
El evento onload es uno de los más utilizados ya que, como se vio en el capítulo de DOM, las funciones que permiten acceder y manipular los nodos del árbol DOM solamente están disponibles cuando la página se ha cargado completamente.
Manejadores de eventos y variable this
JavaScript define una variable especial llamada this que se crea automáticamente y que se emplea en algunas técnicas avanzadas de programación. En los eventos, se puede utilizar la variable this para referirse al elemento HTML que ha provocado el evento. Esta variable es muy útil para ejemplos como el siguiente:
Cuando el usuario pasa el ratón por encima del <div>, el color del borde se muestra de color negro. Cuando el ratón sale del <div>, se vuelve a mostrar el borde con el color gris claro original.
Elemento <div> original:
<div id=”contenidos” style=”width:150px; height:60px; border:thin solid silver”> Sección de contenidos…
</div>
Si no se utiliza la variable this, el código necesario para modificar el color de los bordes, sería el siguiente:
<div id=”contenidos” style=”width:150px; height:60px; border:thin solid silver” onmouseover=”document.getElementById(‘contenidos’).style.borderColor=’black’;” onmouseout=”document.getElementById(‘contenidos’).style.borderColor=’silver’;”>
Sección de contenidos…
</div>
El código anterior es demasiado largo y demasiado propenso a cometer errores. Dentro del código de un evento, JavaScript crea automáticamente la variable this, que hace referencia al elemento HTML que ha provocado el evento. Así, el ejemplo anterior se puede reescribir de la siguiente manera:
<div id=”contenidos” style=”width:150px; height:60px; border:thin solid silver” onmouseover=”this.style.borderColor=’black’;”
onmouseout=”this.style.borderColor=’silver’;”>
Sección de contenidos…
</div>
El código anterior es mucho más compacto, más fácil de leer y de escribir y sigue funcionando correctamente aunque se modifique el valor del atributo id del <div>.
Manejadores de eventos como funciones externas
La definición de los manejadores de eventos en los atributos HTML es el método más sencillo pero menos aconsejable de tratar con los eventos en JavaScript. El principal inconveniente es que se complica en exceso en cuanto se añaden algunas pocas instrucciones, por lo que solamente es recomendable para los casos más sencillos.
Si se realizan aplicaciones complejas, como por ejemplo la validación de un formulario, es aconsejable agrupar todo el código JavaScript en una función externa y llamar a esta función desde el elemento HTML.
Siguiendo con el ejemplo anterior que muestra un mensaje al pinchar sobre un botón: <input type=”button” value=”Pinchame y verás” onclick=”alert(‘Gracias por pinchar’);” />
Utilizando funciones externas se puede transformar en:
function muestraMensaje() {
alert(‘Gracias por pinchar’);
}
<input type=”button” value=”Pinchame y verás” onclick=”muestraMensaje()” />
Esta técnica consiste en extraer todas las instrucciones de JavaScript y agruparlas en una función externa. Una vez definida la función, en el atributo del elemento XHTML se incluye el nombre de la función, para indicar que es la función que se ejecuta cuando se produce el evento.
La llamada a la función se realiza de la forma habitual, indicando su nombre seguido de los paréntesis y de forma opcional, incluyendo todos los argumentos y parámetros que se necesiten.
El principal inconveniente de este método es que en las funciones externas no se puede seguir utilizando la variable this y por tanto, es necesario pasar esta variable como parámetro a la función:
function resalta(elemento) {
switch(elemento.style.borderColor) {
case ‘silver’:
case ‘silver silver silver silver’:
case ‘#c0c0c0’:
elemento.style.borderColor = ‘black’;
break;
case ‘black’:
case ‘black black black black’:
case ‘#000000’:
elemento.style.borderColor = ‘silver’;
break;
}
}
<div style=”width:150px; height:60px; border:thin solid silver”
onmouseover=”resalta(this)” onmouseout=”resalta(this)”>
Sección de contenidos…
</div>
En el ejemplo anterior, la función externa es llamada con el parámetro this, que dentro de la función se denomina elemento. La complejidad del ejemplo se produce sobre todo por la forma en la que los distintos navegadores almacenan el valor de la propiedad borderColor.
Mientras que Firefox almacena (en caso de que los cuatro bordes coincidan en color) el valor black, Internet Explorer lo almacena como black black black black y Opera almacena su representación hexadecimal #000000.