Lavorare con IndexedDB - Parte 3

Benvenuto a finale parte della mia serie IndexedDB. Quando ho iniziato questa serie, il mio intento era di spiegare una tecnologia che non è sempre la più ... amichevole con cui lavorare. In effetti, quando ho provato a lavorare con IndexedDB per la prima volta, l'anno scorso, la mia reazione iniziale era in qualche modo negativa ("Un po 'negativo", come l'Universo è "un po' vecchio"). È stato un lungo viaggio, ma finalmente mi sento un po 'a mio agio nel lavorare con IndexedDB e rispetto ciò che permette. È ancora una tecnologia che non può essere utilizzata ovunque (purtroppo non è stata aggiunta a iOS7), ma credo davvero che sia una tecnologia che le persone possono imparare e utilizzare oggi.

In questo articolo finale, mostreremo alcuni concetti aggiuntivi che si basano sulla demo "completa" che abbiamo creato nell'ultimo articolo. Per essere chiari, tu dovere essere coinvolti nella serie o questa voce sarà difficile da seguire, quindi è anche possibile controllare la prima parte.


Conteggio dei dati

Iniziamo con qualcosa di semplice. Immagina di voler aggiungere il paging ai tuoi dati. Come otterresti un conteggio dei tuoi dati in modo che tu possa gestire correttamente quella caratteristica? Ti ho già mostrato come puoi ottenere tutti i tuoi dati e sicuramente potresti usarli come un modo per contare i dati, ma ciò richiede il recupero di tutto. Se il tuo database locale è enorme, potrebbe essere lento. Fortunatamente la specifica IndexedDB fornisce un modo molto più semplice di farlo.

Il metodo count (), eseguito su objectStore, restituirà un conteggio dei dati. Come ogni altra cosa che abbiamo fatto, sarà asincrona, ma puoi semplificare il codice fino a una chiamata. Per il nostro database delle note, ho scritto una funzione chiamata doCount () questo fa proprio questo:

function doCount () db.transaction (["note"], "readonly"). objectStore ("note"). count (). onsuccess = function (evento) $ ("# sizeSpan"). text ("( "+ event.target.result +" Note totali) "); ; 

Ricorda: se il codice sopra è un po 'difficile da seguire, puoi suddividerlo in più blocchi. Vedi gli articoli precedenti dove ho dimostrato questo. Il gestore di risultati riceve un valore risultato che rappresenta il numero totale di oggetti disponibili nel negozio. Ho modificato l'interfaccia utente della nostra demo per includere un'estensione vuota nell'intestazione.

Note Database 

L'ultima cosa che devo fare è semplicemente aggiungere una chiamata a doCount quando l'applicazione si avvia e dopo ogni operazione di aggiunta o cancellazione. Ecco un esempio del gestore di successo per l'apertura del database.

openRequest.onsuccess = function (e) db = e.target.result; db.onerror = function (event) // Gestore di errori generico per tutti gli errori indirizzati alle richieste // di questo database! alert ("Errore database:" + event.target.errorCode); ; displayNotes (); doCount (); ;

Puoi trovare l'esempio completo nel file zip scaricato come fulldemo2. (Come una FYI, fulldemo1 è l'applicazione com'era alla fine dell'articolo precedente.)


Filtra durante la digitazione

Per la nostra prossima funzione, aggiungeremo un filtro base all'elenco delle note. Negli articoli precedenti di questa serie ho trattato come fa IndexedDB non consentire la ricerca di moduli gratuiti. Non puoi (bene, non facilmente) cercare contenuti che contiene una parola chiave. Ma con la potenza degli intervalli, è facile almeno supportare la corrispondenza all'inizio di una stringa.

Se ricordi, un intervallo ci consente di prendere i dati da un negozio che inizia con un certo valore, finisce con un valore o si trova in mezzo. Possiamo usarlo per implementare un filtro di base contro il titolo dei nostri campi di note. Per prima cosa, dobbiamo aggiungere un indice per questa proprietà. Ricorda, questo può essere fatto solo nell'evento onupgradeneeded.

 if (! thisDb.objectStoreNames.contains ("note")) console.log ("Devo rendere nota l'archivio oggetti"); objectStore = thisDb.createObjectStore ("note", keyPath: "id", autoIncrement: true); objectStore.createIndex ("title", "title", unique: false); 

Successivamente, ho aggiunto un campo modulo semplice all'interfaccia utente:


Poi ho aggiunto un gestore "keyup" al campo, quindi vedrei aggiornamenti immediati mentre scrivo.

$ ("# filterField"). on ("keyup", function (e) var filter = $ (this) .val (); displayNotes (filter););

Nota come sto chiamando displayNotes. Questa è la stessa funzione che ho usato prima per mostrare tutto. Lo aggiornerò per supportare sia un'azione "ottieni tutto" sia un'azione di tipo "get filtrato". Diamo un'occhiata a questo.

