Werken met IndexedDB - Deel 3

Welkom bij de laatste onderdeel van mijn IndexedDB-serie. Toen ik aan deze serie begon, was het mijn bedoeling om een ​​technologie uit te leggen die niet altijd de meest ... vriendelijke is om mee te werken. Toen ik vorig jaar voor het eerst met IndexedDB werkte, was mijn eerste reactie enigszins negatief ("Somewhat negative"), net zoals het universum "enigszins oud" is). Het is een lange reis geweest, maar eindelijk voel ik me enigszins op mijn gemak bij het werken met IndexedDB en ik respecteer wat het toestaat. Het is nog steeds een technologie die niet overal kan worden gebruikt (helaas miste hij de toevoeging aan iOS7), maar ik geloof echt dat het een technologie is die mensen kunnen leren en vandaag kunnen gebruiken.

In dit laatste artikel zullen we enkele aanvullende concepten demonstreren die voortbouwen op de "volledige" demo die we in het laatste artikel hebben gebouwd. Voor de duidelijkheid, jij moet in de serie verstrikt raken of dit bericht moeilijk te volgen is, dus misschien wil je ook deel 1 bekijken.


Gegevens tellen

Laten we beginnen met iets eenvoudigs. Stel u voor dat u paging aan uw gegevens wilt toevoegen. Hoe zou u een telling van uw gegevens krijgen zodat u die functie op de juiste manier kunt afhandelen? Ik heb je al laten zien hoe je kunt komen allemaal uw gegevens en zeker u zou dat kunnen gebruiken als een manier om gegevens te tellen, maar daarvoor moet u alles ophalen. Als uw lokale database enorm is, kan dat langzaam zijn. Gelukkig biedt de IndexedDB-specificatie een veel eenvoudigere manier om het te doen.

De methode count (), uitgevoerd op een objectStore, retourneert een telling van gegevens. Zoals al het andere dat we hebben gedaan, is dit asynchroon, maar u kunt de code vereenvoudigen tot één oproep. Voor onze notitiedatabase heb ik een functie geschreven met de naam doCount () dat doet precies dit:

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

Onthoud - als de bovenstaande code een beetje moeilijk te volgen is, kunt u deze in meerdere blokken opsplitsen. Zie de eerdere artikelen waar ik dit heb laten zien. De resultaatafhandelaar krijgt een resultaatwaarde die het totale aantal beschikbare objecten in de winkel vertegenwoordigt. Ik heb de gebruikersinterface van onze demo aangepast om een ​​lege reeks in de koptekst op te nemen.

Notitie Database 

Het laatste wat ik moet doen, is gewoon een oproep toevoegen aan doCount wanneer de toepassing opstart en na elke toevoegings- of wisbewerking. Hier is een voorbeeld van de succeshandler voor het openen van de database.

openRequest.onsuccess = function (e) db = e.target.result; db.onerror = function (event) // Algemene foutafhandelaar voor alle fouten die zijn getarget op de // requests van deze database! alert ("Databasefout:" + event.target.errorCode); ; displayNotes (); doCount (); ;

Je vindt het volledige voorbeeld in de zip die je hebt gedownload als fulldemo2. (Als een FYI, fulldemo1 is de applicatie zoals deze was aan het einde van het vorige artikel.)


Filter tijdens het typen

Voor onze volgende functie gaan we een basisfilter toevoegen aan de notitielijst. In de eerdere artikelen in deze serie heb ik besproken hoe IndexedDB dat doet niet toestaan ​​voor gratis zoeken naar formulieren. Je kunt niet (goed, niet gemakkelijk) inhoud doorzoeken bevat een sleutelwoord. Maar met de kracht van ranges is het eenvoudig om op zijn minst matching aan het begin van een string te ondersteunen.

Als je je dat herinnert, kunnen we met een bereik gegevens van een winkel halen die begint met een bepaalde waarde, eindigt met een waarde of er tussenin ligt. We kunnen dit gebruiken om een ​​basisfilter te implementeren tegen de titel van onze notitievelden. Ten eerste moeten we een index toevoegen voor deze property. Vergeet niet dat dit alleen kan worden gedaan in het geval van on-gradedeeded.

 if (! thisDb.objectStoreNames.contains ("note")) console.log ("Ik moet de objectstore voor opmerkingen maken"); objectStore = thisDb.createObjectStore ("note", keyPath: "id", autoIncrement: true); objectStore.createIndex ("title", "title", unique: false); 

Vervolgens heb ik een eenvoudig formulierveld toegevoegd aan de gebruikersinterface:


Vervolgens heb ik een "key-up" -handler aan het veld toegevoegd, zodat ik tijdens het typen onmiddellijk updates zag.

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

