Front End

30 nov, 2011

Gerenciamento de histórico de verdade com HTML5

Publicidade

Incluídas na listagem de novas características que estão aparecendo no mundo de desenvolvimento da web com HTML5, há duas muito interessantes: Gerenciamento de histórico e evento hashchange. Essas duas características permitem aplicações JavaScript muito mais ricas e rápidas. Vamos começar com uma rápida visão do que elas proporcionam.

Hashchange

Esse evento é bem simples. Sempre que a propriedade window.location.hash mudar, ao seguir um link, definindo a propriedade, editando a URL ou navegando pelo histórico para voltar e avançar, o evento hashchange disparado no objeto window. Desse modo, usá-lo é realmente fácil:

window.onhashchange = function() { 
alert("hash changed!");
};
window.location.hash = Math.random(); // alerts "hash changed!"

Essa característica está implementada nas versões recentes de todos browsers mais importantes. Em browsers mais antigos, como internet Explorer 6 e 7, você pode facilmente incorporá-la polling a propriedade hash em um intervalo, e disparando manualmente um evento quando ele muda. Isso é fácil de construir em um plugin jQuery, coisa que Ben Alman fez no robusto jquery.hashchange.js plugin.

Gerenciamento do histórico

Esta característica é um pouco mais complexa. Os browsers que a suportam incluem um objeto window.history, com as seguintes propriedades:

  • window.history.back() e window.history.forward(), que proporcionam interfaces programáticas para as funções back() e forward() do browser. 
  • window.history.pushState(stateObj, title, url). Este método inclui uma nova entrada no histórico do browser, que se transforma então no estado atual do browser. Você pode fornecer qualquer JSON-stringifiable object para enviar juntamente com ele, e o browser irá fornecer o objeto novamente quando você navegar para esse ponto (mais sobre isso logo a seguir). Mais importante, se você fornecer uma URL, o browser mudará a URL mostrada na barra de endereços, sem recarregar a página. A mesma URL tem que ser do mesmo domínio, mas você pode mudar o resto, que são a window.location.pathname e window.location.hash. A mudança da URL dessa maneira não disparará um evento “hashchange”.
  • window.history.replaceState(stateObj, title, url). Isso equivale a window.history.pushState, exceto que o estado atual do browser é removido do histórico, de forma que você não pode retornar a ele com o “back”.
  • window.onpopstate. Este evento é disparado sempre que um state object é removido do histórico do browser, o que acontece ao usar o “back” e o “forward”. Os state objects permanecem no disco rígido entre uma sessão e outra, o que é uma característica interessante. O object que passou em uma chamada para pushState ou replaceState é fornecido como a propriedade state no evento object dentro do evento “popstate”.

Este recurso está implementado nas últimas versões do WebKit, o que inclui o Safari, o Chrome e o Firefox.

Quando usar?

O novo gerenciamento de histórico é muito promissor, porque ele permite que aplicativos web vivam através de muitas URLs físicas, rodando em uma única instância. Isso é importante para alguns tipos de aplicativos, que não têm uma boa experiência ao usar hash.

Por exemplo, no Twitter, atualizamos a URL hash ao navegar pelo aplicativo, para que cada página tenha uma URL específica como http://twitter.com/#replies. Contudo, forçamos um carregamento completo de certas páginas, principalmente páginas de perfis (por exemplo, http://twitter.com/bcherry) e páginas de permalink (por exemplo, http://twitter.com/bcherry/status/18966802499). Isso é para que essas URLs possam ser copiadas da barra de endereços e postadas na web.

Queremos ter certeza de que usuários sem JavaScript e bots de mecanismos de busca enviando links para nosso site obterão a página correta do servidor (desde que o browser não mande junto um hash para o servidor). Isto não seria possível se essas URL usassem hashes. Infelizmente, isso significa um aplicativo mais lento por ser necessário um carregamento completo da página para fora e para dentro dessas locações.

