Controlar el botón de atrás o botón adelante del Navegador en Javascript AJAX (Historial de navegación)

Hoy vamos a escribir el mejor articulo en español para dominar el control del botón de atras de los navegadores. A día de hoy es muy facil añadir funcionalidades AJAX a nuestras páginas, pero que problematicas encontramos? La primera y mas indeseable de todas es que perdemos el botón de atras del navegador, el back button, otro día trataremos la seguridad o mejor dicho la inseguridad...

Conociendo a los navegadores y el funcionamiento del historial de navegación:

Firefox 2 : El funcionamiento del botón atrás como todo el mundo sabe es sencillo. Pero cuando se guarda firefox una url en el botón de atrás? hay dos maneras:

  • Cada vez que pulsamos un link o formulario. Firefox almacena una url nueva en su historia de navegación. Este evidentemente no nos sirve porque cambiamos y recargamos la página.
  • Además y por curioso que parezca, cuando en una misma página pulsamos sobre un link, pero esta vez no uno que vaya a otra url, sinó uno que simplemente contenga un ancla para bajar o subir dentro de una misma página. Firefox 2 almacena esta url en su historial, y lo mejor de todo, no recargan la página. Utilizando estas anclas para guardar iinformación conseguiremos nuestro objetivo, saber cuando han pusaldo e botón de atras y que evento debemos actualizar.

Firefox 3: E principio se comporta igual que firefox 2. Pero ahora mismo haciendo unas pruebas, y  pese a no actualizar la url del iframe al pulsar sobre el boton atras, si actualiza el hash de la url, con lo que es posible detectar el cambio de página mediante el iframe oculto. Faltaria verificar si firefox 2 tambien actualiza el hash del iframe, cuando lo probé vi que no actualizaba la url y desisté de hacer mas pruebas. (cuando me refiero a hash hablo de la propiedad : ObjetoIframe.contentWindow.document.location.hash)

Si esto fuera verdad se podría utilizar el iframe oculto tanto para IE, como para firefox 2 y 3...

Internet explorer 6 e Internet Explorer 7: Es algo distinto que firefox por definicion... Así que este navegador solo guarda una url cada vez que recargamos una página html. Esto supone un problema porque lo que pretendemos hacer es sin recargar la página actual. Queremos conseguir que el navegador añada una entrada en su historial, para despues nostros recuperar la información necesaria y refrescar la zona que sea necesaria mediante AJAX.

Pero Internet Explorer tiene algo bueno en este caso. Si colocamos un Iframe en la página, toda url cargada en ese iframe es almacenada en el historial de navegación. Así que genial, ya tenemos como conseguir añadir url's al historial, sin recargar la página actual, pero si el iframe. Tan solo añadiremos un iframe oculto, vació, invisible con css o de tamaño 0px por 0px.  Para engañar al navegador y coseguir añadir url's al historial de navegación cada vez que invoquemos una peticion AJAX.

Además IE no es capaz de memorizar las anclas como url's disntintas motivo por el cual, al no añadir al historial ese cambio de url necesitamos necesariamente recargar una página dentro del iframe. esto hace que debamos platear una solución alternativa a la que utilizaremos en Firefox

Nota: Firefox no vuelve a recargar la página de un iframe! Así que aunque si se añade a la navegación, no recarga la pagina, y no cambia la url del iframe, lo que impide detectar cuando ha habido una variación desde javascript.

Navegador Opera  9.10: Haciendo unas pruebas con este navegador, parece que almacena en su historial tanto los cambios de los iframes como los cambios de ancla's sobre una misma url, por lo que se podría utilizar cuaquiera de las dos variates de la solución. Este ejemplo es compatible con Opera, puesto que solo diferenciamos a IE, la variante de utilizar las anclas para Opera funciona correctamente. Por lo menos para la version 9.10.

Navegador Google Chrome en su versión 1.0.154.43 : No almacena los cambios de iframe dentro de su historial de navegación. Me sorprende, quizas al detectar que la página es igual no la guarda, no lo se... Pero por lo que parece no funcionaria ir cargando paginas en el iframe oculto. Actualmente en la versión 1.0.154.46 Sigue comportandose  de la misma manera. Nuestro ejemplo funciona correctamente en las versiones de google Chrome comentadas. Grácias a que almacena en su historial de navegación los cambios de ancla en una misma página. Así puede comportarse correctamente como si se tratara de un navegador Firefox.