function displayNotes (filter) var transaction = db.transaction (["note"], "readonly"); var content = ""; transaction.oncomplete = function (event) $ (" # noteList "). html (content);; var handleResult = function (event) var cursor = event.target.result; if (cursor) content + = ""; contenuto + =""; contenuto + =""; contenuto + =""; cursor.continue (); else content + ="
Titoloaggiornato&
"+ Cursor.value.title +""+ DtFormat (cursor.value.updated) +"Modifica Elimina
";; var objectStore = transaction.objectStore (" note "); if (filter) // Credit: http://stackoverflow.com/a/8961462/52160 var range = IDBKeyRange.bound (filtro, filtro + "\ uffff"); var index = objectStore.index ("title"); index.openCursor (range) .onsuccess = handleResult; else objectStore.openCursor (). onsuccess = handleResult;

Per essere chiari, l'unico cambiamento qui è in basso. L'apertura di un cursore con o senza un intervallo ci dà lo stesso tipo di risultato del gestore di eventi. Questo è utile in quanto rende questo aggiornamento così banale. L'unico aspetto complesso è in realtà la costruzione della gamma. Notate cosa ho fatto qui. L'input, filter, è ciò che l'utente ha digitato. Quindi immagina che questo sia "The". Vogliamo trovare note con un titolo che inizia con "The" e finisce in qualsiasi personaggio. Questo può essere fatto semplicemente impostando il limite estremo dell'intervallo su un carattere ASCII alto. Non posso prendermi il merito di questa idea. Vedi il link StackOverflow nel codice per l'attribuzione.

Puoi trovare questa demo nel fulldemo3 cartella. Nota che questo sta usando un nuovo database, quindi se hai eseguito gli esempi precedenti, questo sarà vuoto quando lo esegui per la prima volta.

Mentre funziona, ha un piccolo problema. Immagina una nota intitolata "Regola dei santi". (Perché lo fanno. Solo dicendo.) Molto probabilmente proverai a cercare ciò digitando "santi". Se lo fai, il filtro non funzionerà perché è case sensitive. Come lo aggiriamo??

Un modo è semplicemente memorizzare una copia del nostro titolo in minuscolo. Questo è relativamente facile da fare. Innanzitutto, ho modificato l'indice per utilizzare una nuova proprietà chiamata titlelc.

 objectStore.createIndex ("titlelc", "titlelc", unique: false);

Quindi ho modificato il codice che memorizza le note per creare una copia del campo:

