Manipulación del DOM y eventos con selectores JavaScript

1438

Este post es una pequeña referencia de las funciones principales que podemos usar para manipular el DOM desde JavaScript, sin usar frameworks ni librerías externas.

¿Qué es el DOM?

El DOM es la representación de la página web en la memoria del navegador, a la que podemos acceder a través de JavaScript. El DOM es un árbol donde cada nodo es un objeto con todas sus propiedades y métodos que nos permiten modificarlo. Estas son algunas funciones que nos permiten acceder y modificar los elementos del DOM:

Acceso a elementos del DOM

// Obtiene un elemento por id
document.getElementById('someid');
 
// Obtinee una lista con los elementos que tienen esa clase
document.getElementsByClassName('someclass');
 
// Obtiene una HTMLCollection con los todos los elementos 'li'
document.getElementsByTagName('LI');
 
// Devuelve el primer elemento del documento que cumpla la selección (la notación es como en CSS)
document.querySelector('.someclass');
 
// Devuelve una lista de elementos que cumplen con la selección (notación como en CSS)
document.querySelectorAll('div.note, div.alert');

Acceder a hijos/padres de un elemento

// Obtener los hijos de un elemento
var elem = document.getElementById('someid');
var hijos = elem.childNodes;
 
// Su nodo padre
var padre = elem.parentNode;

Crear nuevos elementos en el DOM

// Para crear elementos llamamos a createElement con el nombre del elemento
var nuevoH1 = document.createElement('h1');
var nuevoParrafo = document.createElement('p');
 
// Crear nodos de texto para un elemento
var textoH1 = document.createTextNode('Hola mundo!');
var textoParrafo = document.createTextNode('lorem ipsum...');
 
// Añadir el texto a los elementos
nuevoH1.appendChild(textoH1);
nuevoParrafo.appendChild(textoParrafo);
 
// también podemos asignar directamente el valor a la propiedad innerHTML
nuevoH1.innerHTML = textoH1
nuevoParrafo.innerHTML = textoParrafo
 
// los elementos estarían listos para añadirlos al DOM, ahora mismo solo existen en memoria, pero no serán visibles hasta que no los añadamos a un elemento del DOM

Añadir elementos al DOM

// seleccionamos un elemento
var cabecera = document.getElementById('cabecera');
 
// Añadir elementos hijos a un elemento
cabecera.appendChild(nuevoH1);
cabecera.appendChild(nuevoParrafo);
 
// También podemos añadir elementos ANTES del elemento seleccionado
 
// Tomamos el padre
var padre = cabecera.parentNode;
 
// Insertamos el h1 antes de la cabecera
padre.insertBefore(nuevoH1, cabecera);

También podemos añadir directamente un trozo de HTML antes o después de un elemento del DOM, supongamos que tenemos estos elementos en la página:

<div id='box1'>
  <p>aquí algo de texto</p>
</div>
<div id='box2'>
  <p>otro parrafo bla bla bla</p>
</div>

Podemos hacer:

var box2 = document.getElementById('box2');
box2.insertAdjacentHTML('beforebegin', '<div><p>un parrafo nuevo.</p></div>');
 
// beforebegin - El nuevo HTML es insertado justo antes del elemento, a la misma altura (hermano).
// afterbegin - El nuevo HTML se inserta dentro del elemento, antes del primer hijo.
// beforeend - El nuevo HTML se inserta dentro del elemento, después del último hijo.
// afterend - El nuevo HTML es insertado justo después del elemento, a la misma altura (hermano).

Añadir/eliminar/modificar Clases

// Tomamos un elemento
var cabecera = document.getElementById('cabecera');
 
// elimina una clase del elemento
cabecera.classList.remove('foo');
 
// Añade una clase si no existe
cabecera.classList.add('otra');
 
// añade o elimina varias clases a la vez
cabecera.classList.add('foo', 'bar');
cabecera.classList.remove('foo', 'bar');
 
// Si la clase existe la elimina, si no existe, la crea
cabecera.classList.toggle('visible');
 
// Devuelve true si el elemento contiene esa clase
cabecera.classList.contains('foo');

Por otro lado, el manejo de eventos permite interactuar con el usuario dando más dinamismo a la página.

Podemos asociar un manejador de eventos de nivel 1 a un elemento de diferentes formas:

Asociación de eventos a elementos como atributo HTML

