I den første delen av denne mini-serien skapte vi grunnleggende strukturen i en gjøremålsprogram ved hjelp av et Sinatra JSON-grensesnitt til en SQLite-database, og en Knockout-powered front-end som lar oss legge til oppgaver i vår database. I denne siste delen skal vi dekke litt mer avansert funksjonalitet i Knockout, inkludert sortering, søking, oppdatering og sletting.
La oss starte hvor vi sluttet; her er den relevante delen av vår index.erb
fil.
Lag en ny oppgave
Søkeoppgaver
Ufullstendige oppgaver som gjenstår:
Slett alle komplette oppgaver
DB ID Beskrivelse Dato lagt til Dato endret Fullstendig? Slett X
Sortering er en vanlig oppgave som brukes i mange applikasjoner. I vårt tilfelle ønsker vi å sortere oppgavelisten ved et hvilket som helst toppfelt i vårt oppgaveoversiktstabell. Vi starter med å legge til følgende kode til TaskViewModel
:
t.sortedBy = []; t.sort = funksjon (felt) if (t.sortedBy.length && t.sortedBy [0] == felt && t.sortedBy [1] == 1) t.sortedBy [1] = 0; t.tasks.sort (funksjon (først, neste) hvis (! neste [felt] .call ()) return 1; returnere (neste [felt] .call () < first[field].call()) ? 1 : (next[field].call() == first[field].call()) ? 0 : -1; ); else t.sortedBy[0] = field; t.sortedBy[1] = 1; t.tasks.sort(function(first,next) if (!first[field].call()) return 1; return (first[field].call() < next[field].call()) ? 1 : (first[field].call() == next[field].call()) ? 0 : -1; );
Knockout gir en sorteringsfunksjon for observerbare arrays
Først definerer vi a sortedBy
array som en eiendom av vår visningsmodell. Dette tillater oss å lagre om og hvordan samlingen er sortert.
Neste er sortere()
funksjon. Den aksepterer a felt
argument (feltet vi vil sortere etter) og sjekker om oppgavene er sortert etter den nåværende sorteringsplanen. Vi ønsker å sortere ved hjelp av en "bytt" type prosess. For eksempel, sorter etter beskrivelse en gang, og oppgavene ordner i alfabetisk rekkefølge. Sorter etter beskrivelse igjen, og oppgavene ordner i omvendt alfabetisk rekkefølge. Dette sortere()
funksjonen støtter denne oppførselen ved å sjekke det siste sorteringsskjemaet og sammenligne det med hva brukeren vil sortere etter.
Knockout gir en sorteringsfunksjon for observerbare arrays. Den aksepterer en funksjon som et argument som styrer hvordan arrayet skal sorteres. Denne funksjonen sammenligner to elementer fra arrayet og returnerer 1
, 0
, eller -1
som et resultat av denne sammenligningen. Alle som verdier grupperes sammen (som vil være nyttig for å gruppere komplette og ufullstendige oppgaver sammen).
Merk: egenskapene til arrayelementene må kalles fremfor bare å få tilgang til; Disse egenskapene er faktisk funksjoner som returnerer verdien av eiendommen hvis de kalles uten argumenter.
Deretter definerer vi bindingene på tabelloverskriftene i vårt syn.
DB ID Beskrivelse Dato lagt til Dato endret Fullstendig? Slett
Disse bindingene tillater hver av topptekstene å utløse en sortering basert på den bestått strengverdien; hver av disse direkte kart til Oppgave
modell.
Deretter vil vi kunne markere en oppgave som fullført, og vi vil oppnå dette ved å bare klikke i avkrysningsruten som er knyttet til en bestemt oppgave. La oss begynne med å definere en metode i TaskViewModel
:
t.markAsComplete = funksjon (oppgave) hvis (task.complete () == true) task.complete (true); ellers task.complete (false); task._method = "put"; t.saveTask (oppgave); returnere sant;
De markAsComplete ()
Metoden aksepterer oppgaven som et argument, som automatisk overføres av Knockout når iterating over en samling av elementer. Vi bytter da fullstendig
eiendom, og legg til en ._method = "satt"
eiendom til oppgaven. Dette tillater DataMapper
å bruke HTTP SETTE
verb i motsetning til POST
. Vi bruker deretter vår praktiske t.saveTask ()
metode for å lagre endringene i databasen. Til slutt kommer vi tilbake ekte
fordi tilbake falsk
forhindrer at avmerkingsboksen endrer tilstand.
Deretter endrer vi visningen ved å erstatte boksekoden i oppgavens løkke med følgende:
Dette forteller oss to ting:
fullstendig
er sant.markAsComplete ()
funksjon fra foreldrene (TaskViewModel
i dette tilfellet). Dette overfører automatisk den gjeldende oppgaven i løkken. For å slette en oppgave, bruker vi bare noen få enkle bekvemmeligheter og samtaler saveTask ()
. I vår TaskViewModel
, legg til følgende:
t.destroyTask = funksjon (oppgave) task._method = "delete"; t.tasks.destroy (oppgave); t.saveTask (oppgave); ;
Denne funksjonen legger til en egenskap som ligner på "put" -metoden for å fullføre en oppgave. Den innebygde ødelegge()
Metoden fjerner innleveringsoppgaven fra det observerbare arrayet. Til slutt ringer saveTask ()
ødelegger oppgaven; det er så lenge som ._metode
er satt til å "slette".
Nå må vi endre vårt syn legg til følgende:
X
Dette er veldig likt i funksjonalitet til hele avkrysningsruten. Legg merke til at class = "destroytask"
er rent for styling formål.
Deretter vil vi legge til funksjonen "Slett alle komplette oppgaver". Først legger du til følgende kode i TaskViewModel
:
t.removeAllComplete = funksjon () ko.utils.arrayForEach (t.tasks (), funksjon (oppgave) hvis (task.complete ()) t.destroyTask (oppgave););
Denne funksjonen gjenkjenner bare oppgavene for å avgjøre hvilken av dem som er fullført, og vi kaller destroyTask ()
metode for hver fullstendig oppgave. I vårt syn legger du til følgende for koblingen "Slett alle komplette".
0 "> Slett alle komplette oppgaver
Våre klikkbinding fungerer riktig, men vi må definere completeTasks ()
. Legg til følgende i vår TaskViewModel
:
t.completeTasks = ko.computed (funksjon () return ko.utils.arrayFilter (t.tasks (), funksjon (oppgave) return (task.complete () && task._method! = "delete")); );
Denne metoden er a beregnet eiendom. Disse egenskapene returnerer en verdi som beregnes "på farten" når modellen er oppdatert. I dette tilfellet returnerer vi et filtrert array som inneholder bare komplette oppgaver som ikke er merket for sletting. Deretter bruker vi bare dette arrayet lengde
egenskap for å skjule eller vise koblingen "Slett alle ferdige oppgaver".
Grensesnittet vårt bør også vise mengden av ufullstendige oppgaver. Ligner på vår completeTasks ()
fungere over, definerer vi en incompleteTasks ()
fungere i TaskViewModel
:
t.incompleteTasks = ko.computed (funksjon () return ko.utils.arrayFilter (t.tasks (), funksjon (oppgave) return (! task.complete () && task._method! = "delete")) ;);
Vi får tilgang til dette beregnede filtrerte arrayet i vår visning, slik som dette:
Ufullstendige oppgaver som gjenstår:
Vi ønsker å stil de ferdige elementene annerledes enn oppgavene i listen, og vi kan gjøre dette i vår visning med Knockout css
bindende. Endre tr
åpner tag i vår oppgave arrayForEach ()
loop til følgende.
Dette legger til en
fullstendig
CSS klasse til tabellraden for hver oppgave hvis densfullstendig
eiendommen erekte
.
Oppryddatoer
La oss bli kvitt de stygge Ruby-datastrengene. Vi starter med å definere en
dateformat
fungere i vårTaskViewModel
:t.MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov" desember "]; t.dateFormat = funksjon (dato) hvis (! dato) return "oppdater for å se server dato"; var d = nytt dato (dato); returnere d.getHours () + ":" + d.getMinutes () + "," + d.getDate () + "" + t.MONTHS [d.getMonth ()] + "," + d.getFullYear ;Denne funksjonen er ganske enkel. Hvis datoen ikke er definert av en eller annen grunn, trenger vi bare å oppdatere nettleseren for å trekke inn datoen i begynnelsen
Oppgave
henter funksjon. Ellers oppretter vi en menneskelig lesbar dato med det enkle JavaScriptDato
objekt med hjelp avMÅNEDER
array. (Merk: det er ikke nødvendig å kapitalisere navnet på gruppenMÅNEDER
, selvfølgelig; dette er bare en måte å vite at dette er en konstant verdi som ikke bør endres.)Deretter legger vi til følgende endringer i vårt syn for
created_at
ogupdated_at
eiendommer:Dette går forbi
created_at
ogupdated_at
egenskaper tildateformat ()
funksjon. Igjen er det viktig å huske at egenskapene til hver oppgave ikke er normale egenskaper; de er funksjoner. For å hente verdien må du ringe funksjonen (som vist i eksempelet ovenfor). Merk:$ root
er et søkeord, definert av Knockout, som refererer til ViewModel. Dedateformat ()
Metoden, for eksempel, er definert som en metode for root ViewModel (TaskViewModel
).
Søkeoppgaver
Vi kan søke på våre oppgaver på en rekke måter, men vi vil holde ting enkelt og utføre et forkantssøk. Vær imidlertid oppmerksom på at det er sannsynlig at disse søkeresultatene vil bli database drevet etter hvert som dataene vokser for pagineringens skyld. Men for nå, la oss definere vår
Søke()
metode påTaskViewModel
:t.query = ko.observable ("); t.search = funksjon (oppgave) ko.utils.arrayForEach (t.tasks (), funksjon (oppgave) if (task.description () && t.query ! = "") task. visible (task.description (). toLowerCase (). indexOf (t.query (). toLowerCase ())> = 0); annet hvis (t.query () == "" ) task.isvisible (true); else task.isvisible (false);) return true;Vi kan se at dette gjenkjennes gjennom en rekke oppgaver og sjekker for å se om
t.query ()
(en vanlig observerbar verdi) er i oppgavebeskrivelsen. Merk at denne sjekken faktisk kjører inne i Settere funksjon fortask.isvisible
eiendom. Hvis evalueringen erfalsk
, oppgaven er ikke funnet ogisvisible
eiendommen er satt tilfalsk
. Hvis spørringen er lik en tom streng, er alle oppgaver satt til å være synlige. Hvis oppgaven ikke har en beskrivelse og spørringen er en tom verdi, er oppgaven ikke en del av det returnerte datasettet og er gjemt.I vår
index.erb
fil, satt vi opp vårt søkegrensesnitt med følgende kode:Inngangsverdien er satt til
ko.observable query
. Deretter ser vi atkeyup
Hendelsen er spesifikt identifisert som envalueUpdate
begivenhet. Til slutt setter vi inn en manuell hendelse som bindes tilkeyup
å utføre søket (t.search ()
) funksjon. Ingen skjema innsending er nødvendig; listen over matchende elementer vises og kan fortsatt sorteres, deles, etc. Derfor virker alle interaksjoner til enhver tid.
Endelig kode
index.erb
Å gjøre Lag en ny oppgave
Søkeoppgaver
Ufullstendige oppgaver som gjenstår:
0 "> Slett alle komplette oppgaver
DB ID Beskrivelse Dato lagt til Dato endret Fullstendig? Slett X app.js
funksjon Oppgave (data) this.description = ko.observable (data.description); this.complete = ko.observable (data.complete); this.created_at = ko.observable (data.created_at); this.updated_at = ko.observable (data.updated_at); this.id = ko.observable (data.id); this.isvisible = ko.observable (true); funksjon TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); t.newTaskDesc = ko.observable (); t.sortedBy = []; t.query = ko.observable ("); t.MONTHS = [" Jan "," Feb "," Mar "," Apr "," Mai "," Jun "," Jul "," Aug "," Sep "," Okt "," Nov "," Des "]; $ .getJSON (" http: // localhost: 9393 / tasks ", funksjon (rå) var tasks = $. returnere ny oppgave (oppgave)); t.tasks (oppgaver);); t.incompleteTasks = ko.computed (funksjon () returko.utils.arrayFilter (t.tasks (), funksjon (oppgave) (! task.complete () && task._method! = "delete"));); t.completeTasks = ko.computed (funksjon () return ko.utils.arrayFilter (t.tasks (), funksjon oppgave) return (task.complete () && task._method! = "delete"));; // Operations t.dateFormat = funksjon (dato) hvis (! dato) dato "; var d = ny dato (dato); retur d.getHours () +": "+ d.getMinutes () +", "+ d.getDate () +" "+ t.MONTHS [d.getMonth (): + "," + d.getFullYear (); t.addTask = funksjon () var newtask = ny Oppgave (beskrivelse: this.newTaskDesc ()); $ .getJSON ("/ getdate" (data) newtask.created_at (data.date); newtask.up dated_at (data.date); t.tasks.push (newtask); t.saveTask (newtask); t.newTaskDesc ( ""); ); t.search = funksjon (oppgave) ko.utils.arrayForEach (t.tasks (), funksjon (oppgave) if (task.description () && t.query ()! = "") task.isvisible .deLeser () .LastCase () .Oppl. (t.query () .LaVerCase ())> = 0); Ellers hvis (t.query () == "") task.isvisible (false);) returnere true; t.sort = funksjon (felt) if (t.sortedBy.length && t.sortedBy [0] == felt && t.sortedBy [1] == 1) t.sortedBy [1] = 0; t.tasks.sort (funksjon (først, neste) hvis (! neste [felt] .call ()) return 1; returnere (neste [felt] .call () < first[field].call()) ? 1 : (next[field].call() == first[field].call()) ? 0 : -1; ); else t.sortedBy[0] = field; t.sortedBy[1] = 1; t.tasks.sort(function(first,next) if (!first[field].call()) return 1; return (first[field].call() < next[field].call()) ? 1 : (first[field].call() == next[field].call()) ? 0 : -1; ); t.markAsComplete = function(task) if (task.complete() == true) task.complete(true); else task.complete(false); task._method = "put"; t.saveTask(task); return true; t.destroyTask = function(task) task._method = "delete"; t.tasks.destroy(task); t.saveTask(task); ; t.removeAllComplete = function() ko.utils.arrayForEach(t.tasks(), function(task) if (task.complete()) t.destroyTask(task); ); t.saveTask = function(task) var t = ko.toJS(task); $.ajax( url: "http://localhost:9393/tasks", type: "POST", data: t ).done(function(data) task.id(data.task.id); ); ko.applyBindings(new TaskViewModel());Legg merke til omleggingen av eiendomserklæringer på
TaskViewModel
.
Konklusjon
Du har nå teknikker for å skape mer komplekse applikasjoner!
Disse to opplæringsprogrammene har tatt deg gjennom prosessen med å lage et enkeltsidsprogram med Knockout.js og Sinatra. Programmet kan skrive og hente data, via et enkelt JSON-grensesnitt, og det har funksjoner utover enkle CRUD-handlinger, som masse sletting, sortering og søking. Med disse verktøyene og eksemplene har du nå teknikker for å skape mye mer komplekse applikasjoner!