La velocità gioca un ruolo im­por­tan­te quando si naviga nel web. A nessuno, infatti, piace aspettare un’eternità per il ca­ri­ca­men­to di una pagina. Perché una pagina web venga caricata più ve­lo­ce­men­te possibile, è opportuno che parte delle in­for­ma­zio­ni sia già presente nel computer dell’utente, senza dover essere nuo­va­men­te caricata. IndexedDB offre un modo per farlo: una memoria nel browser dell’utente, di­ret­ta­men­te di­spo­ni­bi­le per ogni sito web. Come funziona?

Perché si utilizza IndexedDB?

È opportuno che non siano solo i server a me­mo­riz­za­re i dati dei client, ma che anche i client con­ser­vi­no dati specifici di un sito web. In questo modo la na­vi­ga­zio­ne è più rapida, in quanto ogni volta che si visita un sito web non devono essere ri­ca­ri­ca­ti tutti i dati. Così, le ap­pli­ca­zio­ni web possono essere uti­liz­za­te anche offline. Anche gli input dell’utente si possono me­mo­riz­za­re lato client. Tra questi ci si riferisce so­prat­tut­to ai cookies. Tuttavia, essi hanno uno spazio e una capacità di in­for­ma­zio­ne molto limitata, troppo piccola per le ap­pli­ca­zio­ni web moderne. Inoltre i cookies devono essere inviati in rete ad ogni richiesta HTTP.

Una prima soluzione è stata offerta dal Web Storage, noto anche come DOM Storage. Questo sistema si basa ancora for­te­men­te sull’idea di cookie, ma estende la di­men­sio­ne da pochi kilobyte a 10 MB. Si tratta comunque di una di­men­sio­ne ridotta. Inoltre, questi file, chiamati spesso su­per­coo­kies, hanno una struttura molto semplice: è inutile provare a ricercare le proprietà di un database moderno. Tuttavia, la di­men­sio­ne ridotta di cookies e su­per­coo­kies non è l’unico motivo per il quale questi non rap­pre­sen­ta­no la soluzione giusta; entrambi i formati, infatti, non con­sen­to­no un’or­ga­niz­za­zio­ne strut­tu­ra­ta dei dati, né la creazione di indici, aspetti che osta­co­la­no la ricerca.

Lo sviluppo di Web SQL ha in­tro­dot­to in un certo un senso un cam­bia­men­to di direzione: una memoria lato client basata su SQL. Ma il World Wide Web Con­sor­tium (W3C), un'or­ga­niz­za­zio­ne per lo sviluppo di standard web, fermò il lavoro a favore di IndexedDB. Sotto la guida di Mozilla, è stato svi­lup­pa­to uno standard che at­tual­men­te è sup­por­ta­to dalla maggior parte dei browser moderni.

Browser che sup­por­ta­no IndexedDB

Chrome Firefox Opera Opera mini Safari IE Edge

Cosa si può fare con IndexedDB?

In­nan­zi­tut­to questo standard è un’in­ter­fac­cia prevista dal browser. Tramite questa, le pagine web possono me­mo­riz­za­re le in­for­ma­zio­ni di­ret­ta­men­te sul browser. Ciò avviene tramite Ja­va­Script. In questo modo ogni sito web può crearsi un proprio database e solo il sito web cor­ri­spon­den­te può accedere al­l'In­de­xedDB (ab­bre­via­zio­ne di Indexed Database API). I dati, quindi, rimangono privati. Nei database sono di­spo­ni­bi­li diversi object storage. Anche in questo caso si possono uti­liz­za­re diversi formati: stringhe, numeri, oggetti, array e date.

IndexedDB non è un database re­la­zio­na­le, ma un sistema di tabelle in­di­ciz­za­te. In realtà si tratta di un database NoSQL, come ad esempio MongoDB. Le voci sono sempre create in coppia: chiave e valore. Il valore è un oggetto e la chiave è la sua proprietà. A questi si ag­giun­go­no gli indici. Essi ve­lo­ciz­za­no la ricerca.

In IndexedDB le azioni sono sempre eseguite in forma di tran­sa­zio­ni. Ogni ope­ra­zio­ne di scrittura, lettura o modifica è integrata in una tran­sa­zio­ne. In tal modo si ga­ran­ti­sce che le modifiche al database vengano eseguite com­ple­ta­men­te o non vengano eseguite per niente. Un vantaggio di IndexedDB è che il tra­sfe­ri­men­to dei dati (nella maggior parte dei casi) non deve essere sin­cro­niz­za­to. Le ope­ra­zio­ni vengono eseguite in modo asincrono. Questo ga­ran­ti­sce che il web browser non si blocchi durante l'o­pe­ra­zio­ne e possa con­ti­nua­re ad essere uti­liz­za­to dal­l'u­ten­te.

