Categorie
Javascript Programmazione

{JS} API Storage: Indexed Database

Cos’è l’API Indexed Database?

Quando si ha la necessità di gestire una grande quantità di dati non possiamo esimerci da utilizzare un database.

Il predecessore dell’ Indexed Database API (noto anche come IndexedDB) fu Web SQL Database API ma quest’ultimo non ebbe grande successo data la grande difficoltà di creare uno standard unificato per la gestione data la moltitudine di dialetti SQL.

Per quanto l’SQL sia considerabile un linguaggio universale con degli standard, ogni particolare RDBMS (Relational database management system) ha espanso e in parte rimodulato il linguaggio.
Sono nati così quelli che possiamo definire non dei veri e propri linguaggi, ma dei dialetti dell’SQL: T-SQL (Transact SQL) del database Microsoft SQL Server, PL-SQL (Procedurale Language SQL) del database Oracle ecc.

Questa gran varietà di dialetti SQL ha decretato il fallimento del progetto Web SQL Database API.

IndexedDB

IndexedDB è un sistema di archiviazione NoSQL su larga scala. Ti consente di archiviare qualsiasi cosa nel browser dell’utente. 

Ogni database IndexedDB è univoco per origine, il che significa che è accessibile dal dominio di appartenenza (o sottodominio) ma non dall’esterno (domini esterni).
 I limiti di archiviazione dei dati sono piuttosto grandi ma i diversi browser potrebbero gestire i limiti e l’eliminazione dei dati in modo differente.

Se proveniamo dalla gestione di database MySQL o in generale di database relazionali, tutto questo potrebbe mandarci in confusione; dobbiamo pensare a IndexedDB come ad un database strutturato in oggetti, che ricalca la struttura di database relazionale ma che la sua natura è quella di risedere nella nostre pagine web, di un app o sito, per poi essere caricato sul client dell’utente.

Un database IndexedDataBase è formato da diversi elementi.

IDB DatabaseIl database rappresenta il livello più alto di un IndexedDB contiene gli Object Stores
IDB ObjectStoreGli Object Store sono contenuti all’interno di un database IDB.
Possiamo pensare agli Object Stores come simili alle tabelle nei database relazionali tradizionali; un ObjectStore conterrà altri oggetti che rappresentano i dati contenti nelle tabelle.
IDB TransactionTutte le operazioni possibili su un o più Object Store sono possibile solo attraverso una transazione.
Si tratta di un wrapper-gestore, un involucro gestore di una o un gruppo-di-operazioni
IDB IndexL’Index Object è un Object Store per organizzare i dati in un altro Object Store; si tratta di un oggetto che dipende da un ObjectStore principale e che riorganizza e re-indicizza gli oggetti presenti in quest’ultimo
IDB CursorUn “Cursor” rappresenta il meccanismo per iterare molteplici records/oggetti contenuti negli ObjectStore di un database IndexedDB.

Creazione e gestione di un IDB

Vediamo ora come procedere alla creazione ed all’utilizzo di un database Indexed.

Verifica del supporto

IndexedDB è quasi universalmente supportato; è comunque buona norma verificare.

if(!window.indexedDB){
console.log("IndexedDB non è supportato da questo browser");
}

Aprire un database

window.indexedDB.open(name, version);

Per aprire un database IDB possiamo utilizzare il metodo .open(); questo prevede 2 paramenti; il metodo costituisce una promise che si risolve con la creazione del database.

I 2 paramenti indicano il nome ed il numero di versione.

ParametroDescrizione
nameindica il nome del database
versionindica la versione del database

“name” e “version” sono importanti, trattandosi di database caricati sul client-browser dell’utente, questi risulteranno utili per l’identificazione, l’aggiornamento e la gestione di evitare errori.

window.indexedDB.open('dati', 1);

Il metodo .open() opera in maniera asincrona, si tratta di una promise, sulla quale possiamo definire dei gestori di evento che restituiranno un oggetto “event” chiamato “IDBRequest” :

