Capítulo 15 API History
Lo que en HTML5 normalmente llamamos API History es en realidad solo una mejora de una vieja API que nunca tuvo una implementación oficial pero fue soportada por navegadores durante años. Esta vieja API estaba compuesta solo por un pequeño grupo de métodos y propiedades, algunos de ellos parte del objeto History. La nueva API History es precisamente una mejora de este objeto y fue incluida oficialmente en la especificación HTML como la interface History. Esta interface combina todos los viejos métodos y propiedades con algunos nuevos para trabajar y modificar el historial del navegador de acuerdo a nuestras necesidades.
El historial del navegador es una lista de todas las páginas web (URLs) visitadas por el usuario durante una sesión. Es lo que hace la navegación posible. Usando los botones de navegación a la izquierda de la barra de navegación de todo navegador podemos ir hacia atrás o hacia adelante en este lista y visitar documentos que vimos anteriormente. Esta lista es construida con URLs reales generadas por los sitios web, incluidas en cada enlace dentro de sus documentos. Con las flechas del navegador podemos cargar la página web que fue visitada anteriormente o volver a la última.
A pesar de la practicidad de los botones de navegación, a veces es útil navegar a través del historial desde dentro del documento. Para simular las flechas de navegación desde Javascript, siempre contamos con los siguientes métodos y propiedades:
back() Este método retrocede un paso en el historial (imitando la flecha izquierda del navegador).
forward() Este método avanza un paso en el historial (imitando la flecha derecha del navegador).
go(pasos) Este método avanza o retrocede en el historial la cantidad de pasos especificados. El atributo pasos puede ser un valor negativo o positivo de acuerdo a la dirección hacia dónde queremos ir.
length Esta propiedad retorna el número de entradas en el historial (el total de URLs en la lista).
Estos métodos y propiedades deben ser declarados como parte del objeto History, con una expresión como history.back(). También podemos usar el objeto Window para referenciar la ventana, pero esto no es necesario. Por ejemplo, si queremos regresar a la página anterior en el historial podemos usar los códigos window.history.back() o window.history.go(-1).
IMPORTANTE: Esta parte de la API es conocida y utilizada por la mayoría de los diseñadores y programadores web estos días. No vamos a estudiar ningún código de ejemplo sobre estos métodos, pero puede visitar nuestro sitio web y seguir los enlaces correspondientes a este capítulo si necesita mayor información al respecto.
Cuando el uso del objeto XMLHttpRequest se volvió estándar y las aplicaciones Ajax se convirtieron en un éxito extraordinario, la forma en la que los usuarios navegaban y accedían a los documentos cambió para siempre. Ahora es común programar pequeños códigos para obtener información desde el servidor y mostrarla dentro del documento actual, sin actualizar el contenido completo de la ventana o cargar un nuevo documento. Los usuarios interactúan con sitios web modernos y aplicaciones desde la misma URL, recibiendo información, ingresando datos y obteniendo resultados de procesos siempre desde la misma página web. La web ha comenzado a emular aplicaciones de escritorio.
Sin embargo, la forma en la que los navegadores siguen los pasos del usuario es a través de URLs. URLs son, de hecho, los datos dentro de la lista de navegación, las direcciones que indican dónde el usuario se encuentra actualmente. Debido a que las nuevas aplicaciones web evitan el uso de URLs para apuntar a la ubicación del usuario dentro de la aplicación, pronto se volvió evidente que pasos importantes en el proceso se perdían sin dejar rastro alguno. Los usuarios podían actualizar datos en una página web docenas de veces y aun así ningún rastro de actividad quedaba almacenado en el historial del navegador para indicar los pasos seguidos.
Nuevos métodos y propiedades fueron incorporados a la ya existente History API con la intención de modificar manualmente la URL en la barra de localización así como también el historial del navegador simplemente usando código Javascript. Desde ahora tenemos la posibilidad de agregar URLs falsas al historial y de este modo mantener control sobre la actividad del usuario.
pushState(estado, título, url) Este método crea una nueva entrada en el historial. El atributo estado declara un valor para el estado de la entrada. Es útil para identificar la entrada más adelante y puede ser especificado como una cadena de texto o un objeto JSON. El atributo título es el título de la entrada, y el atributo url es la URL para la entrada que estamos generando en el historial (este valor reemplazará a la URL que aparece actualmente en la barra de localización). replaceState(estado, título, url) Este método trabaja exactamente igual a pushState (), pero no genera una nueva entrada. En su lugar, reemplaza la información de la actual.
state Esta propiedad retorna el valor del estado de la entrada actual. Este valor será nuil (nulo) a menos que haya sido declarado por alguno de los métodos anteriores usando el atributo estado.
Las URLs generadas usando métodos como pushstate () son URLs falsas en el sentido de que los navegadores nunca controlan su validez y la existencia del documento al que supuestamente apuntan. Depende de nosotros asegurarnos que estas URLs falsas sean en realidad válidas y útiles.
Para crear una nueva entrada en el historial del navegador y cambiar la dirección URL dentro de la barra de navegación, necesitamos usar el método pushstate (). Veamos un ejemplo de cómo trabaja:
<!DOCTYPE html>
<html lang=»es»>
<head>
<title>History API</title>
<link rel=»stylesheet» href=»history.css»> <script src=»history.js»></script>
</head>
<body>
<section id=»contenido»>
Este contenido nunca es actualizado<br> <span id=»url»>página 2</span>
</section>
<aside id=»cajadatos»></aside>
</body>
</html>
Listado 15-1. Plantilla básica para aplicar la API History.
En el Listado 15-1 presentamos un código HTML con los elementos básicos necesarios para probar esta API. Con este propósito, colocamos contenido permanente dentro de un elemento <section> identificado como contenido, un texto que se convertirá en un link para generar la segunda página virtual, y nuestra ya acostumbrada caj adatos para mostrar el contenido alternativo.
Los siguientes son los estilos básicos necesarios para diferenciar las partes que componen la plantilla:
#contenido{
float: left;
padding: 20px;
border: 1px solid #999999;
}
#caj adatos{
float: left;
width: 500px;
margin-left: 20px;
padding: 20px;
border: 1px solid #999999;
}
#contenido span{ color: #0000FF;
cursor: pointer;
}
Listado 15-2. Estilos para las cajas y los elementos <span> (history. css).
Lo que vamos a hacer en este ejemplo es agregar una nueva entrada con el método pushState() y actualizar el contenido sin recargar la página o cargar un nuevo documento.
function iniciar(){
caj adatos=document.getElementById(‘caj adatos’); url=document.getElementById(‘url’); url.addEventListener(‘click’, cambiar, false);
}
function cambiar(){
cajadatos.innerHTML=’La URL es pagina2′; window.history.pushState(null, null, ‘pagina2.html’);
}
window.addEventListener(‘load’, iniciar, false);
Listado 15-3. Generando una nueva URL y nuevo contenido (history. js).
En la función iniciar() del Listado 15-3, creamos la referencia apropiada para el elemento cajadatos y agregamos una escucha para el evento click al elemento <span>. Cada vez que el usuario hace clic sobre el texto dentro de <span>, la función cambiar() es llamada.
La función cambiar() realiza dos tareas: actualiza el contenido de la página con nueva información e inserta una nueva URL al historial. Luego de que esta función es ejecutada, cajadatos muestra el texto «La URL es pagina2» y la URL del documento principal en la barra de localización es reemplazada por la URL falsa «pagina2.html«.
Los atributos estado y título para el método pushState() esta vez fueron declarados como null (nulo). El atributo título no está siendo usado en este momento por ningún navegador y siempre lo declararemos como null, pero el atributo estado, por el contrario, es útil y será aprovechado en próximos ejemplos.
Hágalo usted mismo: Copie la plantilla en el Listado 15-1 dentro de un archivo HTML. Cree un archivo CSS llamado history.css con los estilos del Listado 15-2 y un archivo Javascript llamado history.js con los códigos del Listado 15-3.
Súbalos a su servidor y abra el archivo HTML en su navegador. Haga clic sobre el texto «página 2» y compruebe que la URL en la barra de localización cambió por la URL falsa generada por el código.
Lo que hemos hecho hasta ahora es solo una manipulación del historial de la sesión. Le hicimos creer al navegador que el usuario visitó una URL que, a este punto, ni siquiera existe. Luego de que el usuario hace clic en el enlace «página 2», la URL falsa «pagina2.html» es mostrada en la barra de localización y nuevo contenido es insertado en el elemento cajadatos, todo sin recargar la página web o cargar una nueva. Es un truco interesante pero no realmente útil. El navegador todavía no considera a la nueva URL como un documento real. Si intenta retroceder o avanzar en el historial usando los botones de navegación del navegador, la URL cambia entre la que generamos artificialmente y la URL del documento principal, pero el contenido del documento no es modificado. Necesitamos detectar cuando las URLs falsas son visitadas nuevamente y realizar las modificaciones apropiadas al documento para mostrar el estado correspondiente a la URL actual.
Previamente mencionamos la existencia de la propiedad state. El valor de esta propiedad es declarado durante la generación de la nueva URL y es usado para identificar cuál es la dirección web actual. Para trabajar con esta propiedad, la API provee un nuevo evento:
popstate Este evento es disparado cuando una URL es visitada nuevamente o un documento es cargado. Provee la propiedad state con el valor del estado declarado cuando la URL fue generada con los métodos pushstate () o replaceState (). Este valor es nuil (nulo) si la URL es real, a menos que lo hayamos cambiado antes usando replaceState (), como veremos en el siguiente ejemplo.
En el próximo código mejoraremos el ejemplo previo implementando el evento popstate y el método replacestate() para detectar cuál URL el usuario está solicitando a cada momento.
function iniciar(){
caj adatos=document.getElementByld(‘caj adatos’); url=document.getElementById(‘url’);
url.addEventListener(‘click’, cambiar, false);
window.addEventListener(‘popstate’, nuevaurl ,false);
window.history.replaceState(1, null);
}
function cambiar(){ mostrar(2);
window.history.pushState(2, null, ‘pagina2.html‘);
}
function nuevaurl(e){ mostrar(e.state);
}
function mostrar(actual){
cajadatos.innerHTML=’La URL es página ‘+actual;
}
window.addEventListener(‘load’, iniciar, false);
Listado 15-4. Controlando la ubicación del usuario (history. js).
Debemos hacer dos cosas en nuestra aplicación para tener control absoluto sobre la situación. Primero, tenemos que declarar un valor de estado para cada URL que vamos a utilizar, las falsas y las reales. Y segundo, el contenido del documento debe ser actualizado de acuerdo a la URL actual.
En la función iniciar() del Listado 15-4, una escucha fue agregada para el evento popstate. Esta escucha llamará a la función nuevaurl() cada vez que una URL es visitada nuevamente. La función simplemente actualizará el contenido de la cajadatos con un mensaje indicando cuál es la página actual. Lo que hace es tomar el valor de la propiedad state y enviarlo a la función mostrar () para mostrarlo en pantalla.
Esto funcionará para cada URL falsa, pero como explicamos antes, las URLs reales no tienen un valor de estado por defecto. Usando el método replaceState() al final de la función iniciar() cambiamos la información de la entrada actual (la URL real del documento principal) y declaramos el valor 1 para su estado. Ahora, cada vez que el usuario visite nuevamente el documento principal podremos detectarlo comprobando este valor.
La función cambiar() es la misma que antes, excepto que esta vez usa la función mostrar () para actualizar el contenido del documento y declarar el valor 2 para el estado de la URL falsa.
La aplicación trabaja de la siguiente forma: cuando el usuario hace clic sobre el enlace «página 2», el mensaje «La URL es página 2» es mostrado en pantalla y la URL en la barra de navegación es reemplazada por «pagina2.html» (incluyendo la ruta completa, por supuesto). Esto es lo que habíamos hecho hasta el momento, pero aquí es donde las cosas se vuelven interesantes. Si el usuario presiona la flecha izquierda en la barra de navegación del navegador, la URL dentro de la barra de localización será reemplazada por la que se encuentra en la posición anterior del historial (esta es la URL real de nuestro documento) y el evento popstate será disparado. Este evento llama a la función nuevaurl() que lee el valor de la propiedad state y lo envía a la función mostrar (). Ahora el valor del estado es 1 (el valor que declaramos para esta URL usando el método replaceState ()) y el mensaje mostrado en la pantalla será «La URL es página 1». Si el usuario vuelve a visitar la URL falsa usando la flecha derecha en la barra de navegación, el valor del estado será 2 y el mensaje mostrado en pantalla será nuevamente «La URL es página 2».
Como puede ver, el valor de la propiedad state es cualquier valor que desee usar para controlar cuál es la URL actual y adaptar el contenido del documento a la misma.
Hágalo usted mismo: Use los archivos con los códigos de los Listados 15-1 y 15-2 para el documento HTML y los estilos CSS. Copie el código del Listado 15-4 dentro del archivo history.j s y suba todos los archivos a su servidor. Abra la plantilla HTML en su navegador y haga clic sobre el texto «página 2». La nueva URL será mostrada y el contenido de cajadatos cambiará de acuerdo a la URL
correspondiente. Presione las flechas izquierda y derecha en el navegador para moverse a través del historial y ver cómo la URL cambia y cómo el contenido del documento es actualizado de acuerdo a la URL seleccionada (el contenido es mostrado en pantalla de acuerdo al valor del estado actual).
IMPORTANTE: La URL «pagina2.html» generada con el método pushstate() en los ejemplos previos es considerada falsa, pero debería ser real. El propósito de esta API no es crear URLs falsas sino proveer a los programadores la alternativa de registrar la actividad del usuario en el historial para poder volver a un estado anterior toda vez que sea requerido (incluso luego de que el navegador fue cerrado). Usted mismo deberá asegurarse de que el código en su servidor retorna el apropiado contenido por cada una de las URLs usadas por la aplicación (las reales y las falsas).
La siguiente es una aplicación práctica. Vamos a usar la API History y todos los métodos estudiados anteriormente para cargar un grupo de cuatro imágenes desde el mismo documento. Cada imagen es asociada a una URL falsa que podrá ser usada más adelante para retornar una imagen específica desde el servidor.
El documento principal es cargado con una imagen por defecto. Esta imagen estará asociada al primero de cuatro enlaces que son parte del contenido permanente del documento. Todos estos enlaces apuntarán a URLs falsas referenciando un estado, no un documento real (incluyendo el enlace para el documento principal que será cambiado por «pagina1.html»). Todo el proceso tendrá más sentido pronto, por ahora veamos el código de la plantilla HTML:
<!DOCTYPE html>
<html lang=»es»>
<head>
<title>History API</title>
<link rel=»stylesheet» href=»history.css»>
<script src=»history.j s»></script>
</head>
<body>
<section id=»contenido»>
Este contenido nunca es actualizado <br>
<span id=»url1″>imagen 1</span> – <span id=»url2″>imagen 2</span> – <span id=»url3″>imagen 3</span> – <span id=»url4″>imagen 4</span> – </section>
<aside id=»cajadatos»>
<img id=»imagen» src=»http://www.minkbooks.com/content/
monster!.gif»>
</aside>
</body>
</html>
Listado 15-5. Plantilla para una aplicación «real».
La única diferencia significativa entre esta nueva aplicación y la anterior es el número de enlaces y la cantidad de URLs que estamos manejando. En el código del Listado 15-4, teníamos dos estados, el estado 1 correspondiente al documento principal y el estado 2 para la URL falsa «pagina2.html» generada por el método pushstate(). En este caso, debemos automatizar el proceso y generar un total de cuatro URLs falsas correspondientes a cada imagen disponible.
function iniciar(){ for(var f=1;f<5;f++){
url=document.getElementById(‘url’+f); url.addEventListener(‘click’, function(x){ return function(){ cambiar(x); }
}(f), false);
}
window.addEventListener(‘popstate’, nuevaurl ,false); window.history.replaceState(1, null, ‘pagina1.html’);
}
function cambiar(pagina){ mostrar(pagina);
window.history.pushState(pagina, null, ‘pagina’+pagina+’.html‘);
}
function nuevaurl(e){ mostrar(e.state);
}
function mostrar(actual){ if(actual!=null){
imagen=document.getElementById(‘imagen’);
imagen.src=’http://www.minkbooks.com/content/monster’ +
actual + ‘.gif’;
}
}
window.addEventListener(‘load’, iniciar, false);
Listado 15-6. Manipulando el historial (history.js).
Como se puede apreciar, estamos usando las mismas funciones pero con algunos cambios importantes. Primero, el método replaceState() en la función iniciar () tiene el atributo url declarado como «pagina1.html«. Decidimos programar nuestra aplicación de este modo, declarando el estado del documento principal como 1 y cambiando su URL por «pagina1.html» (independientemente de la URL real del documento). De este modo será simple pasar de un estado a otro, siempre usando el mismo formato y los valores de la propiedad state para construir todas las URL utilizadas por la aplicación. Puede ver este procedimiento en la práctica estudiando la función cambiar (). Cada vez que el usuario hace clic en uno de los enlaces de la plantilla, esta función es ejecutada y la URL falsa es construida con el valor de la variable pagina y agregada al historial de la sesión. El valor recibido por esta función fue previamente declarado en el bucle for al comienzo de la función iniciar (). Este valor es declarado como 1 para el enlace «página 1», 2 para el enlace «página 2», y así sucesivamente.
Cada vez que una URL es visitada, la función mostrar () es ejecutada para actualizar el contenido (la imagen) de acuerdo a la misma. Debido a que el evento popstate a veces es disparado en circunstancias en las que el valor de la propiedad state es nuil (como cuando el documento es cargado por primera vez), controlamos el valor recibido por la función mostrar () antes de continuar. Si este valor es diferente de nuil, significa que la propiedad state fue definida para esa URL, por lo tanto la imagen correspondiente con ese estado es mostrada en pantalla.
Las imágenes usadas para este ejemplo fueron nombradas monster1.gif, monster2.gif, monster3.gif y monster4.gif, siguiendo el mismo orden de los valores de la propiedad state. Así, usando este valor podemos seleccionar la imagen a ser mostrada. Sin embargo, recuerde que los valores usados pueden ser cualquiera que usted necesite y el proceso para crear URLs falsas y contenido asociado debe ser desarrollado de acuerdo con las necesidades de su aplicación.
También recuerde que los usuarios deberían poder regresar a cualquiera de las URLs generadas por la aplicación y ver el contenido correspondiente en la pantalla cada vez que lo deseen. Usted debe preparar su servidor para procesar estas URLs de modo que cada estado de la aplicación (cada URL falsa) esté disponible y sea siempre accesible. Por ejemplo, si un usuario abre una nueva ventana y escribe la URL «pagina2.html» en la barra de navegación, el servidor debería retornar el documento principal conteniendo la imagen «monster2.gif», correspondiente a esta URL, y no simplemente la plantilla del Listado 15-5. La idea detrás de esta API es proveer una alternativa para que los usuarios puedan regresar a cualquier estado previo, en cualquier momento; algo que solo podemos lograr volviendo válidas a las URLs falsas.
IMPORTANTE: El bucle for usado en el código del Listado 15-6 para agregar una escucha para el evento click a cada elemento <span> en el documento aprovecha una técnica Javascript que nos permite enviar valores reales a una función. Para enviar un valor a la función que manejará el evento en un método addEventListener (), debemos declarar el valor real. Si en su lugar enviamos una variable, lo que realmente es enviado no es el valor de la variable sino una referencia a la misma. Por lo tanto, en este caso, para enviar el valor actual de la variable f en el bucle for tenemos que usar varias funciones anónimas. La primera función es ejecutada en el momento en el que el método addEventListener () es declarado. Esta función recibe el valor actual de la variable f (vea los paréntesis al final) y almacena este valor en la variable x. Luego, la función retorna una segunda función con el valor de la variable x. Esta segunda función es la que será ejecutada con el valor correspondiente cuando el evento es disparado. Esta es una técnica compleja que deberá aprender junto con otras si desea crear códigos Javascript profesionales. Para obtener más información sobre este tema, visite nuestro sitio web y siga los enlaces correspondientes a este capítulo.
Hágalo usted mismo: Para probar el último ejemplo use el mismo documento HTML del Listado 15-5 con los estilos CSS del Listado 15-2. Copie el código del Listado 15-6 dentro del archivo history.js y suba los archivos a su servidor. Abra la plantilla en su navegador y haga clic en los enlaces. Navegue a través de las URLs seleccionadas usando los botones de navegación del navegador. Las imágenes en la pantalla cambiarán de acuerdo a la URL en la barra de localización.
API History nos permite manipular el historial de la sesión en el navegador para seguir los pasos de los usuarios dentro de la aplicación. Esta API es incluida en la especificación oficial como la interface History. Esta interface combina métodos y propiedades, nuevos y viejos.
length Esta propiedad retorna el número total de entradas en el historial. state Esta propiedad retorna el valor del estado para la URL actual. go(pasos) Este método avanza o retrocede en el historial de navegación de acuerdo al valor del atributo paso. Este valor puede ser negativo o positivo dependiendo de la dirección de navegación deseada. back() Este método carga la URL anterior desde el historial. forward() Este método carga la URL siguiente desde el historial.
pushState(estado, título, url) Este método inserta nuevos datos en el historial. El atributo estado es el valor del estado que queremos otorgar a esta nueva entrada. El atributo título es el título de la entrada. Y el atributo url es la nueva URL que queremos generar en el historial.
replaceState(estado, título, url) Este método modifica la entrada actual. El atributo estado es el valor del estado que queremos otorgar a la entrada actual. El atributo título es el título de la entrada. Y el atributo url es la nueva URL que queremos asignar a la entrada actual.
popstate Este evento es disparado en determinadas circunstancias para informar el valor del estado actual.