La sicurezza gioca un ruolo im­por­tan­te in IndexedDB. È im­por­tan­te garantire che i siti web non accedano alle banche dati di altri siti web. A questo scopo, IndexedDB ha stabilito una Same-Origin-Policy: il dominio, il pro­to­col­lo del livello di ap­pli­ca­zio­ne e la porta devono essere gli stessi, al­tri­men­ti i dati non sono di­spo­ni­bi­li. Può succedere che le sot­to­car­tel­le di un dominio accedano all’IndexedDB di un’altra sot­to­car­tel­la, se hanno tutte la stessa origine. Tuttavia, l'accesso non è possibile se viene uti­liz­za­ta un'altra porta o se il pro­to­col­lo passa da HTTP a HTTPS o viceversa.

Tutorial su IndexedDB: come si usa

Vi spie­ghia­mo IndexedDB con un esempio. Prima di creare un database e un object store, bisogna in­stal­la­re un sistema di controllo. IndexedDB è ormai sup­por­ta­to da tutti i browser moderni, ma non è indicato per i web browser obsoleti. Per prima cosa occorre chiedersi, dunque, se IndexedDB è sup­por­ta­to dal browser. Ve­ri­fi­ca­te l’oggetto Window.

N.B.

È possibile tenere traccia degli esempi di codice sul pannello degli strumenti di sviluppo nel browser. Grazie agli strumenti, è inoltre possibile vi­sua­liz­za­re gli IndexedDB di altre pagine.

if (!window.indexedDB) {
    alert("IndexedDB wird nicht unterstützt!");
}

Se il browser del­l'u­ten­te non supporta IndexedDB, appare una finestra di dialogo che informa l'utente. In al­ter­na­ti­va, è possibile creare con console.error un messaggio di errore nel vostro file di log.

A questo punto si apre un database. In linea di principio un sito web può aprire diversi database, ma di fatto viene creato un IndexedDB per dominio. In questo modo si ha la pos­si­bi­li­tà di lavorare con diversi Object Stores. L'a­per­tu­ra di un database avviene in seguito a una richiesta, una richiesta asincrona.

var request = window.indexedDB.open("MeineDatenbank", 1);

All’apertura vengono indicati due elementi: prima il nome scelto (come una stringa) e poi il numero di versione (come integrità, quindi un numero intero). Si comincia, ov­via­men­te, con la versione 1. L’oggetto ri­sul­tan­te re­sti­tui­sce uno dei tre eventi:

  • error: si è ve­ri­fi­ca­to un errore durante l’apertura.
  • up­gra­de­nee­ded: la versione del database è cambiata. Esso compare anche all’apertura, perché anche in questo caso cambia il numero di versione: da ine­si­sten­te a 1.
  • success: il database può essere aperto con successo.

A questo punto è possibile creare il database vero e proprio e un object store.

request.onupgradeneeded = function(event) {
    var db = event.target.result;
    var objectStore = db.createObjectStore("Nutzer", { keyPath: "id", autoIncrement: true });
}

Il nostro object store ha il nome di utente. La chiave è id, una semplice nu­me­ra­zio­ne che au­men­tia­mo in modo costante con au­toIn­cre­ment. Quindi è possibile riempire il database e anche l’object store. Create prima di tutto uno o più indici. Nel nostro esempio, creiamo un indice per il nome utente e un indice per gli indirizzi e-mail uti­liz­za­ti.

objectStore.createIndex("Nickname", "Nickname", { unique: false });
objectStore.createIndex("eMail", "eMail", { unique: true });

In questo modo potete trovare fa­cil­men­te i record at­tra­ver­so lo pseu­do­ni­mo uti­liz­za­to di un utente o il suo indirizzo e-mail. I due indici si dif­fe­ren­zia­no per il fatto che il nickname non deve ne­ces­sa­ria­men­te essere assegnato solo una volta, mentre per ogni indirizzo e-mail può esistere una sola voce.

A questo punto è fi­nal­men­te possibile inserire le voci. Tutte le ope­ra­zio­ni con il database devono essere integrate in una tran­sa­zio­ne. Ne esistono tre:

  • readonly: legge i dati da un object store. Più tran­sa­zio­ni di questo tipo possono essere eseguite si­mul­ta­nea­men­te, anche se si ri­fe­ri­sco­no allo stesso settore.
  • readwrite: legge e crea voci. Queste tran­sa­zio­ni possono essere eseguite si­mul­ta­nea­men­te solo se si ri­fe­ri­sco­no a settori diversi.
  • ver­sion­chan­ge: apporta modifiche all’object store o agli indici, ma crea e modifica anche le voci. Questa modalità non può essere creata ma­nual­men­te, ma viene attivata au­to­ma­ti­ca­men­te dal­l'e­ven­to up­gra­de­nee­ded.

Per creare una nuova voce si utilizza readwrite.