$ ("# saveNoteButton"). on ("click", function () var title = $ ("# title"). val (); var body = $ ("# body"). val (); chiave var = $ ("# chiave"). val (); var titlelc = title.toLowerCase (); var t = db.transaction (["note"], "readwrite"); if (chiave === "")  t.objectStore ("note") .add (title: title, body: body, updated: new Date (), titlelc: titlelc); else t.objectStore ("note") .put (title: title, body: body, updated: new Date (), id: Number (chiave), titlelc: titlelc);

Infine, ho modificato la ricerca semplicemente in minuscolo input dell'utente. In questo modo se inserisci "Saints" funzionerà altrettanto bene che inserire "santi".

 filter = filter.toLowerCase (); var range = IDBKeyRange.bound (filtro, filtro + "\ uffff"); var index = objectStore.index ("titlelc");

Questo è tutto. Puoi trovare questa versione come fulldemo4.


Funzionando con le proprietà degli array

Per il nostro miglioramento finale, aggiungerò una nuova funzionalità alla nostra applicazione Note: la codifica. Questo sarà
consente di aggiungere qualsiasi numero di tag (si pensi alle parole chiave che descrivono la nota) in modo da poterne trovare successivamente altri
note con lo stesso tag. I tag verranno archiviati come array. Questo di per sé non è un grosso problema. Ho menzionato all'inizio di questa serie che è possibile archiviare facilmente gli array come proprietà. Quello che è un po 'più complesso è gestire la ricerca. Iniziamo facendo in modo che tu possa aggiungere tag a una nota.

Innanzitutto, ho modificato il modulo della nota per avere un nuovo campo di input. Ciò consentirà all'utente di inserire tag separati da una virgola:


Posso salvare questo semplicemente aggiornando il mio codice che gestisce la creazione / aggiornamento di Note.

 var tag = []; var tagString = $ ("# tag"). val (); if (tagString.length) tags = tagString.split (",");

Si noti che sto impostando il valore predefinito su un array vuoto. Lo compilo solo se hai digitato qualcosa. Salvarlo è semplice come aggiungerlo all'oggetto che passiamo a IndexedDB:

 if (key === "") t.objectStore ("note") .add (title: title, body: body, updated: new Date (), titlelc: titlelc, tags: tags);  else t.objectStore ("note") .put (title: title, body: body, updated: new Date (), id: Number (chiave), titlelc: titlelc, tag: tags); 

Questo è tutto. Se scrivi alcune note e apri la scheda Risorse di Chrome, puoi effettivamente vedere i dati memorizzati.


Ora aggiungiamo tag alla vista quando visualizzi una nota. Per la mia applicazione, ho deciso un caso di utilizzo semplice per questo. Quando viene visualizzata una nota, se ci sono dei tag li elencherò. Ogni tag sarà un link. Se fai clic sul link, ti ​​mostrerò un elenco di note correlate che utilizzano lo stesso tag. Diamo un'occhiata a questa logica prima.

function displayNote (id) var transaction = db.transaction (["note"]); var objectStore = transaction.objectStore ("note"); var request = objectStore.get (id); request.onsuccess = function (event) var note = request.result; var content = "

"+ note.titolo +"

"; if (note.tags.length> 0) content + ="tag: "; note.tags.forEach (funzione (elm, idx, arr) content + =" "+ elm +" ";); content + ="
"; content + ="

"+ note.body +"

"; I $ noteDetail.html (contenuto) .show (); $ noteForm.hide ();;

Questa funzione (una nuova aggiunta alla nostra applicazione) gestisce il codice di visualizzazione delle note associato formalmente all'evento click della cella della tabella. Avevo bisogno di una versione più astratta del codice, in modo da soddisfare lo scopo. Per la maggior parte è lo stesso, ma si noti la logica per verificare la lunghezza della proprietà dei tag. Se l'array non è vuoto, il contenuto viene aggiornato per includere un semplice elenco di tag. Ognuno è avvolto in un collegamento con una particolare classe che userò per la ricerca più tardi. Ho anche aggiunto una div in particolare per gestire quella ricerca.


A questo punto, ho la possibilità di aggiungere tag a una nota e visualizzarli in seguito. Ho anche pianificato di consentire all'utente di fare clic su tali tag in modo che possano trovare altre note utilizzando lo stesso tag. Ora ecco che arriva la parte complessa.

Hai visto come puoi recuperare contenuti basati su un indice. Ma come funziona con le proprietà degli array? Risulta - la specifica ha una bandiera specifica per affrontare questo: multiEntry. Quando si crea un indice basato su array, è necessario impostare questo valore su true. Ecco come la mia applicazione lo gestisce:

objectStore.createIndex ("tags", "tags", unique: false, multiEntry: true);

Che gestisce bene l'aspetto dello storage. Ora parliamo di ricerca. Ecco il gestore di clic per la classe di collegamento tag:

$ (document) .on ("click", ".tagLookup", function (e) var tag = e.target.text; var parentNote = $ (this) .data ("noteid"); var doneOne = false; var content = "Note relative:
"; var transaction = db.transaction ([" note "]," readonly "); var objectStore = transaction.objectStore (" note "); var tagIndex = objectStore.index (" tag "); var range = IDBKeyRange.only (tag); transaction.oncomplete = function (event) if (! doneOne) content + = "Nessun'altra nota ha usato questo tag."; content + = "

"; $ (" # relatedNotesDisplay "). html (content);; var handleResult = function (event) var cursor = event.target.result; if (cursor) if (cursor.value.id! = parentNote) doneOne = true; content + = ""+ cursor.value.title +"
"; cursor.continue ();; tagIndex.openCursor (range) .onsuccess = handleResult;);

C'è un bel po 'qui - ma onestamente - è molto simile a quello che abbiamo discusso prima. Quando fai clic su un tag, il mio codice inizia afferrando il testo del link per il valore del tag. Creo la mia transazione, il mio magazzino oggetti e gli oggetti indice come hai visto prima. La gamma è nuova questa volta. Invece di creare un intervallo da qualcosa a qualcosa, possiamo usare l'unica () api per specificare che vogliamo un intervallo di un solo valore. E sì - mi è sembrato strano anche a me. Ma funziona benissimo. Puoi vedere quindi apriamo il cursore e possiamo scorrere i risultati come prima. C'è un po 'di codice aggiuntivo per gestire i casi in cui potrebbero non esserci corrispondenze. Prendo anche atto del originale nota, cioè quello che stai visualizzando ora, in modo da non visualizzarlo. E questo è davvero. Ho un ultimo bit di codice che gestisce gli eventi click su quelle note correlate in modo da poterli visualizzare facilmente:

$ (document) .on ("click", ".loadNote", function (e) var noteId = $ (this) .data ("noteid"); displayNote (noteId););

Puoi trovare questa demo nella cartella fulldemo5.


Conclusione

Spero sinceramente che questa serie ti sia stata utile. Come ho detto all'inizio, IndexedDB non era una tecnologia che mi piaceva usare. Più ci lavoravo e più ho iniziato a capire come funzionava, più ho iniziato ad apprezzare quanto questa tecnologia potesse aiutarci come sviluppatori web. Ha sicuramente spazio per crescere, e posso sicuramente vedere le persone che preferiscono usare le librerie di wrapper per semplificare le cose, ma penso che il futuro di questa funzione sia grande!