Navegador Safari sobre windows versión 3.2.1: Sobre esta versión de safari el ejemplo funciona bien. Este navegador tambien recuerda la navegación de las anclas.  Safari se comporta como Google Chrome y no guarda en su historial de navegación las páginas cargadas en los iframes. Así que el botón atras cambia al ultimo enlace.

Conclusion:

La mayoria de los navegadores recuerdan en su historial de navegación las anclas pulsadas así podemos utilizar el control de botón de atras con el sistema de cambio anclas. Solo diferenciando a Internet Explorer tendremos suficiente para aplicar las dos soluciones planteadas.

Si alguien tiene tiempo y quiere probarlo sobre MAC y Linux, estaré agradecido.

LA SOLUCIÓN:

Resumen de lo necasario:

  • Un Iframe oculto en la pagina, ( para IE  que no sabe recordar anclas)
  • Un fichero dinamico (asp, php o fiticio) que esté en blanco para usar en en las urls del  iframe.
  • Una porción de codigo que gestione el cambio de urls al hacer peticions AJAX.
  • Una función de javascript (temporizador) que vigile los cambios de las urls cada segundo.
  • Una array javascritp con el historial de navegación, ( y la información necesaria para revertir los cambios y así actualizar la página convenientemente con AJAX)

La solución planteda, para conseguir emular la tecla o boton de atras de los navegadores cuando hacemos peticiones AJAX o aplicaciones de este estilo, es un sistema tan sencillo que no ocupa mas de 30 lineas de código.  El sistema se basa en el siguiente concepto. Vamos ha añadir cada vez que hagamos una peticion AJAX, un identificador concatenado a la url actual en forma de ancla.Asi podriamos hacer una variable incremental con el número de peticion y concatenarlo detras de la url. Mediante un

document.location=document.location+"#"+idNuevaPeticion

Ahora repetiremos el mismo proceso para el iframe oculto, en caso que detectemos que el navegador es IE. Con una ligera modificación. Para que Internet explorer entienda que la página es distinta se deberá concatenar una variable a la url (para no tener que inventarnos cada vez una url distinta.) Para  maor comodidad, aparte de la variable concatenaremos tambien el ancla que nos es mas facil de recoger desde javascript. Ahora quedaria algo asi:

JAVASCRIPT:
  1. function EnviarFormulari(){
  2.  
  3. // 1- activar el flag de que cambiamos nosotros las urls
  4.  
  5. busquedaLanzada=true;    // variable global para determinar si hemos sido nosotros quien hemos cambiado la url o a sido el navegador provocado por un back button o boton de atras
  6.  
  7. // 2 cambiamos las urls
  8.  
  9. // Ahora segun el navegador cambiamos la url del iframe o la del url del document
  10. if (esNavegadorIE()){
  11. var rutaExterna= ObtenerRutaExterna();
  12. Obtenir("historial").src=rutaExterna + "/unFicheroVacio.php?variableEngaño=" + idHistorialActual+ "#"+idHistorialActual;
  13. }else{
  14. window.location=LimpiarUrl(window.location)+'#'+idHistorialActual;
  15. }
  16.  
  17. // 3 - Enviamos la peticon AJAX
  18.  
  19. /**   Aquí iria tu código de peticiones ajax...
  20. crear tu objeto HTTPREQUEST, definir que datos enviamos a que url, etc, etc, no afecta al control del botón de atras...
  21. */
  22.  
  23. }
  24.  
  25. /**
  26. * Existe un problema de denegacion de servicio por cross domain,
  27. * para ello es necesrio que lso javascrips esten todos llamados desde ficheros bajo el mismo protocolo y dominio,
  28. * distingue entre tudominio.com y www.tudominio.com , http://tudominio.com , https://tudominio.com tambien son considerados dominios distintos. de cara a este error.
  29. * Esta funcion recoge los parametros de la url cargada para saber como hemos de cargar los escripts
  30. */
  31.  
  32. function ObtenerRutaExterna(){
  33.  
  34. //existe el problema de across domain denegation Script
  35. var protocoloUrlPrincipal=document.location.protocol;
  36. var dominioUrlPrincipal=document.location.host;
  37.  
  38. return protocoloUrlPrincipal+"//"+dominioUrlPrincipal;
  39. }
  40. /**
  41. * Detectar IE, mediante objetos
  42. */
  43. function esNavegadorIE(){
  44. // hay muchas maneras a mi me gusta esta.
  45. // este componente solo tiene Internet Explorer apartir de la versión 5.5
  46. if (window.ActiveXObject) {
  47. return true;
  48. }
  49. else{
  50. return false;
  51. }
  52. }