EventoDescrizione
successsi verifica quando l’apertura del database è andata a buon fine
errorsi verifica quando c’è un problema nell’apertura del database
upgradeneededgestisce l’aggiornamento del database, aggiornamento di versione e la creazione degli Object Store
var richiestaDB = window.indexedDB.open('dati', 1);

richiestaDB.onsuccess = function(event){
    database = event.target.result;
//oppure
//  database = richiestaDB.result;
    console.log("Il database è stato creato correttamente");
};

richiestaDB.onerror = function(event){
    console.log('Si è verificato un errore nella creazione del DB');
};

La corretta procedura per gestire la creazione di un database è quella di incapsulare la richiesta di creazione all’interno di una variabile “richestaDB”; questo ci consentirà di gestire gli eventi “success” ed “error”.
Particolare attenzione è da rivolgere alla striga che definisce la variabile globale “database”; questa recupera il risultato, il database, restituzione della promise del metodo .open(), mediante l’oggetto “event”.

Vedremo l’utilizzo del terzo evento “upgradeneeded” nel paragrafo successivo relativo alla creazione di Object Store.

Un IndexedDB dopo la creazione  è memorizzato all'interno del browser-client. Non posso esistere due database con lo stesso nome o stessa versione. 
E' possibile eliminare la memorizzazione del DB all'interno delle impostazioni di Chrome

Creare Object Store

Un database contiene in genere uno o più Object Store.
Gli Object Store possono essere considerati simili alle tabelle nei database SQL e devono contenere oggetti dello stesso tipo; es. un Object Store “utenti” dovrà contenere altri oggetti “utente1”, “utente2”, ecc.

Tabelle nel database relazionale mySQL

In un sito ecommerce potremmo creare ad esempio tre Object Store per i clienti, per i prodotti e per gli ordini.

Per garantire l’integrità del database, gli Object Store possono essere creati, modificati e rimossi solo all’interno dell’evento “upgradeneeded“;
letteralmente “aggiornamento necessario”.


La creazione di Object Store rappresenta un aggiornamento della struttura del database in quando il database è inizialmente creato privo di ObjectStore.

A differenza delle tabelle nei database tradizionali, i tipi di dati JavaScript effettivi all’interno dell’archivio possono non essere coerenti;

Ad esempio le proprietà che indicano l’età di un utente potrebbero essere 53'cinquanta-tre'unknown.

Abbiamo a disposizione proprietà e metodi utili per la gestione, verifica e modifica degli Object Store.

Tutte le proprietà ed i metodi per la gestione degli Object Store appartengono al database appena creato “database”:

MetodoDescrizione
database.objectStoreNames
database.objectStoreNames.contains("name")
database.objectStoreNames.lenght
1. restituisce un array elenco degli ObjectStore presenti;
2. verifica l’esistenza dell’Object Store nell’ elenco;
3. restituisce il numero di Object Store presenti.
database.deleteObjectStore("name")rimuove un Object Store
database.createObjectStore("name", {keyPath: "id"});crea un Object Store
richiestaDB.onupgradeneeded = function(event) {
	database = event.target.result;
	if(database.objectStoreNames.contains("utenti")) {
		database.deleteObjectStore("utenti");
	}
	database.createObjectStore("utenti", {keyPath: "id"});
};

L’evento è applicato sempre alla richiesta di creazione database; affinché sia possibile utilizzare il database all’interno della callback dell’evento “upgradeneeded” dovremo ridefinire “database”.

Il motivo è semplice, questo accade perché di fatto l’evento “success” e di conseguenza la creazione della variabile globale “database” non è ancora stata eseguita.
Perché allora non ascoltare l’evento successivamente? anche in questo caso la motivazione è abbastanza semplice; possiamo ascoltare questi eventi solo ed esclusivamente durante l’effettiva creazione del database; successivamente tutte le operazioni sullo stesso database, più precisamente sugli ObjectStore che compongono un database, saranno possibili solo ed esclusivamente mediante le “transazioni” che vedremo più avanti.

Tramite un istruzione “if” e le proprietà ed i metodi per la gestione degli Object Store di un database, verifichiamo l’eventuale presenta dell’ Object Store, provvediamo eventualmente alla sua eliminazione e ne ricreiamo uno nuovo.