Merk op hoe ik displayNotes aanroep. Dit is dezelfde functie die ik eerder heb gebruikt om alles weer te geven. Ik ga het updaten om zowel een 'alles krijgen'-actie als een actie' laten filteren 'te ondersteunen. Laten we er eens naar kijken.

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) inhoud + = ""; inhoud + =""; inhoud + =""; inhoud + =""; cursor.continue (); else content + ="
Titelbijgewerkt&
"+ Cursor.value.title +""+ DtFormat (cursor.value.updated) +"Bewerken verwijderen
";; var objectStore = transaction.objectStore (" note "); if (filter) // Krediet: http://stackoverflow.com/a/8961462/52160 var range = IDBKeyRange.bound (filter, filter + "\ uffff"); var index = objectStore.index ("title"); index.openCursor (bereik) .onsuccess = handleResult; else objectStore.openCursor (). onsuccess = handleResult;

Voor de duidelijkheid, de enige verandering hier is onderaan. Het openen van een cursor met of zonder een bereik geeft ons hetzelfde type gebeurtenishandlerresultaat. Dat is handig, want het maakt deze update zo triviaal. Het enige complexe aspect is het daadwerkelijk opbouwen van het bereik. Let op wat ik hier heb gedaan. De invoer, het filter, is wat de gebruiker heeft getypt. Stel je dus voor dat dit "De" is. We willen aantekeningen vinden met een titel die begint met "De" en eindigt in een willekeurig teken. Dit kan gedaan worden door simpelweg het verre einde van het bereik in te stellen op een hoog ASCII-teken. Ik kan dit idee niet in aanmerking nemen. Zie de StackOverflow-link in de code voor toeschrijving.

Je kunt deze demo vinden in de fulldemo3 map. Merk op dat dit een nieuwe database gebruikt, dus als je de vorige voorbeelden hebt uitgevoerd, is deze leeg als je hem voor het eerst uitvoert.

Hoewel dit werkt, heeft het een klein probleempje. Stel je een briefje voor met de titel: 'Heiligenregel'. (Omdat ze dat doen. Gewoon zeggen.) Hoogstwaarschijnlijk zul je proberen dit te zoeken door "heiligen" te typen. Als u dit doet, werkt het filter niet omdat het hoofdlettergevoelig is. Hoe komen we er omheen?

Eén manier is om gewoon een kopie van onze titel in kleine letters op te slaan. Dit is relatief eenvoudig om te doen. Eerst heb ik de index aangepast om een ​​nieuwe eigenschap genaamd te gebruiken titlelc.

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

Vervolgens heb ik de code aangepast die notities opslaat om een ​​kopie van het veld te maken:

$ ("# saveNoteButton"). on ("klik", functie () var title = $ ("# title"). val (); var body = $ ("# body"). val (); var key = $ ("# key"). val (); var titlelc = title.toLowerCase (); var t = db.transaction (["note"], "readwrite"); if (key === "")  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 (key), titlelc: titlelc);

Ten slotte heb ik de zoekopdracht aangepast om de gebruikersinvoer simpelweg kleiner te maken. Op die manier werkt het net zo goed als het invoeren van 'heiligen' als u 'heiligen' invoert.

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

Dat is het. Je kunt deze versie vinden als fulldemo4.


Werken met matrixeigenschappen

Voor onze laatste verbetering, ga ik een nieuwe functie toevoegen aan onze Note-applicatie - tagging. Dit zal
laat je een willekeurig aantal tags toevoegen (denk aan sleutelwoorden die de notitie beschrijven), zodat je later andere kunt vinden
notities met dezelfde tag. Tags worden als een array opgeslagen. Dat is op zichzelf niet zo'n groot probleem. Ik noemde aan het begin van deze serie dat je arrays gemakkelijk als eigenschappen kunt opslaan. Wat een beetje ingewikkelder is, is het afhandelen van de zoekopdracht. Laten we beginnen met het zo te maken dat je tags aan een notitie kunt toevoegen.

Eerst heb ik mijn notitievorm aangepast om een ​​nieuw invoerveld te hebben. Hierdoor kan de gebruiker tags invoeren gescheiden door een komma:


Ik kan dit opslaan door eenvoudigweg mijn code bij te werken die het aanmaken / bijwerken van aantekeningen verzorgt.

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

Merk op dat ik de waarde aan een lege array standaard. Ik vul het alleen in als je iets hebt ingetypt. Opslaan is net zo eenvoudig als het toevoegen aan het object dat we doorgeven aan 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 (key), titlelc: titlelc, tags: tags); 

Dat is het. Als u een paar notities schrijft en het tabblad Bronnen van Chrome opent, kunt u de gegevens die worden opgeslagen, daadwerkelijk zien.