Vemos ya un par de funciones necesarias una para detectar el navegador, Otra para conseguir la URL tal y como han llegado, esto sirve por si utlizas SSL y tienes dos tipos de protocolos, http://tudominio.com y https://tudominio.com.  De todos modos lo que comenta que soluciona esta función seria si quisieramos ejecutar código javascript desde el iframe al documento principal, pero finalmente no ha sido necesario, asi que se podria simplificar este paso. y poner una ruta absoluta como querais!

la funcion Obtener, tansolo hace un document.GetElementById(), en este caso del iframe.

Lo mas destacable de este codigo es la variable Historial, que hemos creado nosotros y donde nos guardaríamos toda la información necesaria para conseguir hacer la misma petición AJAX que invoca el proceso. esta variable es un array en nuestro caso, que tiene en cada posición del array correspondiente a un id incremental, la busqueda que realiza un buscador AJAX. Con ese dato tenemos suficiente para actualizar la página, para aplicaciones mas complejas, este ARRAY Historial,  deberá ser una estructura mas compleja un objeto o lo que necesite cada uno en su caso. Para cada aplicación esto variará. Abusando de memoria te podria almacenar la respuesta de la petición, para no volver a lanzar al ir atrás...

Enfin, vamos a seguir explicando el preceso

Por ahora hemos visto como conseguir que por cada petición ajax tengamos que el botón de atras del navegador se active, cambie la url visible del navegador en Firefox o cmabie la dle iframe oculto en Internet explorer. Pero ahora como recargamos nosotros la pagina que queriamos???

Bueno necesitaremos crear lo que he denominado como el Guardian de las Urls :P, en definitiva no es mas que una funcion de javascript que se llame cada tanto tiempo para determinar si ha habido un cambio en la url. Cuando este cambio se ha producido tendremos que decidir si lo hemos provocado nostros justo antes de enviar una petición mediante el codigo que hemos visto arriba o  si por el contrario alguien ha pulsado sobre el botón de atras o el botón de alante. Para ello solo sera necesaria una variable o Flag que nos indique esto. En nuestro ejemplillo es la llamada busquedaLanzada que ponemos a true justo antes de cambiar las url's!

Para mirar si la url a cambiamo respecto la ultima vez que hemos pasado a revisarla solo serán necesario 2 variables una para controlar la url de iframe y otra para controlar la url del document.

Os pongo e código usado:

JAVASCRIPT:
  1. var busquedaLanzada=false;// para controlar se se ha echo ya la petición de buscar o es un boton atras.
  2. var temporizador; // variable global que se utiliza para controlar el temporizador de revision de url
  3. var velocidadTemporizador=1000;
  4. var idHistorialActual=0; // proporciona historial de busquedas
  5. var idHistorialAnterior=0;
  6. var idHistorialAnteriorIE=0;
  7. var RUTA_EXTERNA= 'http://sintoner.com/';
  8. var historial=new Array();
  9.  
  10. function GuardianUrl(){
  11. temporitzador=setTimeout("GuardianUrl()",velocidadTemporizador); //1000= 1 seg.
  12. //repite esa funcion cada tanto tiempo. para pararlo se utilizatria un clearTimeout(variable_de_tiempo)
  13.  
  14. // consiguiendo el HASH de las rutas pagina principal e IFRAME.
  15. // HASH URL NORMAL CASO FirefoxF3
  16. var urlActual = window.location.toString();
  17. var busquedaUrl = window.location.hash.substr(1);
  18. // HASH URL IFRAME CASO IE7,IE6
  19. var urlActualIframe = document.getElementById('historial').contentWindow.document.location;
  20. var busquedaUrlIE = String(urlActualIframe.hash.substr(1));
  21.  
  22. //siempre va a pasar que: el de la ur es menor o igual al Actual + Diferente al ultimo mirado (primer if detecta si ha cambiado la url en alguno de los 2 sistemas IE o FF3)
  23. if( (busquedaUrlIE!= idHistorialAnteriorIE && idHistorialAnteriorIE> 0 ) || busquedaUrl<=idHistorialActual && idHistorialAnterior!=busquedaUrl && busquedaUrl>0 && idHistorialActual>0){
  24. if(!busquedaLanzada){
  25. //si no se ha lanzado la busqueda querrá decir que es un botón atras o alante.
  26. if(Obtenir('buscador')) {
  27. if ((busquedaUrlIE!= idHistorialAnteriorIE && idHistorialAnteriorIE> 0 )) {
  28. //caso entramos por cambio en id por IFRAME IE
  29. Obtenir('buscador').value=historial[busquedaUrlIE];
  30. }else {
  31. // caso normal, entramos por el hash de  la url superior FF3
  32. Obtenir('buscador').value=historial[busquedaUrl];
  33. }
  34.  
  35. Cargando();
  36. EnviarFormulari();
  37. }
  38. }else{
  39. //este caso es para cuando existe un cambio de url, pero ha sido provocado por nosotros, no es un boton atras! desactivamos el flag!
  40. busquedaLanzada=false;
  41. }
  42. }
  43.  
  44. // despues de lanzar o no la busqueda igualo el cambio de las urls.
  45. idHistorialAnterior=busquedaUrl;
  46. idHistorialAnteriorIE= busquedaUrlIE;
  47. }