Aqui é que o gerenciamento do histórico do HTML5 pode ser útil.

Então, qual é o problema?

Infelizmente, as implementações existentes de gestão de histórico não são úteis, e não estão no espírito da web.

Nossos aplicativos web devem ser construídos para responder a uma URL. As versões tanto para o cliente quanto para o servidor de um aplicativo devem entender uma estrutura URL compartilhada, e saber como apresentar a mesma página para o browser que reflita aquela URL.

A permissão para que desenvolvedores armazenem informações extras no histórico do browser desvia-se do foco. A única coisa a guardar no histórico deveria ser a URL, e o browser pode associar um título a ela se assim escolher.

Esse é um modelo compatível com REST, que funciona da mesma maneira tanto do lado do cliente quando do servidor. Browsers modernos suportam mudar a URL sem recarregar a página, enquanto os mais antigos continuam a acessar o servidor o tempo todo.

Dessa forma, podemos construir aplicativos que se degradam corretamente em browsers mais antigos e que, quando percebidos por bots, proporcionem uma experiência mais rápida para usuários com browsers mais modernos.

Insira “pathchange”

Tanto “hashchange” quanto pushState/”popstate” devem ser substituídos  por “pathchange”,  que é um evento disparado quando a URL tem qualquer mudança.

Esse evento não fornece nenhuma informação, e o aplicativo deve inspecionar a URL atual para descobrir o state em que deve entrar. Links relacionados não devem forçar o recarregamento da página, devendo ao invés disso disparar o evento “pathchange”.

Assim é possível implementar esse evento em browsers modernos, com base nas características que eles já têm. Aí vai como:

  1. Escute o “hashchange”, e dispare o “pathchange” quando ele ocorrer.
  2. Analise se o navegador não suporta “hashchange” e dispare o “hashchange”, com gatilhos “pathchange”.
  3. Com suporte do histórico, escute o “popstate” e dispare o “pathchange” quando ele ocorrer.
  4. Com suporte do histórico, intercepte todos os links relacionados ao serem clicados, e impeça a navegação normal. Em vez disso, chame window.history.pushState(null, null, href) e dispare um “pathchange”.
  5. Providencie uma função helper para navegar para novas URLs e facilitar, quando suportado, o uso da window.history.pushState.

Implementei isso tudo como um plugin jQuery que é muito fácil de usar:

$(function() { 
$.pathchange.init(); // setup event listeners, etc.
$(window).pathchange(function() {
respondToUrl();
}).trigger("pathchange");

$.pathchange.changeTo("/foo");
});

Também criei uma página demo que apresenta um gerenciamento de histórico para aplicativos HTML5 que usa jquery.pathchange.js de forma subjacente. Cheque em vários browsers para ver a mágica do HTML5 trabalhando, certificando-se de usar os botões “back” e “forward”, e de recarregar a página algumas vezes.

Esta é minha opinião quanto aos recursos do histórico do HTML5. Alguns browsers ainda não estão implementando o que realmente precisamos, mas é encorajador que forneçam o necessário para implementar o que realmente precisamos.

Nota – Vale a pena ressaltar que descobri um bug sério na implementação WebKit de gestão do histórico. Resumidamente, o evento “popstate” é frequentemente perdido quando a rede está ocupada, o que não faz sentido. Veja aqui uma página demo com um caso reproduzível, que dispara uma solicitação de download de uma imagem que gasta 1s em cada “popstate”, o que significa que acionar “back” mais de uma vez a cada segundo leva à perda de entradas no histórico, e a um aplicativo que perde a sincronização com a URL. Você pode contornar isso apurando a URL e, adicionalmente, ouvindo o “popstate”, mas não é uma boa prática. Até que isso seja sanado, você deve ficar alerta quanto a isso no caso de fornecer essa característica a seus usuários, que provavelmente não é adequada para apps AJAX muito complexas.

?

Texto original disponível em: http://www.adequatelygood.com/2010/7/Saner-HTML5-History-Management