Como podemos ver lo que hacemos es añadir un atributo XHTML al elemento con el mismo nombre del evento. El contenido del atributo son las instrucciones que se ejecutarán cuando se produzca el evento, es decir, cuando el usuario click sobre el botón. Este método no suele utilizarse debido a que mezcla la lógica de la programación JavaScript con el marcado XHTML con las consecuencias que eso conlleva.
Utilizando este método es posible acceder al elemento que ha desencadenado el evento a través de la variable this. <input 


type="button" value="Pulsa" onclick="alert('Hola mundo');" />
<input type="button" value="Pulsa" onclick="this.value='Pulsado';" />

Asociación de eventos a elementos como atributo HTML pasándole una función Javascript

De esta forma se consigue mejorar la anterior forma de asociar manejadores, dado que, si tuviéramos que ejecutar mucho código JavaScript en respuesta, podemos escribirlo todo en una función externa y llamarla.

El inconveniente de utilizar esta forma de asociar manejadores de eventos como funciones externas es que no podemos acceder al elemento que ha desencadenado el evento a través de la variable this (ejemplo 1). Aun así, podemos pasarle la variable this como argumento de la función (Ejemplo 2).


function holaMundo() {
alert('Hola Mundo.');

<input type="button" value="Pulsa" onclick="holaMundo()" />


function holaMundo(elemento) { 

alert('Hola Mundo.'); 

elemento.value='Pulsado'; 

<input type="button" value="Pulsa" onclick="holaMundo(this)" />

Asociación de eventos a través de los manejadores de eventos separando los contenidos de la programación JavaScript.

Por último, la mejor forma es asociar los manejadores de eventos separando los contenidos de la programación JavaScript. Para eso asignas un id o un class al elemento que quieras que desencadene un evento. Después lo seleccionas utilizando las funciones de la API de DOM que hemos visto y asignas una función manejadora al evento.

Podemos asignar una función manejadora anónima o el nombre de la función que queremos que se ejecute cuando se produzca el evento.


document.getElementById('boton').onclick = function () { alert('Hola Mundo.'); }

<input type="button" value="Pulsa" id="boton" />


El inconveniente que tenemos con este método es que siempre que tratemos con el DOM tenemos que esperar a que el árbol de nodos se construya. Para asegurarnos que siempre se ejecutará una vez se haya construido el árbol de nodos podemos encerrar todo el código que haga uso de DOM en una función que se ejecute cuando se cargue la página, utilizando el evento 


onload:window.onload = function{ //TODO EL CODIGO }

El flujo de eventos

Si vemos la estructura en árbol de los nodos nos damos cuenta que los elementos hijos están contenidos dentro de un padre. Si hacemos click por ejemplo en un enlace que está contenido dentro de un párrafo estaremos desencadenando el evento onclick en dos elementos del documento.

Ejemplo de evento

<html onclick="evento()"> 

<head>

<title>Event bubbling</title>

</head> 

<body onclick="evento()"> 

<p onclick="evento()"> 

<a href="#" onclick="evento()">Dispara el evento</a> </p> 

</body> 

</html>

El flujo de eventos establece el orden en que se ejecutan los eventos y como ya te puedes imaginar, suele ser diferente en cada navegador.
Event bubbling: En este modelo de flujo de eventos se produce primero el evento en el elemento más interno de la estructura de árbol y va subiendo jerárquicamente hasta llegar al nodo raíz.
En este ejemplo se ejecutaría primero el evento del enlace, seguido del evento del párrafo, seguido del evento del body y por último el evento del documento html.
Event capturing: En este modelo de flujo de eventos se prodce primero el evento del elemento más externo de la estructura de árbol y va bajando jerárquicamente hasta llegar al elemento más interno. En el mismo ejemplo de antes se ejecutaría primero el evento del documento html, seguido del evento del elemento body, seguido del evento del párrafo y por último el evento del enlace.
El flujo de eventos del DOM: El flujo de eventos definido en el estandar del DOM soporta los dos modelos anteriores, pero el event capturing se ejecuta en primer lugar, seguido del event bubbling. Además, incluyen el objeto window del BOM.

El modelo de eventos de DOM level 2

Ejemplo Modelo eventos DOM nivel 2

function holaMundo(){ alert('Hola mundo.'); }

var boton = document.getElementById('boton');
boton.addEventListener('click', holaMundo, false);
boton.removeEventListener('click', holaMundo, false); 


<input type="button" value="Pulsa" id="boton" />

La especificación de nivel 2 de DOM define dos métodos para asignar y quitar manejadores de eventos, addEventListener() y removeEventListener(). Estos dos métodos aceptan tres parámetros: 

el tipo de evento que se quiere capturar sin el prefijo on.

el nombre de la función encargada de manejar el evento.

un booleano para indicar el tipo de flujo de eventos al que se aplica. Si es true se aplica el event capturing, si es false se emplea el event bubbling.
Vamos a ver un ejemplo para asignar y quitar manejadores de eventos:

El objeto event

Ejemplo Objeto event y sus propiedades

function holaMundo(event){ 

var evento = window.event || event; 

alert('Hola Mundo.'); 

var boton = document.getElementById('boton'); 

boton.addEventListener('click', holaMundo, false);

Cuando un evento se produce, la funcion manejadora suele necesitar información sobre el evento que se ha producido. El objeto event es el objeto que representa el evento que se ha producido. Se crea automáticamente cuando se produce el evento y se destruye una vez se haya ejecutado su función manejadora. Para complicarnos las cosas aún más, algunas versiones de Internet Explorer no tienen acceso automático del objeto event, sino que se accede a él a través del objeto window.

Este objeto tiene un conjunto de propiedades con información sobre el evento producido, y como no, vuelta al dilema, ya que Internet Explorer implementa las mismas propiedades con nombres distintos. Así que solo vamos a ver las definidas por DOM, si se quiere desarrollar una solución cross-browser lo mejor es utilizar un framework como jQuery, ya que el mismo framework se encarga de lidiar con estos problema de incompatibilidades.
Os adjunto la tabla del libro de Javier Eguíluz con las propiedades especificadas por DOM:

Propiedad/MétodoDevuelveDescripción
altKeyBooleanDevuelve true si se ha pulsado la tecla ALT y false en otro caso
bubblesBooleanIndica si el evento pertenece al flujo de eventos debubbling
buttonNúmero enteroEl botón del ratón que ha sido pulsado. Posibles valores: 0 – Ningún botón pulsado 1 – Se ha pulsado el botón izquierdo
2 – Se ha pulsado el botón derecho 3 – Se pulsan a la vez el botón izquierdo y el derecho 4 – Se ha pulsado el botón central 5 – Se pulsan a la vez el botón izquierdo y el central 6 – Se pulsan a la vez el botón derecho y el central 7 – Se pulsan a la vez los 3 botones
cancelableBooleanIndica si el evento se puede cancelar
cancelBubbleBooleanIndica si se ha detenido el flujo de eventos de tipobubbling
charCodeNúmero enteroEl código unicode del carácter correspondiente a la tecla pulsada
clientXNúmero enteroCoordenada X de la posición del ratón respecto del área visible de la ventana
clientYNúmero enteroCoordenada Y de la posición del ratón respecto del área visible de la ventana
ctrlKeyBooleanDevuelve true si se ha pulsado la tecla CTRL y falseen otro caso
currentTargetElementEl elemento que es el objetivo del evento
detailNúmero enteroEl número de veces que se han pulsado los botones del ratón
eventPhaseNúmero enteroLa fase a la que pertenece el evento: 0 – Fase capturing 1 – En el elemento destino 2 – Fase bubbling
isCharBooleanIndica si la tecla pulsada corresponde a un carácter
keyCodeNúmero enteroIndica el código numérico de la tecla pulsada
metaKeyNúmero enteroDevuelve true si se ha pulsado la tecla META y falseen otro caso
pageXNúmero enteroCoordenada X de la posición del ratón respecto de la página
pageYNúmero enteroCoordenada Y de la posición del ratón respecto de la página
preventDefault()FunciónSe emplea para cancelar la acción predefinida del evento
relatedTargetElementEl elemento que es el objetivo secundario del evento (relacionado con los eventos de ratón)
screenXNúmero enteroCoordenada X de la posición del ratón respecto de la pantalla completa
screenYNúmero enteroCoordenada Y de la posición del ratón respecto de la pantalla completa
shiftKeyBooleanDevuelve true si se ha pulsado la tecla SHIFT y falseen otro caso
stopPropagation()FunciónSe emplea para detener el flujo de eventos de tipobubbling
targetElementEl elemento que origina el evento
timeStampNúmeroLa fecha y hora en la que se ha producido el evento
typeCadena de textoEl nombre del evento

Para una introducción más completa en forma de tutorial puedes ir a

Fuentes diversas