Problematicas encontradas: Este sistema funciona al 100% en páginas totalmente AJAX, en el momento que pulsemos sobre un enlace para irnos a una página nuestra sin aplicación ajax! Pufff los navegadores borrán los historiales y no se comportará como queremos. Esto es una limitación. Pero en realidad tiene solucion. Ahora estamos guardando el historial en un Array en el cliente, se podría guardar ese arrya en el servidor y con ajax consultar lo que queremos darle. Asi, cuando se cambia de un link normal, se hace un bucle que va cargando las paginas vacias para volver a rellenar ese historial de navegación fictico que hemos montado.  Haber si tengo tiempo y hago el codigo para solucionar este error. Otra manera sería con cada petición guardar el historial en el fichero que atiende las peticiones ajax en el servidor, así tener el historial en el servidor en una sesion de php, por ejemplo...

Se podrían hacer infinidad de cosas vosotros mismos!

Para cualquier duda postear un comentario.

ejemplo funcionando en:

El buscador de  consumibles mas baratos de internet

FIN

Notas: el codigo se podría optimizar un poquito mas, pero como ya va, es sabado, estoy cansado y he perdido el tiempo contando, el genial sistema, si alguien lo mejora que nos lo envíe y lo colgaremos!

Espero sea de utilidad, yo he perdido mucho tiempo con clases que dicen controlarlo y son muy dificiles de utlizar o almenos de integrar y nunca se acaban de asimilar a lo que necesitaba...

Todo el contenido es integramente creado por mí en controlzeta.net Y aunque dejamos que pueda usarse bajo todos los fines, "exigimos" estaría bien que se haga mención al autor Cristian Martinez y se enlace a controlzeta.net!

licencia: Crative Commons

Etiquetas: , , , , , ,

