Arbeta med IndexedDB - Del 3

Välkommen till slutlig en del av min IndexedDB-serie. När jag började denna serie var min avsikt att förklara en teknik som inte alltid är den mest ... vänliga en att arbeta med. I själva verket när jag först försökte arbeta med IndexedDB, förra året var min första reaktion något negativ ("Något negativ" som om universum är "något gammalt"). Det har varit en lång resa, men jag känner mig äntligen lite bekväm med IndexedDB och jag respekterar vad det tillåter. Det är fortfarande en teknik som inte kan användas överallt (det saknas misslyckades att läggas till iOS7), men jag tror verkligen att det är en teknik som kan lära sig och utnyttja idag.

I den här sista artikeln kommer vi att visa några ytterligare begrepp som bygger på den "fullständiga" demo som vi byggde i den senaste artikeln. För att vara tydlig, du måste fångas upp i serien eller det här är svårt att följa, så du kan också kolla in del ett.


Räkna data

Låt oss börja med något enkelt. Tänk dig att du vill lägga till personsökning till dina uppgifter. Hur skulle du få en räkning av dina data så att du kan hantera den funktionen ordentligt? Jag har redan visat dig hur du kan få Allt dina data och säkert du kan använda det som ett sätt att räkna data, men det kräver att allt hämtas. Om din lokala databas är stor kan det vara långsamt. Lyckligtvis ger IndexedDB-specifikationen ett mycket enklare sätt att göra det.

Metoden count (), som körs på ett objektStore, returnerar ett antal data. Liksom allt annat vi har gjort kommer detta att vara asynkront, men du kan förenkla koden till ett samtal. För vår notdatabas har jag skrivit en funktion som heter doCount () det gör just detta:

funktionen doCount () db.transaction (["note"], "readonly"). objektStore ("note"). count (). onsuccess = funktion (händelse) $ ("# sizeSpan"). "+ event.target.result +" Notes Total) "); ; 

Kom ihåg - om koden ovan är lite svår att följa kan du bryta upp den i flera kvarter. Se de tidigare artiklarna där jag demonstrerade detta. Resultathanteraren skickas ett resultatvärde som representerar det totala antalet objekt som finns i butiken. Jag ändrade användargränssnittet i vår demo för att inkludera en tom spänn i rubriken.

Obs Databas 

Det sista jag behöver göra är att helt enkelt lägga till ett samtal till doCount när programmet startar och efter någon tillägg eller radering. Här är ett exempel från framgångshanteraren för att öppna databasen.

openRequest.onsuccess = funktion (e) db = e.target.result; db.onerror = funktion (händelse) // Generisk felhanterare för alla fel som är inriktade på databasens // förfrågningar! varning ("Databasfel:" + event.target.errorCode); ; displayNotes (); doCount (); ;

Du kan hitta det fullständiga exemplet i zip du laddat ner som fulldemo2. (Som en FYI, fulldemo1 är ansökan som det var i slutet av föregående artikel.)


Filtrera som du skriver

För vår nästa funktion lägger vi till ett grundläggande filter i noteringslistan. I de tidigare artiklarna i denna serie behandlade jag hur IndexedDB gör inte tillåta gratisformulärsökning. Du kan inte (väl, inte lätt) söka innehåll som innehåller ett nyckelord. Men med kraften i intervall är det lätt att åtminstone stödja matchning i början av en sträng.

Om du kommer ihåg, tillåter en räckvidd oss ​​att ta tag i data från en butik som antingen börjar med ett visst värde, slutar med ett värde eller ligger däremellan. Vi kan använda detta för att implementera ett grundfilter mot titeln på våra noteringsfält. Först måste vi lägga till ett index för den här egenskapen. Kom ihåg att detta bara kan göras i den uppgraderade händelsen.

 om (! thisDb.objectStoreNames.contains ("note")) console.log ("Jag måste skapa noteringsobjektet"); objectStore = thisDb.createObjectStore ("notera", keyPath: "id", autoIncrement: true); objectStore.createIndex ("title", "title", unikt: false); 

Sedan lade jag till ett enkelt formulärfält till användargränssnittet:


Sedan lade jag till en "keyup" handlare till fältet så jag skulle se omedelbara uppdateringar medan jag skriver.

$ ("# filterField"). På ("keyup", funktion (e) var filter = $ (detta) .val (); displayNotes (filter););

Lägg märke till hur jag ringer på displaynotor. Det här är samma funktion som jag använde tidigare för att visa allt. Jag ska uppdatera den för att stödja både en "få allt" -åtgärd och en "få filtrerad" -typsåtgärd. Låt oss ta en titt på det.

