Capítulo 16 History

El API history de HTML es la manera estándar de manipular el historial de navegación a través de JavaScript. Partes de esta API ya se encontraban disponibles en versiones anteriores de HTML. Ahora, HTML5 incluye una nueva manera de añadir entradas al historial de navegación, modificando la URL pero sin actualizar la página actual, y eventos que se disparan cuando el usuario a eliminado estas entradas, pulsando el botón de volver del navegador. Esto quiere decir que la barra de direcciones sigue funcionando de la misma manera, identificando los recursos de manera única, aún cuando las aplicaciones hacen un uso intensivo de JavaScript sin recargar la página.

Como sabemos, una URL representa un recurso único. Podemos enlazarlo directamente, almacenarlo como favorito, los motores de búsqueda pueden analizar su contenido, podemos copiarlo y enviarlo por email... La URL realmente importa.

Así pues, lo que queremos es que contenidos únicos dispongan de una URL única. Hasta ahora, el comportamiento normal de los navegadores recargar de nuevo la página si modificábamos la URL, realizando una nueva petición y obteniendo de nuevo todos los recursos del servidor. No había manera de decir al navegador que cambiase la URL pero descargase únicamente la mitad de la página. El API history de HTML5 permite precisamente esto. En lugar de solicitar la carga de toda la página, podemos utilizar JavaScript para cargar únicamente los contenidos que deseemos.

La idea es la siguiente. Imaginemos que tenemos una página A y otra página B, que comparten el 90% de su contenido. Cuando un usuario se encuentra en la página A, y quiere navegar a la B, lo normal es realizar una petición completa. En lugar de realizar esta petición, interrumpimos esta navegación y realizamos los siguientes pasos de manera manual:

  1. Cargar el 10% de contenido diferente de la página B, a través de algún método como AJAX.
  2. Cambiar el contenido, utilizando innerHTML u otros métodos del DOM. Es posible que tengamos que reiniciar los eventos asociados a los elementos.
  3. Actualizamos la URL del navegador, indicando la dirección de la página B, utilizando el API history de HTML5.

Tras realizar estos pasos, disponemos de un DOM exacto al de la página B, como si hubiésemos navegado hasta ella, pero sin realizar una petición completa.

16.1 API

El API de HTML4 ya incluía algunos métodos básicos para movernos a través del historial de navegación, como eran history.back();, history.forward() y history.go(n). Sin embargo, estos métodos no permitían modificar la pila del historial, por lo que no eran de gran utilidad. HTML5 ha introducido dos nuevos métodos que nos permiten añadir y modificar las entradas del historial, concretamente history.pushState() y history.replaceState(). Además de estos métodos, se ha añadido también un evento window.onpopstate, que es lanzado cada vez que alguna de las entradas de history cambia.

16.1.1 Método pushState()

Supongamos que estamos visitando http://www.arkaitzgarro.com/html5/index.html y a través de un script realizamos la siguiente operación:

var stateObj = { foo: "bar" };
history.pushState(stateObj, "Demos", "demos.html");

Esto va a provocar que en la barra de direcciones se muestre http://www.arkaitzgarro.com/html5/demos.html, pero el navegador no va a cargar demos.html ni va a comprobar su existencia. En este punto, si navegamos a otra página como http://www.google.es/, y después presionamos el botón de volver, en la URL se mostrará http://www.arkaitzgarro.com/html5/demos.html y la página lanzará el evento popstate, donde el estado contiene una copia de stateObj. La página que se mostrará será index.html, pero deberemos realizar un trabajo extra al lanzarse el evento para mostrar el contenido correcto.

El método pushState() toma tres parámetros: un objeto de estado, un título y una URL:

  • El objeto de estado: es un objeto de JavaScript asociado con la nueva entrada del historial creada con pushState(). Cada vez que el usuario navega al estado creado, el evento popstate es disparado, y la propiedad state del evento contiene una copia de este objeto. Este objeto puede representar cualquier que se pueda serializar. Como este objeto se almacena en el disco, es posible recuperarlo aunque el navegador se cierre. Los navegadores imponen un límite de tamaño a la hora de almacenar estados (en el caso de Firefox 640KB). Si se necesita más espacio, es recomendable utilizar sessionStorage o localStorage.
  • El título: representa el nuevo título de la página a la que navegamos.
  • La nueva URL: corresponde con la nueva URL que se añade al historial de navegación. Esta URL puede ser absoluta o relativa, la única restricción es que corresponda al dominio del documento actual. Si no se especifica este parámetro, la URL corresponde con el documento actual.

En esencia, ejecutar el método pushState() es similar a definir window.location = "#foo", ya que en ambos casos se crea y activa una nueva entrada en el historial asociada con el documento actual. Pero pushState() ofrece las siguientes ventajas:

  • La nueva URL puede ser cualquier URL dentro del dominio actual. En cambio, window.location = "#foo" se mantiene siempre en el documento actual.
  • No es necesario cambiar la URL actual para añadir una nueva entrada y almacenar datos asociados.
  • Podemos asociar datos a una nueva entrada en el historial. Con el enfoque basado en hash (#), los datos tenemos que añadirlos a la URL.

16.1.2 Método replaceState()

El método history.replaceState() funciona de manera similar a history.pushState(), a excepción de que replaceState() modifica la entrada actual del historial, en lugar de añadir una nueva. Éste método es útil cuando queremos actualizar el objeto de estado de la entrada actual en respuesta a una acción del usuario.

16.1.3 Evento popstate

El evento popstate es lanzado cada vez que la entrada actual del historial cambia por otra entrada existente en el mismo documento. Si la entrada del historial que está siendo activada fue creada a través de history.pushState() o se modificó con history.replaceState(), el evento popstate recibe como parámetro una copia del estado de la entrada del historial.

Éste evento no se lanza cuando se llama a history.pushState() o history.replaceState(). Únicamente se dispara realizando una acción en el navegador como pulsando el botón atrás o ejecutando history.back() en JavaScript. Un ejemplo de este comportamiento:

window.onpopstate = function(event) {
    alert(document.location + ", state: " + JSON.stringify(event.state));
};
 
history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");
 
// alerts "http://example.com/index.html?page=1, state: {"page":1}"
history.back();
// alerts "http://example.com/index.html, state: null
history.back();
// alerts "http://example.com/index.html?page=3, state: {"page":3}
history.go(2);

Cuando la página se carga, por ejemplo al reiniciar el navegador, tango Chrome como Safari lanzan el evento popstate, pero no es el caso de Firefox. Sin embargo, en este caso, es posible acceder a los datos almacenados en pushState desde la propiedad state del objeto history.

window.onload = function() {
    var currentState = history.state;
}

Índice de contenidos