14 comentarios para “Controlar el botón de atrás o botón adelante del Navegador en Javascript AJAX (Historial de navegación)”

  1. Javi dice:

    Esto no funciona bien, más que nada porque está un poco enrevesada la explicacion y tal.... Estaría bien que subieras el ejemplo funcionando para descargar.

    Es un buen trabajo.

  2. cristian dice:

    Hola Javier,

    Este código está probado y funcionando en el enlace que comentamos al final. Puede que exista algún error de sintaxis den Javascript, que haya cometido al copiarlo. Si ha detectado algun error en nuestra solución del botón atrás en Javascript. Sea tan amable de comunicárnoslo, que modificaremos el post.

    Se te ha enviado un correo explicativo.

    si nos enseñas el código que usas quizás podamos indicarle donde tiene el error.

  3. recordmedia dice:

    de fabula, me pongo a implementarlo en mi website en seguida. Es 100% ajax.

  4. Ivan Gallo dice:

    Hola cristina excelente trabajo, esto es precisamente lo que ando buscando, pero me puedes hacer un gran favor mi llave, de mandarme un ejemplo con la implementacion , de verdad lo estoy necesitando urgente

  5. claudio ivan dice:

    esto es lo q necesitaba serias tan amable de mandarme un ejemplo con la implemntacion por favor.Muchas gracias

  6. Ivan dice:

    Hola que tal, podrías poner el ejemplo que dices del bucle para llenar el historial después de regresar de una pagina normal? tengo ese problema, gracias.

  7. cristian dice:

    Hola,

    el código lo podeis obtener en sintoner.com...

    el ejemplo del bucle es un sencillo:

    for(i=0;0<historialArecuperar;i++)

    dentrodel bucle tan solo habria que ir cargando als urls que con el id del historial en el iframe o en la url para firefox... con eso es sucificiente...

    Yo no lo he probado, pero habria que estudiar que tal funciona.

  8. mrkents dice:

    Saludos,

    debo decir que esta es una muy buena forma de manejarlo por lo menos para mi que no uso cosas extrañas, sino más bien ajax normalito implementado por mi y este ejemplo lo pude adaptar a lo mio.

    Solo tuve 1 problema, no me funcionó con el script actual que tienes en el post, me tuve que copiar el controlador.js del ejemplo (http://sintoner.com) que es un poco distinto OJO, algunas cositas si lo compararas bien.

    Pero al final me funcionó!!, muchas gracias.

    Si a alguien no le funcionó y no entendió el ejemplo, podría publicar en mi blog como lo usé. claro, si cristian me permite.. y lógicamente con link de referencia.. este post.

  9. cristian dice:

    Puede ser que el código copiado varíe un poco.

    Por otro lado adelante, comparte en tu blog, citanos y pon enlace aquí en otro comentario que la gente puede seguir la pista. El conocimiento es para compartirlo.

    Por otro lado podíamos mirar que hay un error con la codificación de los accentos en safari para windows. Avísame si te pasa igual!

    saludos mrkents

  10. cristian dice:

    varia gente pregunta el ejemplo con la implementación pero es que lo tenéis en el código javascript de http://www.sintoner.com

    lo queréis todo echo, eh! :P

    saludos

  11. Sitio web con Ajax totalmente, y funcionando los botones de atrás y adelante del navegador – Ajax back button « Jorge García (mrkents), Blog’s dice:

    [...] que ha construido Cristian Martínez que tiene poblicado en su blog en el post “Controlar el botón de atrás o botón adelante del Navegador en Javascript AJAX (Historial de naveg...“, donde nos presenta de forma clara como lo ha hecho y un ejemplo del mismo funcionando (en [...]

  12. mrkents dice:

    Saludos,

    En la siguiente dirección tengo el ejemplo del uso que le he dado, espero les sirva.
    http://mrkents.wordpress.com/2010/03/09/sitio-web-con-ajax-totalmente-y-funcionando-los-botones-de-atras-y-adelante-del-navegador-ajax-back-button/

    Por ahora solo le hago seguimiento a firefox y a internet explorer.
    Cristian, muchas gracias.

  13. cristian dice:

    Como critica constructiva, no olvidar que Ajax debería ser un complemento para las webs así que al ejemplo, le añadiría links html normales, para que sin javascript siguiera funcionando la página (USABILIDAD).

    Añadir, que de cara a aplicaciones web (programas) al estilo gmail u google Docs, el javascrpt pasa a ser obligatorio, pues sin el no podríamos hacer nada.

    Por otro lado quisiera dar una recomendación, las peticiones ajax, suelen dar ciertos problemas como por ejemplo:
    * Algunas peticiones se pierden.
    * Dos peticiones consecutivas al ser asíncronas pueden llegar en orden aleatorio, primero la segunda y luego la primera, de tal forma que el usuario recibe algo que no espera.
    *Se hace necesario controlar peticiones perdidas, que hacer si una petición se pierde? volvemos a poner el contenido anterior, relanzamos la petición automáticamente pasados unos segundos?

    * Adicionalmente la respuesta si es html y es muy compleja ( tabla html con un listado de 10 productos) Al retardo de la petición hay que esperar el tiempo que el navegador sube a memoria dicho html, mas el tiempo de inserción del html en el document. Este proceso puede ser bastante lento, cuando la respuesta es un poco grande. puede tardar 15 segundos. Suficiente como para que tarde mas que refrescar toda la página entera (gracias a la cache de las imágenes)

    * Y por ultimo el gran problema, que pasa si solo un trozo de la web es ajax? que el sistema de historial, se pierde tras pulsar el primer enlace html en exploradores como Explorer 6,7,…

    Así que recomiendo que SÓLO SE USE SI REALMENTE ES NECESARIO…

    Por lo demás AJAX es perfecto, y para sistemas de votaciones,

  14. Rodrigo Espinoza dice:

    Hola:

    Revisé el sitio de ejemplo que pones http://www.sintoner.com todo bien como lo explicas… pero cuando presionas el botón de actualizar al parecer el script de ajax no maneja el evento de refresh porque vuelve a la pagina anterior (creo) o al menos es lo que me ocurrió siempre que lo probé... al parecer creo yo; que controlaste los botones de atrás y adelante pero te falto el de actualizar.
    Podrías incluir una solución para este evento tanto o más importante que los otros dos.

    saludos

Deja un comentario

CAPTCHA Image Audio Version
Reload Image