“database”, omettendo “var”, è accessibile globalmente; espandendo l’oggetto potremo verificare la creazione dell’Object Store all’interno della proprietà “objectStoresNames”.

Chiavi primarie degli Object Store

Nell’esempio precedente nella riga

database.createObjectStore("utenti", {keyPath: "id"});

abbiamo indicato un secondo paramento di tipo oggetto con all’interno una proprietà “keyPath”

Così come avviene nelle tabelle dei database relazionali, tramite questo secondo paramento-oggetto e la sua proprietà “keyPath”, è possibile indicare una chiave-primaria che consente di indentificare univocamente gli oggetti contenuti all’interno di un Object Store.

Tipi di keyPath

E’ possibile assegnare ad un Object Store differenti tipologie di chiavi primarie.

keyPathDescrizione
defaultse non specificata, indexedDB assocerà un valore incrementale univoco automaticamente
auto incrementalepuò essere specificato ma rappresenta il comportamento di default
nome proprietà univocaè possibile indicare come chiave il nome di una proprietà sempre presente in tutti gli oggetti presenti nell’Object Store, purché abbia valore univoco
nome + auto incrementaleè possibile indicare un nome per la chiave al cui interno inserire un valore auto incrementale
database.createObjectStore("utenti", {keyPath: "email"});

Una keyPatch può essere una proprietà, presente negli oggetti presenti nell’Object Store, purché esista in tutti gli oggetti e abbia valore univoco. Per esempio, nel caso di un “utenti” Object Store, possiamo scegliere l’indirizzo email come percorso chiave.

database.createObjectStore("utenti", { autoIncrement: true });

In alternativa potremmo anche usare un generatore di chiavi, come “autoIncrement.” Il generatore di chiavi crea un valore univoco per ogni oggetto Object Store. 
Per impostazione predefinita, se non specifichiamo una chiave, IndexedDB crea una chiave incrementale automaticamente.

database.createObjectStore("utenti", {  keyPath: 'id', autoIncrement: true });

In fine è possibile indicare un nome arbitrario per la chiave univoca ed associare un valore auto incrementale.

Transazioni per operare sul database

Tutte le operazioni sugli Object Store di un IndexedDB, successive alla creazione del database, possono essere eseguite solo ed esclusivamente all’interno di una transazione.

Una transazione può essere considerata come un wrapper (involcro) sicuro attorno a un’operazione o un gruppo di operazioni e garantisce l’integrità del database.

Le transazione hanno in comune alcune caratteristiche:

  • Se una delle azioni all’interno di una transazione non riesce, tutte le azioni vengono annullate. 
  • Le transazioni sono specifiche per uno o più Object Store e vengono definite all’apertura della transazione. 
  • Possono essere di sola lettura o di lettura e scrittura.
var transazione = database.transaction(["utenti"], "readwrite");
	var objectStoreTransactionTarget = transazione.objectStore("utenti");
	objectStoreTransactionTarget.add({
		id: 3,
		nome: "Giovanni",
		cognome: "Despota",
	});

Una transizione è creabile attraverso il metodo .transaction() che appartiene al database “database”; il metodo prevede due parametri:
il nome dell’Object store che sarà interessato dalla transazione e la sua tipologia di transazione; i tipi di transazione sono due: readonly e readwrite.

Il metodo .objectStore() applicato alla transazione creata, indica l’oggetto Object Store su cui eseguirla; può sembrare ridondante ma il motivo è semplice:
una transazione unica può riguardare più Object Store

var transazione = database.transaction(['utenti', 'professionisti', 'civili'], 'readwrite');
transazione.objectStore('utenti').add({...});
transazione.objectStore('professionisti').put({...});
transazione.objectStore('civili').get({...});

.objectStore ci consente di eseguire più operazioni su ObjectStore differenti.

La creazione semplice di una transazione singola, per quanto possibile, non risulta scalabile e ci obbligherebbe a creare una nuova transazione per ogni modifica all’Object Store “utenti”

