Vamos falar de IndexedDB

A Webstore API do HTML5 (local e session storage) já é bastante usada para guardar dados offline, porém essa API não permite uma flexibilidade boa para fazer buscas eficientes, nem guardar dados duplicados em uma mesma chave. Para esses casos existe uma API chamada IndexedDb que consiste basicamente em um banco de dados chave-valor, NoSQL,  disponível offline, rodando diretamente no navegador do usuário. Nesse post pretendo fazer um pouco de como funciona e falar de alguns conceitos que aprendi enquanto estudava essa API.

Conceitos

Antes de ver algum código é interessante entender um pouco do funcionamento e as características do indexedDb. O indexedDb segue um conceito chamado “Transactional Database Sytem” que apesar de parecer confuso logo de inicio se mostra bem simples de usar. O IndexDB salva os dados em objetos que são armazenados em ObjectStores, para manipular os dados se usa transações e como citei a cima o IndexedDB é não SQL, se você estiver acostumado a usar SELECTS e INSERTS é bom desapegar por que no IndexedDB a brincadeira é outra.

Dentre as principais características se destacam:

  • IndexedDB guarda pares de chave e valor
  • IndexedDB é construida no modelo de base de dados transacional
  • A API do IndexedDB é predominantemente assíncrona
  • IndexedDB Usa requests em todo o lugar
  • IndexedDB é Orientado a Objeto

Esses e outros conceitos estão explicadas detalhadamente no portal de desenvolvimento da mozilla, que junto com a documentação da w3c foi o material mais completo sobre o assunto que eu encontrei e usei para fazer esse post.

Suporte

Segundo o Can I Use, o suporte atual do IndexedDB abrange os browsers mais usados no mercado, com a exceção do Safari. É importante observar que a API tem o suporte parcial do IE 10 e 11.

Conceito de ObjectStore

É importante entender a função de um elemento muito importante no indexedDB, ele é o ObjectStore ou Armazém de objetos. Uma Base de dados no indexedDB é composta por uma ou mais ObjectStore (OS) cada OS guarda, como o nome já indica, objetos e esses objetos por sua vez são os dados que queremos guardar. Em uma analogia aos banco de dados tradicionais, o OS seria como uma tabela. Mas ao contrário dos bancos de dados tradicionais no IndexedDB os objetos não precisam necessariamente ter estrutura igual, podendo haver chaves a mais e ou a menos em cada objeto.

Vamos pro código!

Criando uma Base de dados IndexedDB

Criar uma base de dados no IndexedDB é bastante simples, para faze-lo usas-se o método open() do objeto indexedDB como ilustrado abaixo:

// Exemplo 1
var request = indexedDB.open('mydb'), // Cria um bd ou abre um já existente.
	db = null;
	
request.onsuccess = function(e){
	//Basicamente todas as ações a serem feitas no indexedDb serão realizadas dentro de um callback.
	db = request.result; // retorna uma instancia do bd
	console.log("Banco de dados: "+ db.name + " Versão: " + db.version);
	// Exibe: "Banco de dados: mydb Versão: 1"
}
request.onerror = function(e){
	console.log(e.error)
}

O método open() serve tanto pra criar como para iniciar nossa data base, toda DB tem por obrigação um nome e uma versão, na situação acima o nome da DB é “mydb” e a versão quando não especificada no segundo parâmetro do método open() é 1. Como explicado lá no começo, praticamente tudo nessa API é assíncrono logo o método open() retorna uma request. Usamos a função onsuccess() que será disparada caso tudo tenha dado certo, e a onerror() para o tratamento de erros.

Para usar efetivamente a DB precisamos criar um ObjectStore. No exemplo 2 abaixo mudaremos um pouco a estrutura do código para facilitar os exemplos futuros:

