Construirea de aplicații Web cu singură pagină cu Sinatra Partea 2

În prima parte a acestei mini-serii, am creat structura de bază a unei aplicații de rezolvat folosind o interfață Sinatra JSON la o bază de date SQLite și un front-end de tip Knockout care ne permite să adăugăm sarcini în baza noastră de date. În această parte finală, vom acoperi câteva funcții ușor mai avansate în Knockout, inclusiv sortarea, căutarea, actualizarea și ștergerea.

Să începem unde am rămas; aici este partea relevantă a noastră index.erb fişier.

Creați o nouă sarcină

Sarcini de căutare

Incomplete Sarcini rămase:

Ștergeți toate sarcinile complete
ID-ul DB Descriere Data adaugata Data modificata Complet? Șterge
X

Fel

Sortarea este o sarcină obișnuită folosită în multe aplicații. În cazul nostru, dorim să sortăm lista de sarcini cu orice câmp de antet în tabelul cu lista de sarcini. Vom începe prin adăugarea următorului cod la TaskViewModel:

t.sortedBy = []; t.sort = funcție (câmp) if (t.sortedBy.length && t.sortedBy [0] == câmp && t.sortedBy [1] == 1) t.sortedBy [1] = 0; ttasks.sort (funcția (primul, următorul) if (! next [field] .call ()) return 1; < 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 oferă o funcție de sortare pentru tablourile observabile

În primul rând, definim a sortate dupa matrice ca proprietate a modelului nostru de vizualizare. Aceasta ne permite să stocăm dacă și cum este sortată colecția.

Înainte este fel() funcţie. Acceptă a camp (câmpul pe care dorim să îl sortăm) și verifică dacă sarcinile sunt ordonate după schema curentă de sortare. Vrem să sortăm folosind un tip de proces "toggle". De exemplu, sortați după descriere o dată, iar sarcinile se aranjează în ordine alfabetică. Sortați după descriere din nou, iar sarcinile se aranjează în ordine inversă în ordine alfabetică. Acest fel() funcția acceptă acest comportament verificând cea mai recentă schemă de sortare și comparându-l cu ceea ce vrea să sorteze utilizatorul.

Knockout oferă o funcție de sortare pentru tablourile observabile. Acceptă o funcție ca argument care controlează modul de sortare a matricei. Această funcție compară două elemente din matrice și returnează 1, 0, sau -1 ca urmare a acestei comparații. Toate valorile similare sunt grupate împreună (care vor fi utile pentru gruparea sarcinilor complete și incomplete împreună).

Notă: proprietățile elementelor matrice trebuie să fie numite mai degrabă decât pur și simplu accesate; aceste proprietăți sunt de fapt funcții care returnează valoarea proprietății dacă este chemată fără argumente.

Apoi, definim legăturile de pe anteturile tabelului în opinia noastră.

ID-ul DB Descriere Data adaugata Data modificata Complet? Șterge

Aceste legături permit fiecărui antet să declanșeze un sortare pe baza valorii șirului transferat; fiecare dintre aceste hărți directe către Sarcină model.


Marchează ca completă

În continuare, dorim să putem marca o sarcină ca completă și vom realiza acest lucru prin simpla apăsare a casetei de selectare asociate unei anumite sarcini. Să începem prin definirea unei metode în TaskViewModel:

t.markAsComplete = funcție (sarcină) if (task.complete () == true) task.complete (true);  altceva task.complete (false);  task._method = "put"; t.saveTask (sarcină); return true; 

markAsComplete () metoda acceptă sarcina ca argument, care este trecut automat de Knockout atunci când iterăm peste o colecție de elemente. Atunci vom comuta complet proprietății și adăugați o ._method = "pus" proprietate la sarcină. Asta permite DataMapper pentru a utiliza HTTP A PUNE verb în contrast cu POST. Apoi vom folosi convenabilul nostru t.saveTask () pentru salvarea modificărilor în baza de date. În cele din urmă, ne întoarcem Adevărat pentru că se întorc fals împiedică caseta de selectare să schimbe starea.

Apoi, schimbăm vizualizarea înlocuind codul casetei de selectare din interiorul buclei de sarcini cu următoarele:

Acest lucru ne spune două lucruri:

  1. Caseta este verificată dacă complet este adevarat.
  2. La clic, executați markAsComplete () funcție de la părinte (TaskViewModel în acest caz). Aceasta trece automat sarcina curentă în buclă.

Ștergerea sarcinilor

Pentru a șterge o sarcină, pur și simplu folosim câteva metode de convenție și sunăm saveTask (). În a noastră TaskViewModel, adăugați următoarele:

t.destroyTask = funcție (sarcină) task._method = "delete"; t.tasks.destroy (sarcină); t.saveTask (sarcină); ;

Această funcție adaugă o proprietate similară metodei "pune" pentru finalizarea unei sarcini. Sistemul încorporat distruge() metoda elimină sarcina transmisă din matricea observabilă. În cele din urmă, sunând saveTask () distruge sarcina; care este, atâta timp cât ._metodă este setat la "delete".

Acum trebuie să ne modificăm punctul de vedere; adăugați următoarele:

X

Acest lucru este foarte asemănător funcțional cu caseta de selectare completă. Rețineți că class = "destroytask" este pur și simplu în scopuri de styling.


Ștergeți toate finalizate

Apoi, dorim să adăugăm funcția "ștergeți toate sarcinile complete". Mai întâi, adăugați următorul cod la TaskViewModel:

t.removeAllComplete = funcția () ko.utils.arrayForEach (t.tasks (), funcția (task) if (task.complete ()) t.destroyTask (task);); 

Această funcție doar repetă sarcinile pentru a determina care dintre ele sunt complete și numim destroyTask () pentru fiecare sarcină completă. În opinia noastră, adăugați următoarele pentru linkul "ștergeți totul".

 0 "> Ștergeți toate sarcinile complete

Legarea clicurilor va funcționa corect, dar trebuie să definim completeTasks (). Adăugați următoarele la adresa noastră TaskViewModel:

t.completeTasks = ko.computed (functie () returnati ko.utils.arrayFilter (t.tasks (), function (task) return (task.complete () && task._method! = "delete")); );

Această metodă este a calculat proprietate. Aceste proprietăți returnează o valoare care este calculată "în zbor" atunci când modelul este actualizat. În acest caz, vom returna o matrice filtrate care conține numai sarcini complete care nu sunt marcate pentru ștergere. Apoi, pur și simplu folosim matricea asta lungime să ascundeți sau să afișați linkul "Ștergeți toate lucrările finalizate".


Sarcini incomplete rămase

Interfața noastră ar trebui să afișeze și numărul de sarcini incomplete. Similar cu al nostru completeTasks () funcția de mai sus, definim un incompleteTasks () funcția în TaskViewModel:

t.incompleteTasks = ko.computed (function () returnați ko.utils.arrayFilter (t.tasks (), function (task) return (! task.complete () && task._method! = "delete" ;);

Apoi accesăm această matrice filtrate computerizată în opinia noastră, după cum urmează:

Incomplete Sarcini rămase:


Sarcini completate de stil

Vrem să stilizăm elementele completate diferit de sarcinile din listă și putem face acest lucru în opinia noastră cu Knockout's css legare. Modificați tr deschiderea etichetei în sarcina noastră arrayForEach () bucla la următoarele.

 

Acest lucru adaugă a complet Clasa CSS la rândul de masă pentru fiecare sarcină dacă este complet proprietatea este Adevărat.


Curățați datele

Să scăpăm de acele șiruri urâte de date Ruby. Vom începe prin definirea unui formatul datei în funcția noastră TaskViewModel:

t.MONTHS = ["Jan", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sep", " decembrie "]; t.dateFormat = funcția (data) if (! date) return "refresh to see date server";  var d = new Data (data); returnați d.getHours () + ":" + d.getMinutes () + "," + d.getDate () + "" + t.MONTHS [d.getMonth () ; 

Această funcție este destul de simplă. Dacă, din orice motiv, data nu este definită, pur și simplu trebuie să reîmprospătăm browserul pentru a trage în data de la data inițială Sarcină preluarea funcției. În caz contrar, vom crea o dată citibilă la om cu JavaScript simplu Data obiect cu ajutorul LUNI matrice. (Notă: nu este necesară capitalizarea numelui matricei LUNI, desigur; aceasta este pur și simplu o modalitate de a ști că aceasta este o valoare constantă care nu trebuie schimbată.)

Apoi, adăugăm următoarele modificări la vizualizarea noastră pentru creat la și updated_at proprietăţi:

 

Aceasta trece prin creat la și updated_at proprietăți la formatul datei() funcţie. Încă o dată, este important să rețineți că proprietățile fiecărei sarcini nu sunt proprietăți normale; acestea sunt funcții. Pentru a obține valoarea lor, trebuie să apelați funcția (după cum se arată în exemplul de mai sus). Notă: $ rădăcină este un cuvânt cheie, definit de Knockout, care se referă la ViewModel. formatul datei() metoda, de exemplu, este definită ca o metodă a funcției ViewModel rădăcină (TaskViewModel).


Sarcini de căutare

Ne putem căuta sarcinile într-o varietate de moduri, dar vom păstra lucrurile simple și vom efectua o căutare frontală. Rețineți, totuși, că este posibil ca aceste rezultate ale căutării să fie conduse de baze de date, pe măsură ce datele cresc de dragul paginării. Dar pentru moment, să definim noi căutare() metoda pe TaskViewModel:

t.query = ko.observable ("); t.search = funcția (task) ko.utils.arrayForEach (t.tasks (), function (task) if (task.description () && t.query ! () () () () () () () () ()  task.isvisible (true); altceva task.isvisible (false);) return true;

Putem vedea că acest lucru se repetă prin gama de sarcini și verificări pentru a vedea dacă t.query () (o valoare observabilă obișnuită) este în descrierea sarcinii. Rețineți că acest control rulează de fapt în interiorul prepelicar funcția pentru task.isvisible proprietate. În cazul în care evaluarea este fals, sarcina nu a fost găsită și este vizibil proprietatea este setată la fals. Dacă interogarea este egală cu un șir gol, toate sarcinile sunt setate să fie vizibile. Dacă sarcina nu are o descriere și interogarea este o valoare non-goală, sarcina nu face parte din setul de date returnat și este ascuns.

În a noastră index.erb fișier, am setat interfața noastră de căutare cu următorul cod:

Valoarea de intrare este setată la ko.observable query. Apoi vedem că keyup evenimentul este identificat în mod specific ca a valueUpdate eveniment. În cele din urmă, am stabilit un eveniment manual obligatoriu keyup pentru a executa căutarea (t.search ()). Nu este necesară depunerea formularului; lista de elemente de potrivire va fi afișată și poate fi în continuare selectată, ștergătoare etc. Prin urmare, toate interacțiunile funcționează în orice moment.


Codul final

index.erb

          A face        

Creați o nouă sarcină

Sarcini de căutare

Incomplete Sarcini rămase:

0 "> Ștergeți toate sarcinile complete
ID-ul DB Descriere Data adaugata Data modificata Complet? Șterge
X

app.js

funcția Task (date) this.description = ko.observable (date.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 (date.id); this.isvisible = ko.observable (adevărat);  funcția TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); t.newTaskDesc = ko.observabil (); t.sortedBy = []; t.query = ko.observable ("); t.MONTHS = [" Jan "," Feb "," Mar "," Apr "," Mai "," Iun "," Iul "," Aug " (Raw), funcția (element) , sarcina, funcția (raw),  returnați noi Task (item)); t.tasks (tasks);); t.incompleteTasks = ko.computed (funcție () return ko.utils.arrayFilter (t.tasks (), function ());)); t.completeTasks = ko.computed (functie () returnati ko.utils.arrayFilter (t.tasks (), function ( ()) return (task.complete () && task._method! = "delete"));); // Operații t.dateFormat = funcția (data) if (! date) return " date "; var d = new Data (data); returnați d.getHours () +": "+ d.getMinutes () +", "+ d.getDate ()) "+", "+" d.getFullYear (); t.addTask = function () var newtask = new Task (description: this.newTaskDesc (); (date) newtask.created_at (data.date); newtask.up dated_at (data.date); t.tasks.push (newtask); t.saveTask (newtask); t.newTaskDesc ( ""); ; t.search = funcția (task) ko.utils.arrayForEach (t.tasks (), funcția (task) if (task.description () && t.query ()! = "") task.isvisible .txt () () () () () () () () () task.isvisible (false);) return true;  t.sort = funcție (câmp) if (t.sortedBy.length && t.sortedBy [0] == câmp && t.sortedBy [1] == 1) t.sortedBy [1] = 0; ttasks.sort (funcția (primul, următorul) if (! next [field] .call ()) return 1; < 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());

Rețineți rearanjarea declarațiilor de proprietate pe TaskViewModel.


Concluzie

Acum aveți tehnicile pentru a crea aplicații mai complexe!

Aceste două tutoriale te-au dus în procesul de creare a unei aplicații cu o singură pagină cu Knockout.js și Sinatra. Aplicația poate scrie și prelua date, printr-o interfață simplă JSON, și are caracteristici care depășesc acțiunile CRUD simple, cum ar fi ștergerea în masă, sortarea și căutarea. Cu aceste instrumente și exemple, aveți acum tehnici pentru a crea aplicații mult mai complexe!