database.aggiungiUtente = function(utente) {
	var transazione = database.transaction(["utenti"], "readwrite");
	var objectStoreTransactionTarget = transazione.objectStore("utenti");
	var richiestaModifica = objectStoreTransactionTarget.add({
		"id": utente.id,
		"nome": utente.nome,
		"cognome": utente.cognome,
	});
	richiestaModifica.onsuccess = function(event) {
		console.log("Utente inserito correttamente!");
	}
	richiestaModifica.onerror = function(event) {
		console.log("Si è verificato un errore nell'inserimento di un utente!");
	}
};

Abbiamo creato una nuova proprietà per il database “aggiungiUtente” che è in realtà una funzione che prevede un parametro “utente”; questa funzione contiene al suo interno una “transaction” e l’annessa gestione degli eventi “success” ed “error”.
Potremo quindi riutilizzare il wrapper appena creato in questo modo:

database.aggiungiUtente({id: 1, nome: "Mario", cognome: "Rossi"});
database.aggiungiUtente({id: 2, nome: "Giovanni", cognome: "Bianchi"});
database.aggiungiUtente({id: 3, nome: "Guido", cognome: "Lavespa"});

Tutto il codice fin qui:

if(!window.indexedDB){
console.log("IndexedDB non è supportato da questo browser");
}

var richiestaDB = window.indexedDB.open("dati", 1);

richiestaDB.onsuccess = function(event) {
	database = event.target.result;
        console.log("Il database è stato creato corretamente");
};

richiestaDB.onerror = function(event) {
	console.log("Si è verificato un errore nell'apertura del DB");
};
richiestaDB.onupgradeneeded = function(event) {
	var database = event.target.result;
	if(database.objectStoreNames.contains("utenti")) {
		database.deleteObjectStore("utenti");
	}
	database.createObjectStore("utenti", {keyPath: "id"});
};


