materials

Document Object Model (DOM)

Table of Contents generated with DocToc

Introducción

La mayoría de las veces que programamos con Javascript es para que se ejecute en una página web mostrada por el navegador. En este contexto tenemos acceso a ciertos objetos que nos permiten interactuar con la página (DOM) y con el navegador (Browser Object Model, BOM).

El DOM es una estructura en árbol que representa todos los elementos HTML de la página y sus atributos. Todo lo que contiene la página se representa como nodos del árbol y mediante el DOM podemos acceder a cada nodo, modificarlo, eliminarlo o añadir nuevos nodos de forma que cambiamos dinámicamente la página mostrada al usuario.

La raíz del árbol DOM es document y de este nodo cuelgan el resto de elementos HTML. Cada uno constituye su propio nodo y tiene subnodos con sus atributos, estilos y elementos HTML que contiene.

Por ejemplo, la página HTML:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Página simple</title>
</head>
<body>
  <p>Esta página es <strong>muy simple</strong></p>
</body>
</html>

se convierte en el siguiente árbol DOM:

Árbol DOM

Cada etiqueta HTML suele originar 2 nodos:

Cada nodo es un objeto con sus propiedades y métodos.

El ejemplo anterior está simplificado porque sólo aparecen los nodos de tipo elemento pero en realidad también generan nodos los saltos de línea, tabuladores, espacios, comentarios, etc. En el siguiente ejemplo podemos ver TODOS los nodos que realmente se generan. La página:

<!DOCTYPE html>
<html>
<head>
  <title>My Document</title>
</head>
<body>
  <h1>Header</h1>
  <p>
    Paragraph
  </p>
</body>
</html>

se convierte en el siguiente árbol DOM:

Dom tree

Acceso a los nodos

Los principales métodos para acceder a los diferentes nodos son:

También tenemos ‘atajos’ para obtener algunos elementos comunes:

EJERCICIO: Para hacer los ejercicios de este tema descárgate esta página de ejemplo y ábrela en tu navegador. Obtén por consola, al menos de 2 formas diferentes:

Acceso a nodos a partir de otros

En muchas ocasiones queremos acceder a cierto nodo a partir de uno dado. Para ello tenemos los siguientes métodos que se aplican sobre un elemento del árbol DOM:

IMPORTANTE: a menos que me interesen comentarios, saltos de página, etc siempre debo usar los métodos que sólo devuelven elementos HTML, no todos los nodos.

Recorrer el árbol DOM

EJERCICIO: Siguiento con la página de ejemplo obtén desde la consola, al menos de 2 formas diferentes:

Propiedades de un nodo

Las principales propiedades de un nodo son:

Otras propiedades:

EJERCICIO: Obtén desde la consola, al menos de 2 formas:

Manipular el árbol DOM

Vamos a ver qué métodos nos permiten cambiar el árbol DOM, y por tanto modificar la página:

OJO: Si añado con el método appendChild un nodo que estaba en otro sitio se elimina de donde estaba para añadirse a su nueva posición. Si quiero que esté en los 2 sitios deberé clonar el nodo y luego añadir el clon y no el nodo original.

Ejemplo de creación de nuevos nodos: tenemos un código HTML con un DIV que contiene 3 párrafos y vamos a añadir un nuevo párrafo al final del div con el texto ‘Párrafo añadido al final’ y otro que sea el 2º del div con el texto ‘Este es el nuevo segundo párrafo’:

Si utilizamos la propiedad innerHTML el código a usar es mucho más simple:

OJO: La forma de añadir el último párrafo (línea #3: miDiv.innerHTML+='<p>Párrafo añadido al final</p>';) aunque es válida no es muy eficiente ya que obliga al navegador a volver a pintar TODO el contenido de miDIV. La forma correcta de hacerlo sería:

let ultimoParrafo = document.createElement('p');
ultimoParrafo.innerHTML = 'Párrafo añadido al final';
miDiv.appendChild(ultimoParrafo);

Así sólo debe repintar el párrafo añadido, conservando todo lo demás que tenga miDiv.

Podemos ver más ejemplos de creación y eliminación de nodos en W3Schools.

EJERCICIO: Añade a la página:

Modificar el DOM con ChildNode

Childnode es una interfaz que permite manipular del DOM de forma más sencilla pero no está soportada en los navegadores Safari de IOS. Incluye los métodos:

Atributos de los nodos

Podemos ver y modificar los valores de los atributos de cada elemento HTML y también añadir o eliminar atributos:

A algunos atributos comunes como id, title o className (para el atributo class) se puede acceder y cambiar como si fueran una propiedad del elemento (elemento.atributo). Ejemplos:

let miPrimeraLista = document.getElementsByTagName('ul')[0];  // selecciona el 1º UL de la página
miPrimeraLista.id = 'primera-lista';
// es equivalente ha hacer:
miPrimeraLista.setAttribute('id', 'primera-lista');

Estilos de los nodos

Los estilos están accesibles como el atributo style. Cualquier estilo es una propiedad de dicho atributo pero con la sintaxis camelCase en vez de kebab-case. Por ejemplo para cambiar el color de fondo (propiedad background-color) y ponerle el color rojo al elemento miPrimeraLista haremos:

miPrimeraLista.style.backgroundColor = 'red';

De todas formas normalmente NO CAMBIAREMOS ESTILOS a los elementos sino que les pondremos o quitaremos clases que harán que se le apliquen o no los estilos definidos para ellas en el CSS.

Atributos de clase

Ya sabemos que el aspecto de la página debe configurarse en el CSS por lo que no debemos aplicar atributos style al HTML. En lugar de ello les ponemos clases a los elementos que harán que se les aplique el estilo definido para dicha clase.

Como es algo muy común en lugar de utilizar las instrucciones de elemento.setAttribute('className', 'destacado') o directamente elemento.className='destacado' podemos usar la propiedad classList que devuelve la colección de todas las clases que tiene el elemento. Por ejemplo si elemento es <p class="destacado direccion">...:

let clases=elemento.classList;   // clases=['destacado', 'direccion'], OJO es una colección, no un Array

Además dispone de los métodos:

Tened en cuenta que NO todos los navegadores soportan classList por lo que si queremos añadir o quitar clases en navegadores que no lo soportan debemos hacerlo con los métodos estándar, por ejemplo para añadir la clase ‘rojo’:

let clases = elemento.className.split(" ");
if (clases.indexOf('rojo') == -1) {
  elemento.className += ' ' + 'rojo';
}