Procurar

Categorias

Posts recentes

Arquivo

Blog feeds

Bookmarks


Histórico do browser para navegação via Ajax
quarta, 15 de novembro de 2006

Pesquisei e quebrei a cabeça para achar essa solução… tanto que questionei entre dar apenas o ‘mapa’ ou o script pronto. Mas sou um cara open-source por natureza, por isso vou fazer as duas coisas.

Bem, todos já sabemos que comandos em javascript não ativam os botões de histórico do navegador, mesmo que mudem 100% do conteúdo exibido. Isso não é legal, pois sabemos que o usuário busca usar os recursos que conhece e o botão ‘Voltar’ é uma instituição, existe desde que o mundo é mundo. Eu me sentia meio que ‘na obrigação’ de possibilitar isso ao usuário.

Pensei então, há alguns meses em uma solução - que me serviu no momento, mas que não ativava os botões, apenas criava um ‘histórico de comandos’ num array, com funções para adicionar, exibir e testar itens do histórico. Com isso eu fazia os meus próprios botões de histórico. Achei que fosse suficiente, mas hoje vejo que não, pois na maior parte dos casos o usuário nem entende que há um histórico ‘paralelo’ e que ele precisa clicar em outros botões para navegar…
Veja o script original:

var _ajax = {}; // o obj. _ajax era usado para outros fins…
// histórico de comandos - 2006-06-07
_ajax.history = {
‘items’: [],
‘currentIndex’: 0,
‘go’: function(ref) {
if(_ajax.history.items[_ajax.history.currentIndex + ref] false)
_ajax.history.currentIndex += ref;
else return;
new Function(_ajax.history.items[_ajax.history.currentIndex])();
},
‘add’: function (str) {
if(str == _ajax.history.items[_ajax.history.currentIndex]) return;
_ajax.history.currentIndex = _ajax.history.items.length;
_ajax.history.items.push(str);
},
‘hasBack’: function() {
return (_ajax.history.items[_ajax.history.currentIndex - 1] || false) ? true : false;
},
‘hasForward’: function() {
return (_ajax.history.items[_ajax.history.currentIndex + 1] || false) ? true : false;
}
};

Caí de cabeça atrás da solução, que eu já sabia, tinha algo a ver com a propriedade hash do objeto location. Descobri algumas possibilidades, a começar pelos famosos frameworks (que eu abomino…), algumas funções interessantes que só funcionavam em Mozilla, outras gigantescas demais para o meu gosto. Tentei então entender como as coisas eram feitas e fazer eu mesmo. Este é sempre o método mais enriquecedor (mas nem sempre é o mais eficiente… é bom lembrar -rsrsrs).

Entendendo

O objeto window.location tem uma propriedade chamada ‘hash’, que mostra ou define aquela parte da URL que vem no final, depois do caracter ‘#’ - que usamos para navegar para diferentes pontos na mesma página, com, por ex.:

<a name="meu_hash"></a> e chamamos com: <a href="#meu_hash">meu_hash</a>

Pois é, ele é a chave para o histórico do navegador, pois ao mudar o hash, mudamos a URL. Então se ao executar um comando, eu mudar o hash o navegador armazena como uma nova página? A resposta é não. Se não há um clique explícito do usuário em um link como o descrito acima (com um A NAME="…"), ao clicar no botão Voltar, nada acontece! O IE nem habilita o botão!!! Bem, mas a URL está diferente e é isso que vamos usar. Usaremos uma variável para armazenar o hash e um setInterval para testá-lo a intervalos regulares. Dê uma olhada nesse link: http://juliogreff.wordpress.com/2006/10/20/ativando-o-botao-voltar/.

Com o script que o Júlio mostra, resolve-se a questão. Mas resta um problema: o IE não habilitou o botão voltar apenas com a mudança do hash… Mais pesquisas e mais pesquisas para achar a solução, que quando foi encontrada me pareceu tão óbvia que não sei por que não pensei nisso antes (sempre pensamos isso, né?). Quando carregamos um frame ou iframe o IE cria um novo item no histórico! Veja a página de onde tirei a idéia: http://www.contentwithstyle.co.uk/Articles/38/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps.
Esse exemplo é crossbrowser, mas muito complicado. Serviu para esclarecer, mas não tive vontade de usá-lo.