Laten we nu tags toevoegen aan de weergave wanneer u een notitie weergeeft. Voor mijn aanvraag heb ik hiervoor een eenvoudige use case gekozen. Als er een notitie wordt weergegeven en er zijn tags, vermeld ik ze. Elke tag zal een link zijn. Als u op die link klikt, laat ik u een lijst met verwante opmerkingen zien met dezelfde tag. Laten we eerst naar die logica kijken.

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.title +"

"; if (note.tags.length> 0) content + ="Tags: "; note.tags.forElke (functie (iepen, idx, arr) inhoud + =" "+ iep +" ";); inhoud + ="
"; inhoud + ="

"+ note.body +"

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

Deze functie (een nieuwe toevoeging aan onze applicatie) handelt de notaweergavecode af die formeel is gebonden aan de tabelcelklikgebeurtenis. Ik had een meer abstracte versie van de code nodig, dus dit voldoet aan dat doel. Voor het grootste deel is het hetzelfde, maar let op de logica om de lengte van de eigenschap tags te controleren. Als de array niet leeg is, wordt de inhoud bijgewerkt met een eenvoudige lijst met tags. Elk is verpakt in een link met een bepaalde klasse die ik later zal gebruiken voor het opzoeken. Ik heb ook speciaal een div toegevoegd om die zoekopdracht af te handelen.


Op dit moment heb ik de mogelijkheid om tags aan een notitie toe te voegen en deze later weer te geven. Ik ben ook van plan om de gebruiker toe te staan ​​op die tags te klikken, zodat ze andere notities kunnen vinden met dezelfde tag. Nu komt hier het complexe gedeelte.

U hebt gezien hoe u inhoud kunt ophalen op basis van een index. Maar hoe werkt dat met array-eigenschappen? Blijkbaar - de specificatie heeft een specifieke vlag om hiermee om te gaan: multiEntry. Wanneer u een array-gebaseerde index maakt, moet u deze waarde op true instellen. Hier is hoe mijn applicatie het aanpakt:

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

Dat gaat goed met het opslagaspect om. Laten we het nu hebben over zoeken. Hier is de klikhandler voor de tagkoppelingklasse:

$ (document) .on ("klik", ".tagLookup", functie (e) var tag = e.target.text; var parentNote = $ (this) .data ("noteid"); var doneOne = false; var content = "Gerelateerde opmerkingen:
"; var transaction = db.transaction ([" note "]," readonly "); var objectStore = transaction.objectStore (" note "); var tagIndex = objectStore.index (" tags "); var range = IDBKeyRange.only (tag); transaction.oncomplete = function (event) if (! doneOne) content + = "Geen andere opmerkingen hebben deze tag gebruikt."; content + = "

"; $ (" # relatedNotesDisplay "). html (inhoud);; 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 (bereik) .onsuccess = handleResult;);

Er is nogal wat hier - maar eerlijk gezegd - het is erg vergelijkbaar met wat we eerder hebben besproken. Wanneer u op een tag klikt, begint mijn code met het pakken van de tekst van de link voor de tagwaarde. Ik maak mijn transactie-, objectwinkel- en indexobjecten zoals u eerder hebt gezien. Het bereik is deze keer nieuw. In plaats van een bereik van iets en iets te maken, kunnen we de enige () API gebruiken om aan te geven dat we een bereik van slechts één waarde willen. En ja - dat leek mij ook raar. Maar het werkt geweldig. Je kunt zien dat we de cursor openen en we kunnen de resultaten herhalen zoals eerder. Er is een beetje aanvullende code voor het afhandelen van gevallen waarin er mogelijk geen overeenkomsten zijn. Ik neem ook nota van de origineel opmerking, dat wil zeggen degene die u nu bekijkt, zodat ik het ook niet weergeef. En dat is het echt. Ik heb een laatste stukje code dat klikgebeurtenissen op die gerelateerde opmerkingen verwerkt, zodat u ze gemakkelijk kunt bekijken:

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

Je vindt deze demo in de map fulldemo5.


Conclusie

Ik hoop van harte dat deze serie je behulpzaam was. Zoals ik in het begin al zei, was IndexedDB geen technologie die ik graag gebruikte. Hoe meer ik ermee werkte en hoe meer ik begon te denken over hoe het dingen deed, des te meer begon ik te beseffen hoezeer deze technologie ons als webontwikkelaars zou kunnen helpen. Het heeft zeker ruimte om te groeien, en ik kan zeker zien dat mensen de voorkeur geven aan wrapper-bibliotheken om dingen te vereenvoudigen, maar ik denk dat de toekomst voor deze functie geweldig is!