//Exemplo 2
MyBD.open = function(){
	var request = indexedDB.open('meuslivros');
	
	request.onupgradeneeded = function(e){
		// é a primeira vez que o banco é executado
		var db = e.target.result, // Apenas para facilitar,
		    store = db.createObjectStore("books", {keyPath: "isbn"});  // O keyPath especifica um key que será autoincrementada

 		// Coloca um valor inicial
 		store.put({title: "Harry Potter e a pedra filosofal", author: "J. K. Rowling", isbn: 1231});
 		store.put({title: "Harry Potter e a câmara dos segredos",author: "J. K. Rowling", isbn: 1232});
	}

	request.onsuccess = function(e) {
	  	MyBD.db = request.result;
	  	console.log(MyBD.db);
	};
	request.onblocked = function(e){
		console.log(e)
	}
	request.onerror = function(e){
		console.log(e)
	}
}

No código acima vemos que foi criado o DB “meuslivros”. Para inicializar nosso banco adicionamos a request mais um método, dessa vez chamado onupgradeneeded(). Esse método é chamado apenas na primeira execução do método open() que é quando a DB será criada.

Dentro do onupgradeneeded() iremos criar nossas OS (ObjectStores)  usando o método createObjectStore() que recebe no primeiro parâmetro  o nome do OS (“books”) e no segundo parâmetro, um objeto com algumas configurações opcionais, nesse caso criamos um KeyPath que é funciona como uma chave única obrigatória que serve como id para nossos dados. Como estamos falando de livros no exemplo, usei o código internacional de livros como chave única (isbn).

Com o OS criado podemos adicionar alguns valores iniciais usando o método put() da store que criamos, como ao criar o OS usei eu configurei o keypath, ele deve estar presente em todos os objetos que forem ser cadastrados.

Com isso nós criamos uma DB, o callback onsuccess() será executado e guardaremos o request.result que neste caso é uma instância do banco para nossos métodos futuros.  O onupgradeneeded() só é executado uma vez na próxima vez que o método open() for chamado ele ira ignorar esse método e ir direto ao onsuccess().

É relevante levantar o fato de que só se pode criar ou deletar stores dentro do upgradeneeded, ou em uma transaction do tipo versionchange.

Como adicionar valores no banco.

Agora que criamos precisamos popular nossa DB, como você viu é possível inserir dados logo após a criação da database dentro do onupgradeneeded(), já para adicionar fora dele,precisamos usar transactions. Veremos como faze-lo abaixo.

Transactions

Uma transaction é uma solicitação que fazemos ao IndexedDB para ler, ler e escrever ou alterar o banco. O conceito é bem simples, veja o código:

MyBD.addBooks = function(){

	var tx = MyBD.db.transaction("books","readwrite"), // Cria uma transaction
		store = tx.objectStore("books"); // Seleciona books
	
		store.put({title: "HarryPotter e a camara secreta", author: "J.K.Rowling", isbn: 123321});
	
	
	tx.oncomplete = function(){
		//Deu tudo certo
		return true; 
	}

}

No caso acima usamos a instância que salvamos lá no open para criar uma transaction na variável tx. O método transaction() recebe no primeiro parâmetro uma array com os OSs que participarão da transação (podemos usar uma string caso desejamos trabalhar com apenas um OS), o segundo parâmetro indica a natureza da transação. Transações do tipo readonly são usadas para fazer consultas no banco já as do tipo readwrite são usadas para adicionar e ou deletar dados do banco, por fim temos a versionchange que é usada para editar a estrutura do banco (adicionar e ou remover OSs).

No exemplo utilizamos a readwrite para poder adicionar novos dados a OS “books”, usamos o método objectStore() para selecionar a OS “books” e por fim, usamos novamente o método put() para adicionar o objeto a OS. Caso tudo tenha dado correto o callback oncomplete() será acionado.

O código acima poderia ser reescrito de uma linha como vemos abaixo:

MyBD.addBooks = function(livros){

	var tx = MyBD.db.transaction("books","readwrite"), // Cria uma transaction
	 	tx.objectStore("books").put({title: "HarryPotter e a camara secreta", author: "J.K.Rowling", isbn: 123321});// seleciona a transaction e adiciona os dados
	
	
	tx.oncomplete = function(){
		//Deu tudo certo
		return true; 
	}

}

 