Bem, baseado nesses scripts resolvi criar um terceiro, fundindo com o que já tinha pronto (mostrado acima) criei o objeto ajaxHistory. Vou falar sobre a lógica que usei antes de mostrar o script. Basicamente preservei os métodos e propiedades do antigo objeto e acrescentei novos métodos para permitir o uso do objeto history do navegador.

Em nosso objeto haverá um array para armazenar os comandos (como strings) chamado ajaxHistory.items e um inteiro que indica o índice do item exibido no momento, ajaxHistory.currentIndex. O sistema original funciona da forma mais simples possível. A cada comando que modifique o conteúdo da página, executamos ajaxHistory.add enviando uma string que se executada como comando reproduz a operação que modificou a página. Depois podemos usar os métodos ajaxHistory.go ou ajaxHistory.goTo para navegar pelos itens do nosso ‘histórico de comandos’. Além do método goTo, o array ajaxHistory.captions foi adicionado, possibilitando ajustar o título da página de acordo com o item exibido. Aí podemos usar ajaxHistory.hasBack e ajaxHistory.hasForward para testar se há itens atrás ou adiante do atual e assim habilitar ou desabilitar nossos botões personalizados de histórico.

Ok, mas e para usar os benditos botões de histórico do browser? Para isso vários métodos foram criados, mas para executar, basta chamar o método ajaxHistory.activateBrowserHistory no onload da página, uma única vez. Isso dispara uma série de eventos que em conjunto habilitam o histórico do navegador. Vamos a eles.

A lógica e a execução da coisa são diferentes para Mozilla e IE (novidade…).

Em ambos os casos o hash é modificado ao criar um novo item ou ao carregar um item do histórico (ajaxHistory.setHash).

No Mozilla criamos um setInterval chamando o método ajaxHistory.checkHash a cada 1/10 de seg. testando se o hash é igual a ajaxHistory.currentIndex, se for diferente, o usuário clicou no botão voltar (ou avançar). O hash é setado com o valor do índice do item no array ajaxHistory.items, então ajaxHistory.goTo é chamado com o hash como parâmetro.

No IE um iframe é inserido na página, com display:none. Este iframe carrega o arquivo ‘historyframe.html’ que deve ser criado como descrito abaixo:

<html><head><script type="text/javascript" language="javascript">
function checkHistory() {
var pi = parent.location.hash.replace(/#/,'’), si = location.href.replace(/^.+\?/,'’);
if(si && pi && (parent.ajaxHistory || false))
if(pi != si) parent.ajaxHistory.goTo(si);
}
</script></head><body onLoad="checkHistory();"></body></html>

No caso do IE, ajaxHistory.setHash recarrega o iframe enviando na query string o valor do índice, assim: ‘historyframe.html?2′, por exemplo. O arquivo ‘historyframe.html’ ao ser carregado testa o índice enviado na URL com o hash na URL da janela principal, se forem diferentes, ajaxHistory.goTo é chamado com o índice enviado na URL como parâmetro.

O Mozilla, ao contrário do IE, habilita os botões mas não há ação mesmo quando o iframe é recarregado, portando os dois métodos foram necessários. Bem, lá vai o script:

/**
* ajaxHistory - histórico de comandos para navegação por Ajax
* ———————————————–
* autor: Cau Guanabara
* data: 2006-06-07 :: 2006-11-07
* ———————————————–
*/

var ajaxHistory = {
‘items’: [],
‘captions’: [],
‘currentIndex’: 0,
‘go’: function(ref) {
if(this.items[this.currentIndex + ref] || false) this.currentIndex += ref;
else return;
if(this.activeHistory) this.setHash();
if(this.captions[this.currentIndex]) document.title = this.captions[this.currentIndex];
new Function(this.items[this.currentIndex])();
},
‘goTo’: function(ind) {
if(this.items[ind] || false) this.currentIndex = ind;
else return;
if(this.activeHistory) this.setHash();
if(this.captions[this.currentIndex]) document.title = this.captions[this.currentIndex];
new Function(this.items[this.currentIndex])();
},
‘add’: function(str, cap) {
if(str == this.items[this.currentIndex]) return false;
this.currentIndex = this.items.length;
this.items.push(str);
this.captions.push(cap || false);
if(this.activeHistory) this.setHash();
return true;
},
‘hasBack’: function() { return (this.items[this.currentIndex - 1] || false) ? true : false; },
‘hasForward’: function() { return (this.items[this.currentIndex + 1] || false) ? true : false; },
‘activeHistory’: false,
‘activateBrowserHistory’: function() {
if(document.all) this.makeIframe();
else setInterval("ajaxHistory.checkHash()", 100);
this.activeHistory = true;
},
‘makeIframe’: function() {
this.iframe = document.createElement(’iframe’);
this.iframe.style.display = ‘none’;
this.iframe.src = ‘historyframe.html’;
document.getElementsByTagName(’body’)[0].appendChild(this.iframe);
},
‘checkHash’: function() {
var curHash = location.hash.replace(/#/,'’);
if(this.currentIndex != curHash) this.goTo(curHash);
},
’setHash’: function() {
top.location.hash = this.currentIndex;
if(document.all)
this.iframe.contentWindow.location.href = ‘historyframe.html?’+this.currentIndex;
}
};

Note que este objeto não deve ser inicializado com o operador new, basta chamar os métodos diretamente. Veja abaixo um exemplo de uso do sistema (os comandos no exemplo não mudam o conteúdo da pagina, mas isso não faz a menor diferença - qualquer comando javascript é válido)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript" language="javascript" src="ajax_history.js"></script>
<script type="text/javascript" language="javascript">
function request(command, caption) {
ajaxHistory.add(command, caption);
document.title = caption;
new Function(command)();
}
</script>
</head>

<body onload="ajaxHistory.activateBrowserHistory();">
Clique nos links e depois navegue pelo histórico<br /><br />
<p><a href="javascript://" onclick="request(’alert(\’link 1\’);’,'link 1′)">link 1</a><br />
<a href="javascript://" onclick="request(’alert(\’link 2\’);’,'link 2′)">link 2</a><br />
<a href="javascript://" onclick="request(’alert(\’link 3\’);’,'link 3′)">link 3</a> </p>
</body>
</html>

Espero que façam proveito das informações contidas aqui. É isso aí.

10 Comentários »

Feeds para os comentários nesse post:
TrackBack: http://cauguanabara.blogsome.com/2006/11/15/historico-do-browser-para-navegacao-via-ajax/trackback/

  1. não consegui fazer rodar o código… diz que ajaxHistory não esta definido. Pode me ajudar?

    Por Cadu em 2006-11-15 09:48:18, quarta

  2. O sistema do worpress modifica as aspas por acentos ou sei lá o que… será preciso substituir as aspas. Deve ser isso

    Por Cau Guanabara em 2006-11-15 09:48:18, quarta

  3. Não consegui, já fiz a troca e testei… nada!
    Devo estar me confundindo na hora de aspas duplas e simples…
    Tem como colocar pra download ou mandar por email?
    Obrigado!

    Por Cadu em 2006-11-15 09:48:18, quarta

  4. Coloquei para download aqui:
    http://cauguanabara.jsbrasil.com/scripts/ajax_history.js

    Por Cau Guanabara em 2006-11-15 09:48:18, quarta

  5. agora funcionou redondo! Valeu pela ajuda, obrigado!
    Abraços!

    Por Cadu em 2006-11-15 09:48:18, quarta

  6. Olá…

    Estou precisando bastante de uma solção para para esse problema.
    Baixei o código da sua solução e coloquei seguinte trexo:

    … onclick=”request(’document.getElementById(’teste’).innerHTML = ‘xxxxxx’;',’teste’);” …

    ele altera a url ai eu avanço de página quando volto a url lá é a mesma mais não aparece o conteudo.

    o que aconteçe ???

    Obrigado.

    Por Marcelo em 2006-11-15 09:48:18, quarta

  7. No Firefox esta funcionando no IE não.
    Alguma dica ?

    Obrigado.

    Por Marcelo em 2006-11-15 09:48:18, quarta

  8. Comigo só funcionou no FF… se alguem souber pq me ajudem por favor…

    Por Fernando em 2006-11-15 09:48:18, quarta

  9. Coloquei um exemplo de funcionamento no ar:
    http://cauguanabara.jsbrasil.com/scripts/historybuttons/

    Testado em IE7 e FF2

    Por Cau Guanabara em 2006-11-15 09:48:18, quarta

  10. Parabéns pelo dica!
    Realmente sensacional!

    O único probleminha é que no IE o script não carrega as imagens da div… no FF funciona perfeitamente.
    Como eu poderia fazer pra arrumar esse bug no código?

    Brigadão!

    Por Simone em 2006-11-15 09:48:18, quarta

Say something! »















Por favor digite o texto da imagem acima.

lamp! Mapa do site
Histórico do browser para navegação via Ajax