funktion displayNotes (filter) var transaktion = db.transaction (["note"], "readonly"); var innehåll = ""; transaktion.oncomplete = funktion (händelse) $ (" # notislista "). html (innehåll);; varhandResultat = funktion (händelse) varare = event.target.result; if (cursor) content + = ""; innehåll + =""; innehåll + =""; innehåll + =""; cursor.continue (); else content + ="
TitelUppdaterad&
"+ Cursor.value.title +""+ DtFormat (cursor.value.updated) +"Redigera radera
";; var objectStore = transaction.objectStore (" notera "); om (filter) // Kredit: http://stackoverflow.com/a/8961462/52160 var range = IDBKeyRange.bound (filter, filter + "\ uffff"); var index = objectStore.index ("title"); index.openCursor (intervall) .onsuccess = handleResult; else objectStore.openCursor (). onsuccess = handleResult;

För att vara tydlig är den enda förändringen här längst ner. Att öppna en markör med eller utan ett intervall ger oss samma typ av händelsehanteringsresultat. Det är praktiskt då det gör den här uppdateringen så trivial. Den enda komplexa aspekten är att faktiskt bygga intervallet. Lägg märke till vad jag har gjort här. Inmatningen, filtret, är vad användaren skrev. Så föreställ dig att detta är "The". Vi vill hitta anteckningar med en titel som börjar med "The" och slutar i alla tecken. Detta kan göras genom att helt enkelt ställa in den översta delen av intervallet till ett högt ASCII-tecken. Jag kan inte ta kredit för denna idé. Se länken StackOverflow i koden för tillskrivning.

Du kan hitta denna demo i fulldemo3 mapp. Observera att det här använder en ny databas, så om du har kört tidigare exempel kommer den här att vara tom när du kör den först.

Medan detta fungerar har det ett litet problem. Föreställ dig en anmärkning med titeln "Saints Rule." (Eftersom de gör. Bara säger.) Du kommer sannolikt att försöka söka efter detta genom att skriva "saints". Om du gör det, fungerar inte filtret eftersom det är skiftlägeskänsligt. Hur tar vi oss runt det?

Ett sätt är att helt enkelt lagra en kopia av vår titel i små bokstäver. Det här är relativt enkelt att göra. Först ändrade jag indexet för att använda en ny egendom som heter titlelc.

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

Sedan ändrade jag koden som lagrar anteckningar för att skapa en kopia av fältet:

$ ("# saveNoteButton") på ("klicka", funktion () var title = $ ("# title") .val (); var body = $ ("# body"). = $ ("# key") .val (); var titlelc = title.toLowerCase (); var t = db.transaction (["notera"], "readwrite"); t.objectStore ("note") .add (title: title, body: body, uppdaterad: ny Datum (), titlelc: titlelc); else t.objectStore ("note"). titel, kropp: kropp, uppdaterad: ny datum (), id: nummer (nyckel), titlelc: titlelc);

Slutligen ändrade jag sökningen till helt enkelt små bokstäver. På så sätt om du anger "Saints" kommer det att fungera lika bra som att komma in i "heliga".

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

Det är allt. Du kan hitta den här versionen som fulldemo4.


Arbetar med Array Properties

För vår sista förbättring kommer jag att lägga till en ny funktion i vår Obs-applikation - märkning. Det här kommer att
Låt dig lägga till ett antal taggar (tänk sökord som beskriver noten) så att du senare kan hitta andra
anteckningar med samma tagg. Taggar kommer att lagras som en matris. Det är i sig inte så stor sak. Jag nämnde i början av denna serie att du enkelt kunde lagra arrays som egenskaper. Vad som är lite mer komplext är att hantera sökningen. Låt oss börja med att göra det så att du kan lägga till taggar i en anteckning.

Först ändrade jag min anteckningsblankett för att få ett nytt inmatningsfält. Detta gör det möjligt för användaren att skriva in taggar åtskilda av ett kommatecken:


Jag kan spara detta genom att helt enkelt uppdatera min kod som hanterar Notering skapande / uppdatering.

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

Observera att jag anger värdet till en tom matris. Jag fyller bara den om du har skrivit in något. Spara det här är så enkelt som att lägga till det objekt vi skickar till IndexedDB:

 om (nyckel === "") t.objectStore ("notera") .add (title: title, body: body, updated: new Date (), titlelc: titlelc, tags: tags);  other t.objectStore ("note") .put (title: title, body: body, updated: new Date (), id: Nummer (nyckel), titlelc: titlelc, tags: tags); 

Det är allt. Om du skriver några anteckningar och öppnar fliken Krämeres resurser kan du faktiskt se att data lagras.


Nu lägger vi till taggar i vyn när du visar en anteckning. För min ansökan bestämde jag mig för ett enkelt användningsfall för detta. När en anteckning visas, om det finns taggar ska jag lista dem ut. Varje tagg kommer att vara en länk. Om du klickar på den här länken visar jag dig en lista med relaterade anteckningar med samma tagg. Låt oss först titta på den logiken.

funktionen displayNote (id) var transaction = db.transaction (["notera"]); var objectStore = transaction.objectStore ("notera"); var request = objectStore.get (id); request.onsuccess = funktion (händelse) var note = request.result; var innehåll = "

"+ note.title +"

"; om (not.tags.length> 0) content + ="Tags: "; not.tags.forEach (funktion (elm, idx, arr) content + =" "+ elm +" ";); innehåll + ="
"; innehåll + ="

"+ note.body +"

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

Denna funktion (ett nytt tillägg till vår ansökan) hanterar noteringsskärmskoden formellt bunden till tabellcell-händelsen. Jag behövde en mer abstrakt version av koden så att detta uppfyller det syftet. För det mesta är det detsamma, men notera logiken för att kontrollera längden på taggaregenskapen. Om matrisen inte är tom uppdateras innehållet för att inkludera en enkel lista med taggar. Var och en är insvept i en länk med en viss klass som jag ska använda för uppslag senare. Jag har också lagt till en div specifikt för att hantera den sökningen.


Vid denna tidpunkt har jag möjlighet att lägga till taggar i en anteckning samt visa dem senare. Jag har också planerat att låta användaren klicka på dessa taggar så att de kan hitta andra anteckningar med samma tagg. Nu kommer den komplexa delen här.

Du har sett hur du kan hämta innehåll baserat på ett index. Men hur fungerar det med arrayegenskaper? Visas - specifikationen har en specifik flagg för att hantera detta: multiEntry. När du skapar ett array-baserat index måste du ställa in det här värdet till sant. Så här hanterar min ansökan det:

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

Det hanterar lagringsaspekten väl. Låt oss nu prata om sökning. Här är klickhanteraren för taglänkklassen:

$ (dokument) .on ("klicka", ".tagLookup", funktion (e) var tag = e.target.text; var parentNote = $ (this) .data ("noteid"); var doneOne = false; var innehåll = "Relaterade anteckningar:
"; var transaction = db.transaction ([" notera "]," readonly "); var objectStore = transaction.objectStore (" notera "); var tagIndex = objectStore.index (" tags "); varintervall = IDBKeyRange.only (tagg); transaction.oncomplete = funktion (händelse) if (! doneOne) content + = "Inga andra anteckningar använde denna tagg."; content + = "

"; $ (" # relatedNotesDisplay ") .html (innehåll);; var handleResult = funktion (händelse) var markör = event.target.result; if (cursor) if (cursor.value.id! = parentNote) doneOne = true; content + = ""+ cursor.value.title +"
"; cursor.continue ();; tagIndex.openCursor (intervall) .onsuccess = handleResult;);

Det finns en hel del här - men ärligt talat - det är väldigt likt det som vi tidigare dicussed. När du klickar på en tagg börjar min kod genom att ta tag i texten i länken för taggvärdet. Jag skapar min transaktion, objektbutik och indexobjekt som du tidigare sett. Området är nytt den här gången. I stället för att skapa ett intervall från något till något, kan vi använda den enda () api för att ange att vi vill ha en räckvidd av endast ett värde. Och ja - det verkade också konstigt för mig. Men det fungerar bra. Du kan se då öppnar vi markören och vi kan iterera över resultaten som tidigare. Det finns lite extra kod för att hantera fall där det inte finns några matchningar. Jag noterar också original- notera, det vill säga den du tittar nu, så att jag inte visar det också. Och det är verkligen det. Jag har en sista bit kod som hanterar klickhändelser på de relaterade anteckningarna så att du enkelt kan se dem:

$ (dokument) .on ("click", ".loadNote", funktion (e) var noteId = $ (detta) .data ("noteid"); displayNote (noteId););

Du kan hitta denna demo i mappen fulldemo5.


Slutsats

Jag hoppas verkligen att den här serien har varit till nytta för dig. Som sagt i början var IndexedDB inte en teknik jag tyckte om att använda. Ju mer jag arbetade med, och ju mer jag började vikla mitt huvud kring hur det gjorde saker, desto mer började jag uppskatta hur mycket denna teknik skulle kunna hjälpa oss som webbutvecklare. Det har definitivt plats att växa, och jag kan definitivt se att folk föredrar att använda wrappbibliotek för att förenkla saker, men jag tror att framtiden för den här funktionen är stor!