Bygg Single Page Web Apps Med Sinatra Del 2

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

Sortere

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.


Merk som ferdig

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:

  1. Boksen er merket om fullstendig er sant.
  2. På klikk, kjør markAsComplete () funksjon fra foreldrene (TaskViewModel i dette tilfellet). Dette overfører automatisk den gjeldende oppgaven i løkken.

Slette oppgaver

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.


Slett alt fullført

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


Ufullstendige oppgaver som gjenstår

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:


Stil fullførte oppgaver

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 dens fullstendig eiendommen er ekte.


Oppryddatoer

La oss bli kvitt de stygge Ruby-datastrengene. Vi starter med å definere en dateformat fungere i vår TaskViewModel:

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 JavaScript Dato objekt med hjelp av MÅNEDER array. (Merk: det er ikke nødvendig å kapitalisere navnet på gruppen MÅ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 og updated_at eiendommer:

 

Dette går forbi created_at og updated_at egenskaper til dateformat () 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. De dateformat () 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 for task.isvisible eiendom. Hvis evalueringen er falsk, oppgaven er ikke funnet og isvisible eiendommen er satt til falsk. 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 at keyup Hendelsen er spesifikt identifisert som en valueUpdate begivenhet. Til slutt setter vi inn en manuell hendelse som bindes til keyup å 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!