IndexedDB: tutorial per la memoria nel browser

La velocità gioca un ruolo importante quando si naviga nel web. A nessuno, infatti, piace aspettare un’eternità per il caricamento di una pagina. Perché una pagina web venga caricata più velocemente possibile, è opportuno che parte delle informazioni sia già presente nel computer dell’utente, senza dover essere nuovamente caricata. IndexedDB offre un modo per farlo: una memoria nel browser dell’utente, direttamente disponibile per ogni sito web. Come funziona?

Perché si utilizza IndexedDB?

È opportuno che non siano solo i server a memorizzare i dati dei client, ma che anche i client conservino dati specifici di un sito web. In questo modo la navigazione è più rapida, in quanto ogni volta che si visita un sito web non devono essere ricaricati tutti i dati. Così, le applicazioni web possono essere utilizzate anche offline. Anche gli input dell’utente si possono memorizzare lato client. Tra questi ci si riferisce soprattutto ai cookies. Tuttavia, essi hanno uno spazio e una capacità di informazione molto limitata, troppo piccola per le applicazioni 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 fortemente sull’idea di cookie, ma estende la dimensione da pochi kilobyte a 10 MB. Si tratta comunque di una dimensione ridotta. Inoltre, questi file, chiamati spesso supercookies, hanno una struttura molto semplice: è inutile provare a ricercare le proprietà di un database moderno. Tuttavia, la dimensione ridotta di cookies e supercookies non è l’unico motivo per il quale questi non rappresentano la soluzione giusta; entrambi i formati, infatti, non consentono un’organizzazione strutturata dei dati, né la creazione di indici, aspetti che ostacolano la ricerca.

Lo sviluppo di Web SQL ha introdotto in un certo un senso un cambiamento di direzione: una memoria lato client basata su SQL. Ma il World Wide Web Consortium (W3C), un'organizzazione per lo sviluppo di standard web, fermò il lavoro a favore di IndexedDB. Sotto la guida di Mozilla, è stato sviluppato uno standard che attualmente è supportato dalla maggior parte dei browser moderni.

Browser che supportano IndexedDB

Chrome

Firefox

Opera

Opera mini

Safari

IE

Edge

Cosa si può fare con IndexedDB?

Innanzitutto questo standard è un’interfaccia prevista dal browser. Tramite questa, le pagine web possono memorizzare le informazioni direttamente sul browser. Ciò avviene tramite JavaScript. In questo modo ogni sito web può crearsi un proprio database e solo il sito web corrispondente può accedere all'IndexedDB (abbreviazione di Indexed Database API). I dati, quindi, rimangono privati. Nei database sono disponibili diversi object storage. Anche in questo caso si possono utilizzare diversi formati: stringhe, numeri, oggetti, array e date.

IndexedDB non è un database relazionale, ma un sistema di tabelle indicizzate. 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 aggiungono gli indici. Essi velocizzano la ricerca.

In IndexedDB le azioni sono sempre eseguite in forma di transazioni. Ogni operazione di scrittura, lettura o modifica è integrata in una transazione. In tal modo si garantisce che le modifiche al database vengano eseguite completamente o non vengano eseguite per niente. Un vantaggio di IndexedDB è che il trasferimento dei dati (nella maggior parte dei casi) non deve essere sincronizzato. Le operazioni vengono eseguite in modo asincrono. Questo garantisce che il web browser non si blocchi durante l'operazione e possa continuare ad essere utilizzato dall'utente.

La sicurezza gioca un ruolo importante in IndexedDB. È importante 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 protocollo del livello di applicazione e la porta devono essere gli stessi, altrimenti i dati non sono disponibili. Può succedere che le sottocartelle di un dominio accedano all’IndexedDB di un’altra sottocartella, se hanno tutte la stessa origine. Tuttavia, l'accesso non è possibile se viene utilizzata un'altra porta o se il protocollo passa da HTTP a HTTPS o viceversa.

Tutorial su IndexedDB: come si usa

Vi spieghiamo IndexedDB con un esempio. Prima di creare un database e un object store, bisogna installare un sistema di controllo. IndexedDB è ormai supportato da tutti i browser moderni, ma non è indicato per i web browser obsoleti. Per prima cosa occorre chiedersi, dunque, se IndexedDB è supportato dal browser. Verificate 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 visualizzare gli IndexedDB di altre pagine.

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

Se il browser dell'utente non supporta IndexedDB, appare una finestra di dialogo che informa l'utente. In alternativa, è 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 possibilità di lavorare con diversi Object Stores. L'apertura 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, ovviamente, con la versione 1. L’oggetto risultante restituisce uno dei tre eventi:

  • error: si è verificato un errore durante l’apertura.
  • upgradeneeded: la versione del database è cambiata. Esso compare anche all’apertura, perché anche in questo caso cambia il numero di versione: da inesistente 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 numerazione che aumentiamo in modo costante con autoIncrement. 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 utilizzati.

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

In questo modo potete trovare facilmente i record attraverso lo pseudonimo utilizzato di un utente o il suo indirizzo e-mail. I due indici si differenziano per il fatto che il nickname non deve necessariamente essere assegnato solo una volta, mentre per ogni indirizzo e-mailpuò esistere una sola voce.

A questo punto è finalmente possibile inserire le voci. Tutte le operazioni con il database devono essere integrate in una transazione. Ne esistono tre:

  • readonly: legge i dati da un object store. Più transazioni di questo tipo possono essere eseguite simultaneamente, anche se si riferiscono allo stesso settore.
  • readwrite: legge e crea voci. Queste transazioni possono essere eseguite simultaneamente solo se si riferiscono a settori diversi.
  • versionchange: apporta modifiche all’object store o agli indici, ma crea e modifica anche le voci. Questa modalità non può essere creata manualmente, ma viene attivata automaticamente dall'evento upgradeneeded.

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 informazioni nel vostro object store. È inoltre possibile visualizzare i messaggi sul pannello, a seconda del successo della transazione. I dati che sono stati inseriti in un IndexedDB, solitamente 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 transazione fallisce, viene generato un messaggio di errore. Se, invece, la transazione ha successo, scoprirete il contenuto delle due voci: nickname e e-mail. Se non è possibile trovare alcuna voce corrispondente al numero, riceverete informazioni al riguardo.

Se non state cercando una sola voce, ma volete visualizzarne più di una, potete utilizzare un cursore. Questa funzione interroga una voce dopo l'altra. È possibile prendere in considerazione tutte le voci del database o selezionare solo un determinato intervallo 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 informazioni. 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 transazione 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 introduzione a IndexedDB. Potete trovare ulteriori informazioni su Mozilla o Google. Google utilizza una libreria speciale per gli esempi di codice, motivo per cui si differenzia parzialmente da Mozilla.


Abbiamo una proposta per te:
Web hosting a partire da 1 €/mese!

Dominio gratis
Certificato SSL Wildcard incluso
Assistenza clienti 24/7
A partire da 1 €/mese IVA escl. per un anno,
poi 8 €/ mese IVA escl.