Consultado dados do banco.

Assim como para adicionar, para consultar também iremos fazer uso de transaction, nesse caso do tipo readonly, confira abaixo:

MyBD.getBook = function(isbn){
	var tx = MyBD.db.transaction("books","readonly"),
		store = tx.objectStore("books"),
		request = store.get(isbn);
		if(callback === undefined){
			throw new Error("Necessario indicar um callback");
			return false;
		}

		request.onsuccess = function(e){
			var result = request.result;
			 if (result != null) {
			 	//retornou um objeto com os dados do livro
			   	console.log(result);

			  } else {
			       // não encontrou nada
			       console.log(null);
			  }
		}
}

 

No código acima o processo é bem semelhante ao de adicionar, a diferença fica no uso do método get() que receberá o “isbn” do item que queremos. No callback da nossa requisição checamos se o algum resultado foi encontrado, caso nada tenha sido encontrado o result retornara null

Consultas com mais de um resultado

Usar o get() permite trazer apenas um resultado, para conseguirmos uma lista precisamos usar o cursor. Acompanhe abaixo:

MyBD.getBooksList = function(){
	var tx = MyBD.db.transaction("books","readonly"),
		store = tx.objectStore("books"),
                request = store.openCursor();
		request.onsuccess = function() {
		  var cursor = request.result;
		  console.log(cursor)
		  if (cursor) {
		  	//Chamado por cada resultado encontrado
		    console.log(cursor.value.isbn, cursor.value.title, cursor.value.author,cursor.value.avaliacao);

		    cursor.continue();
		  } else {
		    console.log(null);
		  }
		};
}

 

No código acima usamos o método openCursor() ao invés do get(), esse método funciona como um loop, trazendo no onsuccess() um resultado por vez  os resultados estão na propriedade result da request e usamos o método continue() para avançarmos para o próximo resultado até retornar null que saberemos que não há mais resultados..

Deletando itens.

Deletar itens é bastante simples, assim como no get usaremos uma transaction veja abaixo:

MyBD.deleteBookByIsbn = function(isbn,fn){
	var tx = MyBD.db.transaction("books","readwrite"), // Cria uma transaction
		store = tx.objectStore("books"),
		request = store.delete(isbn);
		if(typeof fn != 'function'){
			throw new Error("Necessario indicar um callback");
			return null;
		}
		request.onsuccess = fn;
}

Usamos o método delete() desta vez passando o keypath (no caso o isbn).

Chegando até aqui você já deve saber como criar, adicionar, listar e deletar dados no indexedDB. Como você pode perceber o indexedDB não é coisa de outro mundo e com certeza é a solução dos problemas de muitos desenvolvedores de WebApps que precisam de mais flexibilidade que o local storage.

É interessante lembrar que além do indexedDB, outra tecnologia de Banco de dados estava na especificação da W3C que era o WebSQL que basicamente disponibilizava o SQLite para uso no navegador, porém a W3C Retirou o WebSQL da recomendação e agora recomenda o uso apenas do indexedDB.

O IndexedDB é muito mais que só isso!

O IndexedDB ainda tem muitos outros recursos que ajudam a organizar dados e fazer filtros mais avançados para a busca. Todos os recursos estão descritos e documentados no portal de desenvolvimento da mozilla link aqui. Eu realmente sugiro que você vá lá e também aprenda os outros recursos mais avançados que o IndexedDB oferece.

É isso pessoal, espero que tenham curtido esse pequeno e básico guia sobre IndexedDB, qualquer sugestão, duvida ou erro que tenham encontrado aqui, por favor postem nos comentários.

Até o próximo post.

Post a comment

* Copy This Password *

* Type Or Paste Password Here *

14,971 Spam Comments Blocked so far by Spam Free Wordpress

You may use the following HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>