Uma lista de tarefas simples utilizando HTML5 IndexedDB

HTML5 Rocks

Introdução

IndexedDB é novo em HTML5. Os bancos de dados da web são hospedados e residem no navegador do usuário. Permitindo que os desenvolvedores criem aplicativos com recursos de consulta avançados, ele foi desenvolvido tendo em mente que uma nova geração de aplicativos da web surgirá e poderá trabalhar on-line e off-line.

O código de exemplo deste artigo demonstra como criar um gerenciador de lista de tarefas muito simples. Existe um tour de nível bem alto de alguns recursos disponível em HTML5.

Este tutorial é uma conversão de nosso tutorial Uma simples lista de tarefas usando bancos de dados da web HTML5 e serve para realçar como é fácil fazer a transição para IndexedDB.

O que é IndexedDB?

IndexedDB é um armazenamento de objetos. É diferente de um banco de dados relacional, que tem tabelas, com linhas e colunas de coleções. É uma diferença importante e fundamental e afeta o modo como você projeta e cria seus aplicativos.

Em um armazenamento de dados relacionais tradicional, teríamos uma tabela de itens de atividades que armazenam uma coleção de dados de atividades dos usuários em linhas. Com colunas de tipos de dados nomeados. Para inserir dados, a semântica normalmente segue: INSERT INTO Todo(id, data, update_time) VALUES (1, "Test", "01/01/2010");

IndexedDB é diferente porque você cria um armazenamento de objetos para um tipo de dados e simplesmente mantém objetos JavaScript nesse armazenamento. Cada armazenamento de objetos pode ter uma coleção de índices que deixam a consulta eficiente e se repetem.

IndexedDB também lida com a noção de uma linguagem de consulta padrão ( SQL), em vez de substitui-la por uma consulta com um índice que produz um cursor que será usado para repetir o conjunto de resultados.

Este tutorial fala somente sobre um exemplo real de como usar IndexedDB no contexto de aplicativos existentes gravados para usar WebSQL.

Por que IndexedDB?

Em 18 de novembro de 2010, o W3C anunciou que o banco de dados SQL da web é uma especificação desatualizada. É uma recomendação para que os desenvolvedores da web não usem mais a tecnologia, pois a especificação não receberá nenhuma nova atualização, e os fornecedores de navegador não são incentivados a oferecer suporte para essa tecnologia.

O substituto, IndexedDB, e o assunto deste tutorial é o armazenamento de dados que os desenvolvedores devem usar para armazenar e manipular dados no lado do cliente.

Muitos navegadores importantes como Chrome, Safari, Opera e praticamente todos os dispositivos móveis baseados em Webkit oferecem suporte para WebSQL e provavelmente manterão o suporte no futuro.

Pré-requisitos

Este exemplo usa um namespace para encapsular a lógica do banco de dados.

var html5rocks = {};
html5rocks.indexedDB = {};

Assíncrono e transacional

Na maioria dos casos em que o banco de dados indexado é usado, você usará a API assíncrona. A API assíncrona é um sistema sem bloqueio e, como tal, não obterá dados por meio de valores de retorno, mas fornecerá os dados para uma função de retorno de chamada definida.

O suporte para IndexedDB por meio de HTML é transacional. Não é possível executar comandos ou abrir cursores fora de uma transação. Existem vários tipos de transação: transações de leitura/gravação, somente leitura e captura de tela. Neste tutorial, usaremos as transações de leitura/gravação

Etapa 1. Como abrir o banco de dados

Antes de fazer qualquer coisa com o banco de dados, primeiro é necessário abri-lo.

html5rocks.indexedDB.db = null;

html5rocks.indexedDB.open = function() {
  var request = indexedDB.open("todos");

  request.onsuccess = function(e) {
    html5rocks.indexedDB.db = e.target.result;
    // Do some more stuff in a minute
  };

  request.onfailure = html5rocks.indexedDB.onerror;
};