database.aggiungiUtente = function(utente) {
	var transazione = database.transaction(["utenti"], "readwrite");
	var objectStoreTransactionTarget = transazione.objectStore("utenti");
	var richiestaModifica = objectStoreTransactionTarget.add({
		"id": utente.id,
		"nome": utente.nome,
		"cognome": utente.cognome,
	});
	richiestaModifica.onsuccess = function(event) {
		console.log("Utente inserito correttamente!");
	}
	richiestaModifica.onerror = function(event) {
		console.log("Si è verificato un errore nell'inserimento di un utente!");
	}

Metodi degli Object Store

Possiamo operare mediante i metodi di un Object Store all’interno di una transazione:

.add()il metodo permette l’aggiunta di un dato ma richiede che non sia presente nell’ Object store un altro oggetto con la stessa chiave di identificazione
.put()il metodo put() aggiunge senza la limitazione presente nel metodo add()
.get()recupera un dato passando la chiave di identificazione dell’oggetto da recuperare
.delete()delete rimuove il dato passando la chiave di identificazione dell’oggetto da eliminare

Abbiamo già visto in azione il metodo .add() e di conseguenza sappiamo di poter utilizzare il metodo .put().

Procediamo con il metodo .get()

database.richiediUtente = function(primaryKey) {
	var transazione = database.transaction(["utenti"], "readonly");
	var objectStoreTransactionTarget = transazione.objectStore("utenti");
	var richiestaLettura = objectStoreTransactionTarget.get(primaryKey);
	richiestaLettura.onsuccess = function(event) {
		console.log("Utente trovato: " + event.target.result.nome + " " + event.target.result.cognome);
	}
	richiestaLettura.onerror = function(event) {
		console.log("Si è verificato un errore nella ricerca dell'utente");
	}
};

Nell’esempio precedente utilizziamo la chiave-primaria per recuperare gli oggetti dall’ Object Store,” {keyPath: “id”} “, assegnata al momento della creazione dell’Object Store per contraddistinguere gli oggetti presenti al suo interno.

Questo invece l’utilizzo del metodo .delete(); utilizzeremo anche in questo esempio la chiave-primaria id.

database.eliminaUtente = function(primaryKey) {
	var transazione = database.transaction(["utenti"], "readwrite");
	var objectStoreTransactionTarget = transazione.objectStore("utenti");
	var richiestaEliminazione = objectStoreTransactionTarget.get(primaryKey);
	richiestaEliminazione.onsuccess = function(event) {
		console.log("Utente eliminato");
	}
	richiestaEliminazione.onerror = function(event) {
		console.log("Si è verificato un errore non è stato possibile eliminare l'utente");
	}
};

Index Object

Abbiamo visto che un Indexed Database è formato da pseudo-tabelle chiamate ObjectStore.
Prendiamo ad esempio un Object Store chiamato “utenti”; all’interno di questo Object Store saranno presenti quindi molteplici dati, oggetti, che rappresentano diversi utenti.
Gli oggetti utenti avranno tutti delle proprietà che indicando ad esempio:
id, nome, cognome, sesso, email ecc.

//ObjectStore "utenti"
utenti{
{id: 1, nome: "Mario", cognome: "Rossi", sesso: "M"},
{id: 2, nome: "Rosa", cognome: "Bianchi", sesso: "F"},
{id: 3, nome: "Guido", cognome: "Lavespa", sesso: "M"},
{id: 4, nome: "Roberto", cognome: "Manca", sesso: "M"}
}

L’Object Store “utenti” archivia molteplici oggetti-utente, potremmo voler recuperare gli utenti in base al loro nome o al loro cognome ecc.
L’Index Object ci consentirà di effettuare queste operazioni.

L’oggetto IDB Index è un aggiunta ad un Object store.
Parliamo di aggiunta perchè come vedremo in seguito sarà possibile crearlo e accedervi solo mediante l’ObjectStore principale.

Abbiamo a disposizione i seguenti metodi:

Metodi Descrizione
objectStore.createIndex()metodo che consente di creare un IndexObject
objectStore.index()metodo che consente di accedere a un IndexObject

objectStore.createIndex()

Con il seguente metodo applicabile ad un ObjectStore pre-esistente è possibile creare un IndexObject

objectStore.createIndex("indice", "proprietà", "opzioni");

Il metodo prevede tre parametri:

Come primo parametro il nome dell’indice, che ci servirà per richiamare l’indice stesso, come secondo parametro il nome della proprietà da indicizzare e come terzo parametro un oggetto che rappresenta le opzioni di creazione dell’indice.

var objectStoreUtenti = database.createObjectStore("utenti", {keyPath: "id"});
objectStoreUtenti.createIndex("cognomi", "cognome", {unique: false});

Supponiamo che l’oggetto ObjectStore “utenti” sia già riempito di utenti:

//ObjectStore "utenti"
utenti{
{id: 1, nome: "Mario", cognome: "Rossi", sesso: "M"},
{id: 2, nome: "Rosa", cognome: "Bianchi", sesso: "F"},
{id: 3, nome: "Guido", cognome: "Lavespa", sesso: "M"},
{id: 4, nome: "Roberto", cognome: "Manca", sesso: "M"}
}


L’oggetto Index “cognomi”, creato mediante il metodo .createIndex(), creerà di fatto un nuovo oggetto i cui indici sono rappresentati dai cognomi degli utenti ed il loro rispettivo valore associato è rappresentato dagli oggetti contenuti nello Store principale “utenti”.

Per maggior comprensione, creiamo una rappresentazione dell’oggetto Index attraverso pseudo-codice:

//PSEUDOCODICE - Index Object "cognomi" dell' ObjectStore "utenti"

cognomi { 

Rossi > {id: 1, nome: "Mario", cognome: "Rossi", sesso: "M"},
Bianci > {id: 2, nome: "Rosa", cognome: "Bianchi", sesso: "F"},
Lavespa > {id: 3, nome: "Guido", cognome: "Lavespa", sesso: "M"},
Manca > {id: 4, nome: "Roberto", cognome: "Manca", sesso: "M"},

}

L’oggetto Index in buona sostanza mapperà gli oggetti contenuti nello Store principale “utenti” consentendoci di accedere agli stessi mediante una sorta di nuova filtratura/nuovi indici.

Seppure l’oggetto non sembra ancora accessibile in realtà è già creato; risulterà accessibile in seguito partendo dall’oggetto ObjectStore principale “utenti”, padre dell’Index Object, e mediante l’utilizzo del metodo .index() attraverso il quale potremo richiamare l’oggeto index indicando il nome attribuitoli, “cognomi”.

Options

Nella creazione di un Index Object Store dobbiamo specificare un terzo parametro; questo parametro che negli esempi precedenti abbiamo indicato con “{unique: false}” consente di specificare dettagli sul tipo di indicizzazione.

uniquepossiamo impostare valore ture o false; se true impediamo che ci siano duplicati (es. due utenti con il cognome “Rossi”); con false consentiamo i duplicati.
multiEntrypossiamo impostare valore ture o false; si utilizza quando indichiamo come proprietà da indicizzare un array; se true, indichiamo di creare per ogni valore dell’array un oggetto; se false (impostazione di default), indichiamo di trattare l’intero array come chiave.

objectStore.index()

Nota: L'Index Object deve essere definito durante la creazione del database o all'interno di una transazione successiva che riguarda l'ObjetStore interessato.

Abbiamo creato un Index Object Store ma non l’abbiamo ancora utlizzato.
Il metodo .index() che si applica all’ObjectStore principale e consente di accedere all’Index Object creato.

database.prendiUtentePerCognome = function(cognome) {
    var transazione = database.transaction(["utenti"], "readwrite");
    var objectStoreTransactionTarget = transazione.objectStore("utenti");
	var indice = objectStoreTransactionTarget.index("cognomi");
	var richiestaCognomi = indice.get(cognome);
	richiestaCognomi.onsuccess = function(event) {
		console.log("Utente trovato: " + event.target.result.nome + " " + event.target.result.cognome + " con id " + event.target.result.id);
	}
	richiestaCognomi.onerror = function(event) {
		console.log("Si è verificato un errore nella ricerca dell'utente");
	}
};

In questo esempio abbiamo aggiunto al database la proprietà-funzione “prendiUtentePerCognome” che contiene una transazione.
Con il metodo .index() ed il suo valore “cognomi”, nome all’indice attribuito, richiamiamo l’oggetto indice.
Con il metodo .get() applicato all’indice possiamo ora ricercare gli utenti presenti nell’oggetto principale “utenti” mediante il proprio cognome.

database.prendiUtentiPerCognome("Rossi");
// Utente trovato: Mario Rossi con id 1

Cursor

Un”Cursor” è il meccanismo che ci consente di iterare molteplici records (oggetti) contenuti all’interno di un Object Store o ancor meglio all’interno di un Index Object.
Per comprendere meglio il funzionamento si pensi al meccanismo di ricerca di prodotti all’interno di un e-commerce.
L’utente effettuerà una ricerca per “nome prodotto”, la mappatura dei prodotti presenti in un ObjectStore, che rappresenta il catalogo-totale, potrà essere gestita mediante un Index Object che mappa tutti i prodotti per nome.
Con il cursor potremo mostrare n prodotti, per esempio i primi 5 prodotti, per pagina e consentire di iterare i prodotti di 5 in 5.

Potremo creare un cursore mediante il metodo .openCursor();

.openCursor()

Esistono molteplici proprietà e metodi per gestire un cursore, cominicamo con il metodo per la creazione:

MetodiDescrizione
ObjectStore.openCursor();
IndexObject.openCursor();
il metodo crea un cursore sull’oggetto indicato
objectStore.openCursor(optionalKeyRange, optionalDirection);

Il metodo .openCursor() prevede due parametri opzionali: il primo può rappresentare una chiave o una range-di-chiavi; il secondo indica la direzione di lettura.

Direction

Il parametro “direction” indica la direzione di lettura che può essere “next” o “prev“, quindi l’attraversamento dei dati in avanti o indietro.
Esistono anche "nextunique""prevunique"– come sopra, ma salta gli oggetti con la stessa chiave.

“next” rappresenta l’impostazione di default quindi possiamo anche ometterla

objectStore.openCursor(optionalKeyRange);
KeyRange
L’oggetto IDBKeyRange

Possiamo lavorare con tutti gli oggetti di un ObjectStore e con tutte le chiavi-primarie degli oggetti contenuti nello stesso mediante i metodi:

.getAll()restituisce un oggetto contenente tutti gli oggetti dell’Object Store che corrispondono al parametro specificato o tutti gli oggetti nell’archivio se non vengono forniti parametri.
.getAllKeys()recupera tutte le chiavi degli oggetti presenti nell’Object Store che corrispondono al parametro specificato o di tutti gli oggetti nell’archivio se non vengono forniti parametri.

Metodi come .getAll() e .getAllKeys() restituiscono un un array di chiavi/valori.

Ma un Object Store può essere enorme, più grande della memoria disponibile e quindi .getAll() non riuscirà a ottenere tutti i record come array.

I cursori forniscono i mezzi per aggirare questo problema.

Per lavorare con gli indici, che essi siano le primaryKey di un ObjectStore o le chiavi di IndexObject, possiamo utilizzare i metodi dell’oggetto IDBKeyRange:

IDBKeyRange.upperBound()specifica il limite superiore dell’intervallo
IDBKeyRange.lowerBound()specifica il limite inferiore dell’intervallo
IDBKeyRange.bound()specifica entrambi i limite inferiore e superiore dell’intervallo
IDBKeyRange.only()crea un intervallo contenente un unico valore
IDBKeyRange.includes()restituisce valore booleano che indica se una chiave specificata è all’interno dell’intervallo

Prendiamo il seguente ObjectStore:

var objectStoreUtenti = database.createObjectStore("utenti", {keyPath: "id"});

E immaginiamo contenga quanto segue:

//ObjectStore "utenti"
utenti{
{id: 1, nome: "Mario", cognome: "Rossi"},
{id: 2, nome: "Giovanni", cognome: "Bianchi"},
{id: 3, nome: "Guido", cognome: "Lavespa"),
{id: 4, nome: "Gianna", cognome: "Alfa"},
{id: 5, nome: "Gulio", cognome: "Beta"},
{id: 6, nome: "Marco", cognome: "Gamma"},
{id: 7, nome: "Mirko", cognome: "Marte"},
{id: 8, nome: "Pavel", cognome: "Giove"},
{id: 9, nome: "Lino", cognome: "Luna"}
}

Mediante i metodi sopra elencati possiamo creare dei key-range di diversa natura:

var upperRange = IDBKeyRange.upperBound(2);
var cursore = objectStoreUtenti.openCursor(upperRange);

var lowerRange = IDBKeyRange.lowerBound(2);
var cursore = objectStoreUtenti.openCursor(lowerRange);

var boundRange = IDBKeyRange.bound(2 , 4);
var cursore = objectStoreUtenti.openCursor(boundRange);

var onlyRange = IDBKeyRange.only(3);
var cursore = objectStoreUtenti.openCursor(onlyRange);

boundRange.includes(3);
//true

Creato un keyRage potremo applicarlo all’interno della creazione di un cursore:

objectStore.openCursor(upperRange);
//oppure
objectStore.openCursor(IDBKeyRange.upperBound(2));

Un cursore appartiene ad un ObjectStore o IndexObject;
fin qui abbiamo capito come creare un cursore, un intervallo KeyRange, come applicarlo ad un ObjectStore ma non abbiamo visto ancora il suo utilizzo reale.

Proprietà e metodi di un cursore

Abbiamo a disposizione metodi e proprietà per gestire l’utilizzo di un cursore:

ProprietàDescrizione
cursor.key;
cursor.primaryKey;
La proprietà .key serve per recuperare la chiave di un IndexObject; mentre la proprietà .primaryKey seve per recuperare la chiave dell’ObjectStore
cursor.value;recupera il valore del cursore
database.mostraRisultati = function(limit) {
    var transazione = database.transaction(["utenti"], "readonly");
    var objectStoreTransactionTarget = transazione.objectStore("utenti");
    var richiestaCursore = objectStoreTransactionTarget.openCursor(IDBKeyRange.upperBound(limit));
    richiestaCursore.onsuccess = function() {
  var cursor = richiestaCursore.result;
  if (cursor) {
    var primaryKey = cursor.primaryKey;
    var value = cursor.value;
    var key = cursor.key;
    console.log(key, value);
    cursor.continue();
  }
   }}

Nell’esempio qui sopra abbiamo aggiunto una proprietà-funzione al database; aperto una transazione sull’oggetto “utenti” e creato su di esso un cursore.
Nella gestione dell’evento “success” abbiamo utilizzato le proprietà cursor.value, cursor.key per mostrare i risultati recuperati in base al keyrange indicato.

cursor.continue() & cursor.advance()
MetodiDescrizione
cursor.continue();il metodo passa all’intervallo successivo
cursor.advance();fa spostare il cursore in avanti di n posizioni

I metodi cursor.continue() e cursor.advance() permettono delle impostazione avanzate di iterazione.

L’utilizzo standard del metodo cursor.continue() è quello di essere incluso nel codice (come visto sopra) senza alcun parametro; questo consente di far proseguire l’iterazione che in alternativa si fermerebbe al primo valore riscontrato.

Il metodo .advance() imposta il numero di volte in cui un cursore deve spostare la sua posizione in avanti.

Se indicassimo ad esempio cursor.advance(2) otterremo:

Saranno iterati i primi 10 elementi prendendo il primo valore ogni due degli elementi totali.

Aggiornamento di un IdexedDB

Quando si chiama il metodo  window.indexedDB.open() è possibile specificare il numero di versione del database. 
Se questo numero di versione è maggiore della versione del database già esistente, potrà essere eseguito un aggiornamento.

In un aggiornamento è possibile aggiungere ObjectStore e Index Object.

Nel momento della creazione di un database avevamo invocato il metodo open() indicando il numero di versione uguale a “1”

var richiestaDB = window.indexedDB.open('dati',1);

Quando ci accingiamo ad aggiornare un database pre-esistente dovremo indicare una versione superiore

var richiestaAggDB = window.indexedDB.open('dati', 2);

Attraverso la gestione degli eventi collegati alla creazione/aggiornamento di un database potremo procedere alla creazione di nuovi ObjectStore e IndexObject.
In particolare l’evento che si occupa dell’inizializzazione e configurazione di un database è l’evento “onupgradeneeded”.
Abbiamo a disposizione nuove proprietà collegate al database e più specificatamente all’oggetto event, “upgradeDb”, utili in fase di aggiornamento:

event.oldVersion
event.newVersion
restituisce la versione precedente del database;
restituisce la nuova versione del database;
database.versionrestituisce la versione corrente del database
database.namerestituisce il nome del database
var richiestaAggDB = window.indexedDB.open('dati', 4);
 
richiestaAggDB.onsuccess = function(event){
database = event.target.result;
console.log(`Database "${database.name}" aggiornato alla versione: ${database.version}`);
};
 
richiestaAggDB.onerror = function(event){
console.log('Non è stato possibile aggiornare il database');
};
 
richiestaAggDB.onupgradeneeded = function(event){
database = event.target.result;
var dataBaseOldVersion = event.oldVersion;
if(dataBaseOldVersion == 3){
database.createObjectStore('amministratori'); 
database.createObjectStore('insegnanti'); 
database.createObjectStore('alunni'); }
if(dataBaseOldVersion == 4){
database.createObjectStore('alunni'); }
}
}

Un aggiornamento è del tutto similare al processo di creazione di un database; quello che lo differenzia è il numero di versione che sarà superiore.
Gestiamo l’aggiornamento all’interno dell’evento “upgradeneeded” così come inizializzavamo gli ObjectStore al momento della creazione.
Le uniche differenze presenti sono il recupero della versione precedente mediante “event.oldVersion” ed i controllo delle azioni da eseguire mediante le istruzioni “if”.
Provvediamo a fare un confronto con la versione caricata sul browser dell’utente, per poi indicare quali ObjectStore inserire e quali no in base alla versione pre-esistente.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *