Você está aqui: Home ‣ Dive Into HTML5 ‣
❧
Armazenamento local persistente é uma das áreas onde aplicações nativas de cliente têm mantido uma vantagem sobre aplicações web. Para aplicações nativas, o sistema operacional tipicamente provê uma camada de abstração para armazenar e recuperar dados específicos do aplicativo como preferências ou estado de tempo de execução. Estes valores podem ser armazenados no registro, arquivos INI, arquivos XML ou algum outro lugar de acordo com a convenção da plataforma. Se o seu aplicativo cliente nativo necessita de armazenamento local diferente dos pares chave/valor, você pode inserir o seu próprio banco de dados, inventar o seu próprio formato de arquivo ou qualquer número de outras soluções.
Historicamente, aplicações web não tiveram nenhum desses luxos. Cookies foram inventados no início da história da web e, de fato, eles podem ser usados para armazenamento local persistente de pequenas quantidades de dados. Mas eles têm três desvantagens potencialmente [potentially dealbreaking downsides]:
O que nós realmente queremos é
Antes da HTML5, todas as tentativas de obtenção foram fundamentalmente insatisfatórias de diferentes maneiras.
❧
No início, existia apenas o Internet Explorer. Ou ao menos, era isso que a Microsoft gostaria que o mundo acreditasse. Para esse fim, como parte da Primeira Grande Guerra de Browsers, a Microsoft inventou uma grande quantidade de coisas e as incluiu no seu "vencedor de todas as batalhas", o Internet Explorer. Uma dessas coisas foi conhecida como DHTML Comportamentos, e um desses comportamentos foi conhecido como userData.
userData
possibilita que páginas web armazenem até 64KB de
dados por domínio, em uma estrutura hierárquica baseada em XML. (Domínios
confiáveis, tais como sites de intranet, podem armazenar 10 vezes esta
quantidade. E,
640 KB deveria ser o suficiente para qualquer um.) O IE não apresenta qualquer forma de diálogo de permissões e não há
previsão para o aumento da quantidade de armazenamento disponível.
Em 2002, a Adobe introduziu uma funcionalidade no Flash 6 que ganhou o
infeliz e enganoso nome de “Flash cookies.” Dentro do ambiente do Flash, a
funcionalidade é propriamente conhecida como
Local Shared Objects - Objetos de local compartilhado. Resumidamente, isso permitia que objetos Flash armazenassem até 100KB
de dados por domínio. Brad Neuberg desenvolveu um pré protótipo de uma
ponte Flash-to-JavaScript chamada
AMASS
(AJAX Massive Storage System), porém ficou limitado por algumas
peculiaridades de design do Flash. Em 2006, com o advento da
ExternalInterface
presente no Flash 8, acessar LSOs a partir do JavaScript
tornou-se uma coisa de magnitude mais fácil e rápida. Brad reescreveu o
AMASS e o integrou dentro do popular
Dojo Toolkit com o nome de
dojox.storage
. Flash garante a cada domínio 100 KB de armazenamento “livre.” Além
disso, ele solicita ao usuário para cada ordem de magnitude um aumento no
armazenamento de dados (1 Mb, 10 Mb, e assim por diante).
Em 2007, o Google lançou o Gears, um plugin de código aberto com o objetivo de providenciar capacidades adicionais nos navegadores. (Nós já tínhamos discutido sobre Gears no contexto de providenciar uma API de geolocalização para o Internet Explorer.) Gears possibilita uma API para uma base de dados SQL icorporada baseada em SQLite. Após obter a permissão de um usuário uma vez, Gears pode armazenar ilimitada quantidade de dados por um domínio em uma tabela de banco de dados SQL.
Entretanto, Brad Neuberg e outros continuaram a trabalhar em cima do
dojox.storage
para providenciar uma interface unificada para
todos esses diferentes plugins e APIs. Em 2009,
dojox.storage
podia auto-detectar (e providenciar uma
interface unificada em cima de) Adobe Flash, Gears, Adobe AIR, e um pré
protótipo da HTML5 storage que era apenas implementado em
antigas versões do Firefox.
Quando você analisa essas soluções, um padrão surge: todos eles são
específicos de um único navegador, ou dependentes de plugins de terceiros.
Apesar do esforço heróico para deixar clara as diferenças (no
dojox.storage
), eles todos apresentam interfaces totalmente
diferentes, têm diferentes limitações de armazenamento, e apresentam
diferentes experiências de usabilidade. Portanto, esse é o problema que a
HTML5 se propõe a resolver: providenciar uma padronizada
API, implementada de forma nativa e consistente em vários
navegadores, sem a necessidade e dependência de plugins de terceiros.
❧
O que me refiro como “HTML5 Storage” é a especificação nomeada de Web Storage, que estava na especificação da HTML5 por um tempo, mas era tratado separadamente por desinteresse e razões políticas. Certos navegadores se referenciavam como “Local Storage” ou “DOM Storage.” A nomeação ficava ainda mais complicada em navegadores relacionados com padrões emergentes, que vou discutir mais adiante neste capítulo.
Então o que é HTML5 Storage? Simplesmente armazenar, é a forma de páginas da web amazenarem pares de chave/valor localmente, dentro do navegador do cliente. Semelhante aos cookies, esses dados persistem mesmo depois de você sair do site, fechar a guia ou o navegador. Diferente dos cookies, esses dados nunca são transmitidos ao servidor (a menos que você queria enviá-los manualmente). Diferente de todas as tentativas anteriores de fornecimento de armazenamento local persistente, este é implementado de forma nativa nos navegadores, para que esteja disponível mesmo quando os plugins de terceiros não estiverem.
Quais navegadores? Bom, as últimas versões de quase todos os navegadores suportam HTML5 Storage… até o Internet Explorer!
IE | Firefox | Safari | Chrome | Opera | iPhone | Android |
---|---|---|---|---|---|---|
8.0+ | 3.5+ | 4.0+ | 4.0+ | 10.5+ | 2.0+ | 2.0+ |
Direto do seu código JavaScript, você acessa o HTML5 Storage
através do objeto localStorage
no objeto global
window
. Antes de usá-lo, você deve
detectar se o navegador tem suporte.
↶ Verificando suporte HTML5 Storage
function supports_html5_storage() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
}
Ao invés de escrever essa função, você pode usar Modernizr para detectar o suporte da HTML5 Storage.
if (Modernizr.localstorage) {
// window.localStorage é suportado pelo seu navegador!
} else {
// não há suporte nativo ao HTML5 storage no seu navegador! :(
// tente talvez dojox.storage ou alguma solução de terceiros
}
❧
HTML5 Storage é baseado em pares de chave/valor nomeadas.
Você pode armazenar dados em uma chave nomeada, e com essa mesma chave,
recuperar os dados armazenados. A chave nomeada é uma string. E seu valor,
é qualquer um suportado pelo JavaScript, incluindo Strings, Booleanos,
Inteiros, ou Flutuantes. Conteúdo, os dados são armazenados como uma
sequência de strings. Se você estiver armazenando e recuperando qualquer
outro tipo de dado diferente de Strings, você precisa usar funções como
parseInt()
ou parseFloat()
para recuperar os
dados em um tipo esperado pelo JavaScript.
interface Storage {
getter any getItem(in DOMString key);
setter creator void setItem(in DOMString key, in any data);
};
Chamando setItem()
com o nome de uma chave que já existe, ela
sobrescreve o valor anterior. Chamando getItem()
com o nome
de uma chave que não existe, retornará null
ao invés de de
uma exceção.
Como outros objetos JavaScript, você pode tratar o objeto
localStorage
como um array associativo. Ao invés de usar os
métodos getItem()
e setItem()
, você pode usar
simplesmentes colchetes. Por exemplo, no trecho seguinte:
var foo = localStorage.getItem("bar");
// ...
localStorage.setItem("bar", foo);
…pode ser reescrito para usar a sintaxe de colchetes ao invés:
var foo = localStorage["bar"];
// ...
localStorage["bar"] = foo;
Existem métodos para remover um valor já dado a uma chave, e limpar o armazenamento local inteiro (isto é, apagar todas as chaves/valor existentes de uma só vez).
interface Storage {
deleter void removeItem(in DOMString key);
void clear();
};
Chamando removeItem()
com uma chave inexistente, não
retornará nada.
Finalmente, há uma propriedade para obter o número total de chaves/valor na área de armazenamento local, e também, para percorrer todas as chaves pelo seu índice (e retornar o nome de cada uma).
interface Storage {
readonly attribute unsigned long length;
getter DOMString key(in unsigned long index);
};
Se você chamar key()
com um índice que não está entre
0–(length
-1), a função retornará null
.
Se quiser manter o controle das constantes mudanças no armazenamento
local, você pode capturar o evento storage
. O evento
storage
é ativado no objeto window
sempre que
setItem()
, removeItem()
, ou
clear()
é chamado e, geralmente, muda alguma coisa.
Por exemplo, se você definir um valor a uma chave existente ou chamar
clear()
em chaves não nomeadas, o evento
storage
não será chamado, porque realmente, nada mudou na
área de armazenamento local.
O evento storage
é suportado em todos os lugares em que o
objeto localStorage
funciona, incluindo Internet Explorer 8.
IE 8 não suporta o padrão W3C addEventListener
(será
adicionado, finalmente, no IE 9). Portanto, para pegar o evento
storage
, você vai precisar checar qual mecanismo de evento o
navegador suporta. (Se você já fez isso antes, para outros eventos, pode
pular essa seção. Pegando o evento storage
, funciona da
mesma forma como todos os outros eventos, você estará sempre preso. Se
você preferir usar jQuery ou qualquer outra biblioteca JavaScript para
registrar os eventos de manipulação, você pode fazer isso com o evento
storage
, também.)
if (window.addEventListener) {
window.addEventListener("storage", handle_storage, false);
} else {
window.attachEvent("onstorage", handle_storage);
};
A função de callback handle_storage
é chamada junto ao objeto
StorageEvent
, exceto no Internet Explorer que os objetos de
evento são armazenados no window.event
.
function handle_storage(e) {
if (!e) { e = window.event; }
}
Nesse ponto, a variável e
será o objeto
StorageEvent
, que tem as seguintes propriedades:
Propriedade | Tipo | Descrição |
---|---|---|
key |
string | o nome da chave que foi adicionada, removida ou alterada |
oldValue |
qualquer |
o valor antigo (agora atualizado), ou null se for um
novo item adicionado
|
newValue |
qualquer | o novo valor, ou null se um item foi removido |
url * |
string | a página que chamou o método que realizou a mudança |
* Nota: a propriedade url é originalmente chamada de
uri . Alguns navegadores lançaram essa propriedade antes
das mudanças na especificação. Para uma compatibilidade máxima, você
deve checar se a propriedade url existe, se não, checar
se a propriedade uri existe.
|
O evento storage
não é cancelável. Dentro da função callback
handle_storage
, não há maneira de parar essa mundaça. É uma
simples forma do navegador dizer, “ei, isso já aconteceu. Não há nada que
você possa fazer, eu só queria te avisar sobre isso.”
Ao falar sobre
a história dos hacks no armazenamento local usando
plugins de terceiros, fiz questão de mencionar a limitação de cada
técnica, tal como limite de armazenamento. Apenas percebi que não tinha
mencionado nada sobre as limitações do agora padronizado armazenamento da
HTML5. Vou dar a vocês as respostas primeiro, em seguida
explicarei elas. As respostas, em ordem de importância, são “5 megabytes,”
“QUOTA_EXCEEDED_ERR
,” e “Não.”
“5 megabytes” é a quantidade em espaço que cada origem tem por padrão. Isto é surpreendentemente consistente em todos os navegadores, embora seja formulada como não mais do que uma sugestão na especificação do Armazenamento local da HTML5. Um aspecto a ter em mente é que você está armazenando strings, não as informações no seu formato original. Se você está armazenando muitos inteiros ou floats, a diferença de representação pode realmente aumentar. Cada dígito naquele float está sendo armazenado como um caractere, não na representação usual de um ponto de número flutuante.
“QUOTA_EXCEEDED_ERR
” é a exceção que será lançada se você
exceder sua cota de armazenamento de 5 megabytes. “Não” é a resposta para
a próxima questão óbvia, “Eu posso pedir permissão do usuário para usar
mais espaço de armazenamento?” No momento em que escrevo (fevereiro 2011),
não há navegador que suporte qualquer mecanismo para desenvolvedores web
requisitarem mais espaço de armazenamento. Alguns navegadores (como Opera) permitem que o usuário controle a cota de armazenamento de cada site,
mas isto é uma ação puramente iniciada pelo usuário, não algo que você
como desenvolvedor pode construir na sua aplicação web.
❧
Vamos ver o Armazenamento da HTML5 em ação. Recorde o jogo Halma que nós construímos no capítulo do canvas. Existe um pequeno problema com o jogo: se você fechar o navegador no meio do jogo, você perderá seu progresso. Porém, com o armazenamento da HTML5, podemos salvar o progresso localmente, dentro do próprio navegador. Aqui está uma demonstração no ar. Faça alguns movimentos, depois feche a aba do navegador, em seguida reabra ela. Se o seu navegador suporta Armazenamendo da HTML5, a página de demonstração deve magicamente recordar a sua exata posição dentro do jogo, incluindo o número de movimentos que você fez, a posição de cada uma das peças do tabuleiro, e até mesmo se uma determinada peça está selecionada.
Como isto funciona? Cada momento que uma mudança acontece dentro do jogo, chamamos esta função:
function saveGameState() {
if (!supportsLocalStorage()) { return false; }
localStorage["halma.game.in.progress"] = gGameInProgress;
for (var i = 0; i < kNumPieces; i++) {
localStorage["halma.piece." + i + ".row"] = gPieces[i].row;
localStorage["halma.piece." + i + ".column"] = gPieces[i].column;
}
localStorage["halma.selectedpiece"] = gSelectedPieceIndex;
localStorage["halma.selectedpiecehasmoved"] = gSelectedPieceHasMoved;
localStorage["halma.movecount"] = gMoveCount;
return true;
}
Como você pode ver, ela usa o objeto localStorage
para salvar
se há um jogo em progresso (gGameInProgress
, um booleano). Se
assim for, percorre as peças (gPieces
, um
Array
JavaScript) e salva o número de linha e coluna de cada
uma. Em seguida ele salva alguns estados adicionais do jogo, incluindo a
peça que está selecionada (gSelectedPieceIndex
, um inteiro),
se a peça está no meio de uma série potencialmente longa de saltos
(gSelectedPieceHasMoved
, um booleano), e o número total de
movimentos feito até então (gMoveCount
, um inteiro).
No carregamento da página, em vez de chamar automaticamente uma função
newGame()
que resetaria estas variáveis para valores
"hard-coded", chamamos uma função resumeGame()
. Usando
Armazenamento HTML5, a função resumeGame()
checa
se um estado relacionado ao jogo em progresso está armazenado localmente.
Se está, ele recupera esses valores usando o objeto do
localStorage
.
function resumeGame() {
if (!supportsLocalStorage()) { return false; }
gGameInProgress = (localStorage["halma.game.in.progress"] == "true");
if (!gGameInProgress) { return false; }
gPieces = new Array(kNumPieces);
for (var i = 0; i < kNumPieces; i++) {
var row = parseInt(localStorage["halma.piece." + i + ".row"]);
var column = parseInt(localStorage["halma.piece." + i + ".column"]);
gPieces[i] = new Cell(row, column);
}
gNumPieces = kNumPieces;
gSelectedPieceIndex = parseInt(localStorage["halma.selectedpiece"]);
gSelectedPieceHasMoved = localStorage["halma.selectedpiecehasmoved"] == "true";
gMoveCount = parseInt(localStorage["halma.movecount"]);
drawBoard();
return true;
}
A parte mais importante desta função é a ressalva que mencionei no início
deste capítulo, que vou repetir aqui:
Informações são armazenadas como string. Se você está armazenando algo
diferente de string, você vai precisar forçar uma conversão quando você
recuperá-la.
Por exemplo, a bandeira para verificar se existe um jogo em progresso
(gGameInProgress
) é um booleano. Na função
saveGameState()
, nós apenas armazenamos e não nos preocupamos
em relação ao datatype:
localStorage["halma.game.in.progress"] = gGameInProgress;
Pórem, na função resumeGame()
, precisamos tratar o valor que
pegamos da área de armazenamento local como uma string e construir
manualmente o valor booleano apropriado:
gGameInProgress = (localStorage["halma.game.in.progress"] == "true");
Semelhantemente, o número de movimentos está armazenado no
gMoveCount
como um inteiro. Na função
saveGameState()
, apenas armazenamos:
localStorage["halma.movecount"] = gMoveCount;
Mas na função resumeGame()
, precisamos forçar o valor para um
inteiro, usando a função nativa do JavaScript parseInt()
:
gMoveCount = parseInt(localStorage["halma.movecount"]);
❧
Enquanto o passado é cheio de truques e soluções alternativas, a condição presente do Armazenamendo da HTML5 é surpreendentemente otimista. Uma nova API vem sendo padronizada e implementada em todos os principais navegadores, plataformas e dispositivos. Como um desenvolvedor web, isto não é algo que você vê todo dia, não é verdade? Porém, há mais vida além dos “5 megabytes de pares de chaves/valores nomeados” e o futuro do armazenamento local persistente é…como poderei colocá-lo… bom, existem visões conflitantes.
Uma visão é uma sigla que você provavelmente já sabe: SQL. Em 2007, a Google lançou Gears, um plugin de código aberto que incluía um banco de dados incorporado com base de dados no SQLite. Esse protótipo inicial influenciou a criação da especificação do Web SQL Database. Web SQL Database (formalmente conhecida como “WebDB”) fornece uma fina camada em torno de uma base de dados SQL, permitindo você fazer coisas como essa pelo JavaScript:
↶código atual funcionando em 4 navegadores
openDatabase('documents', '1.0', 'Local document storage', 5*1024*1024, function (db) {
db.changeVersion('', '1.0', function (t) {
t.executeSql('CREATE TABLE docids (id, name)');
}, error);
});
Você pode ver, maior parte da ação reside na sequência que você passa para
o método executeSql
. Esta string pode ser qualquer comando
suportado pelo SQL, incluindo SELECT
,
UPDATE
, INSERT
and DELETE
. É como
programação de base de dados backend, exceto o fato de você está fazendo
isto via JavaScript! Oh alegria!
A especificação da Base de Dados SQL vem sendo implementada por quatro navegadores e plataformas.
IE | Firefox | Safari | Chrome | Opera | iPhone | Android |
---|---|---|---|---|---|---|
· | · | 4.0+ | 4.0+ | 10.5+ | 3.0+ | 2.0+ |
Claro, se você já usou mais de um produto de banco de dados em sua vida, você está ciente que “SQL” é mais um termo de marketing do que um rígido e rápido padrão. (Alguns podem dizer o mesmo da “HTML5,” mas não se importe.) Claro, existe uma especificação atual do SQL (ela é chamada de SQL-92), mas não há nenhuma base de dados do servidor no mundo que está em conformidade com esta e apenas esta especificação. Existe o SQL da Oracle, SQL da Microsoft, SQL do MySQL, SQL do PostgreSQL e SQL do SQLite. De fato, cada um desses produtos adiciona novas funcionalidades ao SQL no passar do tempo, então mesmo dizendo “SQL do SQLite” não é suficiente para determinar exatamente o que você está falando. Você precisa dizer a versão do SQL que acompanha o SQLite versão X.Y.Z.”
Tudo o que nos leva ao seguinte aviso, atualmente residindo no topo da especificação da Base de Dados SQL Web:
Esta especificação chegou a um impasse: todos os implementadores interessados têm utilizado o mesmo backend do SQL (Sqlite), mas precisamos de múltiplas implementações independentes para prosseguir ao longo de um caminho de normalização. Até outro implementador estar interessado em implementar esta especificação, a descrição do dialeto SQL foi deixada como simplesmente uma referência para Sqlite, o que não é aceitável para um padrão.
Isto é contra esse cenário que vou apresentá-lo para uma outra visão concorrente avançada, persistente, armazenamento local para aplicações web: A API de "Indexed Database", formalmente conhecida como “WebSimpleDB,” agora carinhosamente conhecida como “IndexedDB.”
A API do “Indexed Database” expõe o que é chamado um objeto de armazenamento. Um objeto de armazenamento compartilha vários conceitos com o a base de dados SQL. Existem “base de dados” com “registros,” e cada registro tem um número definido de “campos.” Cada campo tem um “datatype” específico, que é definido quando a base de dados é criada. Você pode selecionar um subconjunto de registros, depois enumerá-los com um “índice.” Alterações no armazenamento de objetos são tratados dentro de “transações”.
Se você já fez alguma programação de banco de dados SQL,
esses termos provavelmente serão familiares. A principal diferença é que o
objeto de armazenamento não tem linguagem de consulta estruturada. Você
não constrói uma declaração como
"SELECT * from USERS where ACTIVE = 'Y'"
. Em vez disso, você
usa métodos fornecidos pelo objeto de armazenamento para adicionar um
índice na base de dados nomeada “USERS
,” enumerar os
registros, filtrar os registros de usuários inativos, e usar métodos de
acesso para obter os valores de cada campo nos registros restantes.
Uma visão geral do IndexedDB
é um bom tutorial de como IndexedDB funciona, fazendo comparações lado a
lado entre o IndexedDB e base dados SQL.
No momento que escrevo, IndexedDB está implementado apenas na versão beta do Firefox 4. (Em contraste, a Mozilla afirmou que eles nunca irão implementar Base de Dados SQL Web.) Google afirmou que estão considerando o suporte ao IndexedDB para o Chromium e o Google Chrome. E até mesmo a Microsoft disse que o IndexedDB “é uma ótima solução para a web.”
Então o que você, como desenvolvedor web, pode fazer com IndexedDB? Neste momento, praticamente nada além de algumas demonstrações técnicas. Um ano a frente? Talvez algo mais. Para iniciar, confira os links na seção “Leitura complementar” com alguns bons tutoriais.
❧
HTML5 storage:
globalStorage
, um não-padrão precursor do
localStorage
. Mozilla adicionou suporte ao padrão
localStorage
no Firefox 3.5.)
Trabalho prévio de Brad Neuberg et. al. (pré-HTML5):
userData
do IE)
dojox.storage.manager
referência da API
Web SQL Database:
IndexedDB:
❧
Isso foi “O Passado, Presente & Futuro de Armazenamento Local para Aplicações Web.” Consulte o Sumário, caso queira continuar com a leitura.
Em associação a Google Press, O’Reilly está distribuindo este livro em variados formatos, incluindo papel, ePub, Mobi, DRM-free e PDF. A edição paga é chamada “HTML5: Up & Running” e está disponível agora. Este capítulo está incluído na versão paga.
Se você gostou deste capítulo e quer mostrar sua apreciação, basta comprar o livro “HTML5: Up & Running” com esse link afiliado ou comprar a edição eletrônica diretamente da O’Reilly. Você vai ganhar um livro, e eu vou ganhar um trocado. Atualmente, não aceito doações diretas.
Copyright MMIX–MMXI Mark Pilgrim