const dbconnect = window.indexedDB.open('MeineDatenbank', 1);
dbconnect.onupgradeneeded = ev => {
    console.log('Upgrade DB');
    const db = ev.target.result;
    const store = db.createObjectStore('Nutzer', { keyPath: 'id', autoIncrement: true });
    store.createIndex('Nickname', 'Nickname', { unique: false });
    store.createIndex('eMail', 'eMail', { unique: true });
}
dbconnect.onsuccess = ev => {
    console.log('DB-Upgrade erfolgreich');
    const db = ev.target.result;
    const transaction = db.transaction('Nutzer', 'readwrite');
    const store = transaction.objectStore('Nutzer');
    const data = [
        {Nickname: 'Raptor123', eMail: 'raptor@example.com'},
        {Nickname: 'Dino2', eMail: 'dino@example.com'}
    ];
    data.forEach(el => store.add(el));
    transaction.onerror = ev => {
        console.error('Ein Fehler ist aufgetreten!', ev.target.error.message);
    };
    transaction.oncomplete = ev => {
        console.log('Daten wurden erfolgreich hinzugefügt!');
        const store = db.transaction('Nutzer', 'readonly').objectStore('Nutzer');
        //const query = store.get(1); // Einzel-Query
        const query = store.openCursor()
        query.onerror = ev => {
            console.error('Anfrage fehlgeschlagen!', ev.target.error.message);
        };
        /*
        // Verarbeitung der Einzel-Query
        query.onsuccess = ev => {
            if (query.result) {
                console.log('Datensatz 1', query.result.Nickname, query.result.eMail);
            } else {
                console.warn('Kein Eintrag vorhanden!');
            }
        };
        */
        query.onsuccess = ev => {
            const cursor = ev.target.result;
            if (cursor) {
                console.log(cursor.key, cursor.value.Nickname, cursor.value.eMail);
                cursor.continue();
            } else {
                console.log('Keine Einträge mehr vorhanden!');
            }
        };
    };
};

Con questa funzione inserite in­for­ma­zio­ni nel vostro object store. È inoltre possibile vi­sua­liz­za­re i messaggi sul pannello, a seconda del successo della tran­sa­zio­ne. I dati che sono stati inseriti in un IndexedDB, so­li­ta­men­te possono essere anche letti. A tal fine si utilizza get.

var transaction = db.transaction(["Nutzer"]);
var objectStore = transaction.objectStore("Nutzer");
var request = objectStore.get(1);
request.onerror = function(event) {
    console.log("Anfrage fehlgeschlagen!");
}
request.onsuccess = function(event) {
    if (request.result) {
        console.log(request.result.Nickname);
        console.log(request.result.eMail);
    } else {
        console.log("Kein Eintrag vorhanden!");
    }
};

Con questo codice cercate la voce con chiave 1, quindi con valore id 1. Se la tran­sa­zio­ne fallisce, viene generato un messaggio di errore. Se, invece, la tran­sa­zio­ne ha successo, sco­pri­re­te il contenuto delle due voci: nickname e e-mail. Se non è possibile trovare alcuna voce cor­ri­spon­den­te al numero, ri­ce­ve­re­te in­for­ma­zio­ni al riguardo.

Se non state cercando una sola voce, ma volete vi­sua­liz­zar­ne più di una, potete uti­liz­za­re un cursore. Questa funzione interroga una voce dopo l'altra. È possibile prendere in con­si­de­ra­zio­ne tutte le voci del database o se­le­zio­na­re solo un de­ter­mi­na­to in­ter­val­lo di chiavi.

var objectStore = db.transaction("Nutzer").objectStore("Nutzer");
objectStore.openCursor().onsuccess = function(event) {
    var cursor = event.target.result;
    if (cursor) {
        console.log(cursor.key);
        console.log(cursor.value.Nickname);
        console.log(cursor.value.eMail);
        cursor.continue();
    } else {
        console.log("Keine Einträge mehr vorhanden!");
    }
};

Abbiamo creato due indici, per poter accedere anche a queste in­for­ma­zio­ni. Anche in questo caso si utilizza get.

var index = objectStore.index("Nickname");
index.get("Raptor123").onsuccess = function(event) {
    console.log(event.target.result.eMail);
};

Infine, se si desidera eliminare una voce dal database, ciò si esegue in modo molto simile all’aggiunta di un record, con una tran­sa­zio­ne readwrite.

var request = db.transaction(["Nutzer"], "readwrite")
    .objectStore("Nutzer")
    .delete(1);
request.onsuccess = function(event) {
    console.log("Eintrag erfolgreich gelöscht!");
};
In sintesi

Questo articolo fornisce una prima in­tro­du­zio­ne a IndexedDB. Potete trovare ulteriori in­for­ma­zio­ni su Mozilla o Google. Google utilizza una libreria speciale per gli esempi di codice, motivo per cui si dif­fe­ren­zia par­zial­men­te da Mozilla.

Vai al menu prin­ci­pa­le