Abrimos um banco de dados chamado "todos" e o atribuímos à variável db no objeto html5rocks.indexedDB. Agora podemos usar isso para consultar nosso banco de dados ao longo deste tutorial.

Step2. Como criar um armazenamento de objetos

Só é possível criar armazenamentos de objetos em uma transação "SetVersion". Ainda não falei sobre setVersion, mas ele é um método muito importante; é o único local do código em que armazenamentos de objetos e índices podem ser criados.

html5rocks.indexedDB.open = function() {
  var request = indexedDB.open("todos",
    "This is a description of the database.");

  request.onsuccess = function(e) {
    var v = "1.0";
    html5rocks.indexedDB.db = e.target.result;
    var db = html5rocks.indexedDB.db;
    // We can only create Object stores in a setVersion transaction;
    if(v!= db.version) {
      var setVrequest = db.setVersion(v);

      // onsuccess is the only place we can create Object Stores
      setVrequest.onfailure = html5rocks.indexedDB.onerror;
      setVrequest.onsuccess = function(e) {
        var store = db.createObjectStore("todo",
          {keyPath: "timeStamp"});

        html5rocks.indexedDB.getAllTodoItems();
      };
    }

    html5rocks.indexedDB.getAllTodoItems();
  };

  request.onfailure = html5rocks.indexedDB.onerror;
}

O código acima faz bastante coisa. Definimos um método "open" em nossa API que, quando chamado, abrirá o banco de dados "todos". A solicitação de "open" não é executada imediatamente. Em vez disso, IDBRequest é retornado. O método indexedDB.open será chamado quando a função atual for encerrada. Isso é um pouco diferente do modo como normalmente atribuímos retornos de chamada assíncronos, mas temos a chance de associar nossos próprios listeners ao objeto IDBRequest antes da execução dos retornos de chamada.

Se a solicitação de abertura for bem-sucedida, o retorno de chamada onsuccess será executado. Nesse retorno de chamada, verificamos a versão do banco de dados e, se ela não for igual ao número esperado, chamaremos "setVersion".

SetVersion é o único local do código onde podemos alterar a estrutura do banco de dados. Nele, podemos criar e excluir armazenamentos de objetos e criar e remover índices. Uma chamada para setVersion retorna um objeto IDBRequest onde podemos anexar nossos retornos de chamada. Quando bem-sucedida, começamos a criar nossos armazenamentos de objetos.

Os armazenamentos de objetos são criados com uma única chamada para createObjectStore. O método pega um nome de armazenamento e um objeto de parâmetro. O objeto de parâmetro é muito importante porque permite definir propriedades opcionais importantes. Em nosso caso, podemos definir keyPath que é a propriedade que torna um objeto individual no armazenamento exclusivo. No exemplo, essa propriedade é "timeStamp". "timeStamp" deve estar presente em todos os objetos armazenados em objectStore.

Assim que o armazenamento de objetos é criado, chamamos o método getAllTodoItems.

Step3. Como adicionar dados a um armazenamento de objetos

Como estamos criando um gerenciador de listas de atividades, é muito importante adicionar itens de atividades ao banco de dados. Isso é feito da seguinte maneira:

html5rocks.indexedDB.addTodo = function(todoText) {
  var db = html5rocks.indexedDB.db;
  var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE, 0);
  var store = trans.objectStore("todo");
  var request = store.put({
    "text": todoText,
    "timeStamp" : new Date().getTime()
  });

  request.onsuccess = function(e) {
    // Re-render all the todo's
    html5rocks.indexedDB.getAllTodoItems();
  };

  request.onerror = function(e) {
    console.log(e.value);
  };
};

O método addTodo é muito simples. Primeiro, obtemos uma referência rápida para o objeto de banco de dados, iniciamos uma transação READ_WRITE e obtemos uma referência para o armazenamento de objetos.

Agora que o aplicativo tem acesso ao armazenamento de objetos, podemos emitir um comando put simples com um objeto JSON básico. A propriedade timeStamp é a chave exclusiva para o objeto e é usada como "keyPath". Quando a chamada é bem-sucedida, o evento onsuccess é acionado e conseguimos processar o conteúdo na tela.

Etapa 4. Como consultar os dados em um armazenamento.

Agora que os dados estão no banco de dados, precisamos arranjar uma maneira de acessá-los de modo significativo. Felizmente, isso é muito simples:

html5rocks.indexedDB.getAllTodoItems = function() {
  var todos = document.getElementById("todoItems");
  todos.innerHTML = "";

  var db = html5rocks.indexedDB.db;
  var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE, 0);
  var store = trans.objectStore("todo");

  // Get everything in the store;
  var keyRange = IDBKeyRange.lowerBound(0);
  var cursorRequest = store.openCursor(keyRange);

  cursorRequest.onsuccess = function(e) {
    var result = e.target.result;
    if(!!result == false)
      return;

    renderTodo(result.value);
    result.continue();
  };

  cursorRequest.onerror = html5rocks.indexedDB.onerror;
};

Todos os comandos usados nesse exemplo são assíncronos e, assim, os dados não são retornados de dentro da transação.

O código faz uma transação e instancia uma pesquisa de keyRange nos dados. O keyRange define um subconjunto simples dos dados que queremos consultar no armazenamento. Como o keyPath do armazenamento é o valor numérico de data/hora, definimos o menor valor da pesquisa como 0 (qualquer coisa desde a época) para retornar todos os nossos dados.

Agora há uma transação, uma referência ao armazenamento que queremos consultar e um período em que haverá repetição. Só nos resta abrir o cursor e associar um evento "onsuccess".

Os resultados são transmitidos pelo retorno de chamada bem-sucedido no cursor, onde processamos o resultado. O retorno de chamada é acionado somente uma vez por resultado. Desse modo, para que você continue repetindo os dados, precisará garantir que a chamada "continue" no objeto de resultado.

Etapa 4a. Como processar dados de um armazenamento de objetos

Assim que os dados forem buscados no armazenamento de objetos, o método renderTodo será chamado para cada resultado no cursor.

function renderTodo(row) {
  var todos = document.getElementById("todoItems");
  var li = document.createElement("li");
  var a = document.createElement("a");
  var t = document.createTextNode();
  t.data = row.text;

  a.addEventListener("click", function(e) {
    html5rocks.indexedDB.deleteTodo(row.text);
  });

  a.textContent = " [Delete]";
  li.appendChild(t);
  li.appendChild(a);
  todos.appendChild(li)
}

Para um determinado item de atividade, simplesmente pegamos o texto e fazemos a interface para o item, incluindo um botão de exclusão (para que você possa excluir um item de atividade).

Etapa 5. Como excluir dados de uma tabela

html5rocks.indexedDB.deleteTodo = function(id) {
  var db = html5rocks.indexedDB.db;
  var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE, 0);
  var store = trans.objectStore("todo");

  var request = store.delete(id);

  request.onsuccess = function(e) {
    html5rocks.indexedDB.getAllTodoItems();  // Refresh the screen
  };

  request.onerror = function(e) {
    console.log(e);
  };
};

Assim como acontece com o código para colocar dados em um armazenamento de objetos, a exclusão dos dados também é simples. Inicie uma transação, faça referência ao armazenamento de objetos com seu objeto e emita um comando delete com o ID exclusivo de seu objeto.

Etapa 6. Como associar tudo

Quando a página for carregada, abra o banco de dados, crie a tabela (se necessário) e processe todos os itens de atividades que talvez já estejam no banco de dados.

function init() {
  html5rocks.indexedDB.open(); // open displays the data previously saved
}

window.addEventListener("DOMContentLoaded", init, false);

Uma função que obtém os dados de DOM é necessária. Assim, chame o método html5rocks.indexedDB.addTodo:

function addTodo() {
  var todo = document.getElementById('todo');

  html5rocks.indexedDB.addTodo(todo.value);
  todo.value = '';
}

O produto final

Comments

0