Nella prima parte di questa mini-serie, abbiamo creato la struttura di base di un'applicazione to-do utilizzando un'interfaccia Sinatra JSON per un database SQLite e un front-end basato su Knockout che ci consente di aggiungere attività al nostro database. In questa parte finale, tratteremo alcune funzionalità leggermente più avanzate in Knockout, inclusi l'ordinamento, la ricerca, l'aggiornamento e l'eliminazione.
Iniziamo da dove eravamo rimasti; ecco la parte rilevante del nostro index.erb
file.
Crea una nuova attività
Cerca attività
Attività incomplete rimanenti:
Elimina tutte le attività complete
DB ID Descrizione Data aggiunta Data modificata Completare? Elimina X
L'ordinamento è un'attività comune utilizzata in molte applicazioni. Nel nostro caso, vogliamo ordinare l'elenco delle attività da qualsiasi campo di intestazione nella nostra tabella delle liste di attività. Inizieremo aggiungendo il seguente codice al TaskViewModel
:
t.sortedBy = []; t.sort = function (field) if (t.sortedBy.length && t.sortedBy [0] == field && t.sortedBy [1] == 1) t.sortedBy [1] = 0; t.tasks.sort (function (first, next) if (! next [campo] .call ()) return 1; return (next [field] .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 fornisce una funzione di ordinamento per matrici osservabili
Innanzitutto, definiamo a ordinato per
array come una proprietà del nostro modello di vista. Questo ci consente di memorizzare se e come la raccolta è ordinata.
Il prossimo è il ordinare()
funzione. Accetta a campo
argomento (il campo che vogliamo ordinare) e controlla se le attività sono ordinate secondo lo schema di ordinamento corrente. Vogliamo ordinare usando un tipo di "commutazione" del processo. Ad esempio, ordina per descrizione una volta e le attività sono organizzate in ordine alfabetico. Ordina di nuovo per descrizione, e le attività si organizzano in ordine alfabetico inverso. Questo ordinare()
la funzione supporta questo comportamento controllando lo schema di ordinamento più recente e confrontandolo con ciò che l'utente desidera ordinare.
Knockout fornisce una funzione di ordinamento per matrici osservabili. Accetta una funzione come argomento che controlla come deve essere ordinato l'array. Questa funzione confronta due elementi dell'array e restituisce 1
, 0
, o -1
come risultato di tale confronto. Tutti i valori simili sono raggruppati insieme (che sarà utile per raggruppare compiti completi e incompleti insieme).
Nota: le proprietà degli elementi dell'array devono essere richiamate anziché semplicemente accedute; queste proprietà sono in realtà funzioni che restituiscono il valore della proprietà se chiamato senza argomenti.
Successivamente, definiamo i collegamenti sulle intestazioni delle tabelle nella nostra vista.
DB ID Descrizione Data aggiunta Data modificata Completare? Elimina
Questi collegamenti consentono a ciascuna delle intestazioni di attivare un ordinamento in base al valore stringa passato; ognuna di queste mappe direttamente al Compito
modello.
Successivamente, vogliamo essere in grado di contrassegnare un'attività come completa e lo faremo semplicemente facendo clic sulla casella di controllo associata a un'attività specifica. Iniziamo definendo un metodo nel TaskViewModel
:
t.markAsComplete = function (task) if (task.complete () == true) task.complete (true); else task.complete (false); task._method = "put"; t.saveTask (task); ritorna vero;
Il markAsComplete ()
metodo accetta l'attività come argomento, che viene automaticamente passato da Knockout durante l'iterazione su una raccolta di elementi. Abbiamo quindi attivare il completare
proprietà e aggiungere un ._method = "put"
proprietà al compito. Questo permette DataMapper
usare l'HTTP METTERE
verbo al contrario di INVIARE
. Quindi usiamo il nostro conveniente t.saveTask ()
metodo per salvare le modifiche al database. Alla fine, torniamo vero
perché ritorno falso
impedisce al riquadro di cambiare stato.
Successivamente, cambiamo la vista sostituendo il codice della casella di controllo all'interno del ciclo attività con quanto segue:
Questo ci dice due cose:
completare
è vero.markAsComplete ()
funzione dal genitore (TaskViewModel
in questo caso). Questo passa automaticamente l'attività corrente nel ciclo. Per eliminare un'attività, utilizziamo semplicemente alcuni metodi di comodità e chiamate saveTask ()
. Nel nostro TaskViewModel
, aggiungere il seguente:
t.destroyTask = function (task) task._method = "delete"; t.tasks.destroy (task); t.saveTask (task); ;
Questa funzione aggiunge una proprietà simile al metodo "put" per il completamento di un'attività. Il built-in distruggere()
metodo rimuove l'attività inoltrata dall'array osservabile. Finalmente, chiamando saveTask ()
distrugge il compito; cioè, finché il ._metodo
è impostato su "cancella".
Ora dobbiamo modificare la nostra vista; aggiungere il seguente:
X
Questo è molto simile in funzionalità alla casella di controllo completa. Si noti che il class = "destroytask"
è puramente per scopi di stile.
Successivamente, vogliamo aggiungere la funzionalità "Elimina tutte le attività complete". Innanzitutto, aggiungi il seguente codice al TaskViewModel
:
t.removeAllComplete = function () ko.utils.arrayForEach (t.tasks (), function (task) if (task.complete ()) t.destroyTask (task););
Questa funzione semplicemente scorre le attività per determinare quali di esse sono complete, e chiamiamo il destroyTask ()
metodo per ogni compito completo. A nostro avviso, aggiungi quanto segue per il link "Elimina tutto completo".
0 "> Elimina tutte le attività complete
La nostra associazione di clic funzionerà correttamente, ma dobbiamo definire completeTasks ()
. Aggiungi il seguente al nostro TaskViewModel
:
t.completeTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function (task) return (task.complete () && task._method! = "delete")); );
Questo metodo è a calcolato proprietà. Queste proprietà restituiscono un valore che viene calcolato "al volo" quando il modello viene aggiornato. In questo caso, restituiamo un array filtrato che contiene solo attività complete che non sono contrassegnate per la cancellazione. Quindi, usiamo semplicemente questo array lunghezza
proprietà per nascondere o mostrare il link "Elimina tutte le attività completate".
La nostra interfaccia dovrebbe anche mostrare la quantità di compiti incompleti. Simile al nostro completeTasks ()
funzione sopra, definiamo un incompleteTasks ()
funzione in TaskViewModel
:
t.incompleteTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function (task) return (! task.complete () && task._method! = "delete")) ;);
Accederemo quindi a questo array filtrato calcolato nella nostra vista, in questo modo:
Attività incomplete rimanenti:
Vogliamo modellare gli elementi completati in modo diverso dalle attività nell'elenco, e possiamo farlo a nostro avviso con Knockout css
rilegatura. Modifica il TR
tag di apertura nel nostro compito arrayForEach ()
loop al seguente.
Questo aggiunge un
completare
Classe CSS per la riga della tabella per ogni attività, se presentecompletare
la proprietà èvero
.
Pulisci le date
Liberiamoci da quelle brutte stringhe di date di Ruby. Inizieremo definendo a
formato data
funzione nel nostroTaskViewModel
:t.MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", " Dec "]; t.dateFormat = function (date) if (! date) return "aggiorna per vedere la data del server"; var d = new Date (date); return d.getHours () + ":" + d.getMinutes () + "," + d.getDate () + "" + t.MONTHS [d.getMonth ()] + "," + d.getFullYear () ;Questa funzione è abbastanza semplice. Se per qualsiasi motivo la data non è definita, è sufficiente aggiornare il browser per inserire la data nell'iniziale
Compito
funzione di recupero. Altrimenti, creiamo una data leggibile dall'uomo con il semplice JavaScriptData
oggetto con l'aiuto delMESI
array. (Nota: non è necessario scrivere in maiuscolo il nome dell'arrayMESI
, ovviamente; questo è semplicemente un modo per sapere che questo è un valore costante che non dovrebbe essere modificato.)Successivamente, aggiungiamo le seguenti modifiche alla nostra vista per
created_at
eupdated_at
proprietà:Questo passa il
created_at
eupdated_at
proprietà alformato data()
funzione. Ancora una volta, è importante ricordare che le proprietà di ogni attività non sono proprietà normali; sono funzioni. Per recuperare il loro valore, è necessario chiamare la funzione (come mostrato nell'esempio precedente). Nota:$ radice
è una parola chiave, definita da Knockout, che fa riferimento a ViewModel. Ilformato data()
metodo, ad esempio, è definito come un metodo del ViewModel root (TaskViewModel
).
Ricerca di compiti
Possiamo cercare i nostri compiti in vari modi, ma manterremo le cose semplici ed eseguiremo una ricerca front-end. Tenere presente, tuttavia, che è probabile che questi risultati di ricerca vengano gestiti dal database man mano che i dati crescono per motivi di impaginazione. Ma per ora, definiamo il nostro
ricerca()
metodo attivoTaskViewModel
:t.query = ko.observable ("); t.search = function (task) ko.utils.arrayForEach (t.tasks (), function (task) if (task.description () && t.query () ! = "") task.isvisible (task.description (). toLowerCase (). indexOf (t.query (). toLowerCase ())> = 0); else if (t.query () == "" ) task.isvisible (true); else task.isvisible (false);) restituisce true;Possiamo vedere che questo itera attraverso l'array di attività e controlli per vedere se
t.query ()
(un valore osservabile regolare) è nella descrizione dell'attività. Si noti che questo controllo viene eseguito all'interno del file setter funzione per iltask.isvisible
proprietà. Se la valutazione èfalso
, il compito non è stato trovato e ilè visibile
la proprietà è impostata sufalso
. Se la query è uguale a una stringa vuota, tutte le attività sono impostate per essere visibili. Se l'attività non ha una descrizione e la query è un valore non vuoto, l'attività non fa parte del set di dati restituito ed è nascosta.Nel nostro
index.erb
file, abbiamo impostato la nostra interfaccia di ricerca con il seguente codice:Il valore di input è impostato su
query ko.osservabile
. Successivamente, vediamo che ilkeyup
l'evento è identificato in modo specifico come avalueUpdate
evento. Infine, impostiamo un binding di eventi manuale sukeyup
per eseguire la ricerca (t.search ()
) funzione. Non è necessaria la presentazione di moduli; l'elenco degli elementi corrispondenti verrà visualizzato e può essere ancora ordinabile, cancellabile, ecc. Pertanto, tutte le interazioni funzionano sempre.
Codice finale
index.erb
Fare Crea una nuova attività
Cerca attività
Attività incomplete rimanenti:
0 "> Elimina tutte le attività complete
DB ID Descrizione Data aggiunta Data modificata Completare? Elimina X app.js
function Task (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); function TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); t.newTaskDesc = ko.observable (); t.sortedBy = []; t.query = ko.observable ("); t.MONTHS = [" Jan "," Feb "," Mar "," Apr "," May "," Jun "," Jul "," Aug "," Sep "," Oct "," Nov "," Dec "]; $ .getJSON (" http: // localhost: 9393 / tasks ", function (raw) var tasks = $ .map (raw, function (item) return new Task (item)); t.tasks (tasks);); t.incompleteTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function (task) return (! task.complete () && task._method! = "delete"));); t.completeTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function ( task) return (task.complete () && task._method! = "delete"));); // Operations t.dateFormat = function (date) if (! date) return "aggiorna per vedere il server data "; var d = new Date (date); return d.getHours () +": "+ d.getMinutes () +", "+ d.getDate () +" "+ t.MONTHS [d.getMonth ()] + "," + d.getFullYear (); t.addTask = function () var newtask = new Task (description: this.newTaskDesc ()); $ .getJSON ("/ getdate", funzione (dati) newtask.created_at (data.date); newtask.up dated_at (data.date); t.tasks.push (newtask); t.saveTask (newtask); t.newTaskDesc ( ""); ); t.search = function (task) ko.utils.arrayForEach (t.tasks (), function (task) if (task.description () && t.query ()! = "") task.isvisible (task .description (). toLowerCase (). indexOf (t.query (). toLowerCase ())> = 0); else if (t.query () == "") task.isvisible (true); else task.isvisible (false);) restituisce true; t.sort = function (field) if (t.sortedBy.length && t.sortedBy [0] == field && t.sortedBy [1] == 1) t.sortedBy [1] = 0; t.tasks.sort (function (first, next) if (! next [campo] .call ()) return 1; return (next [field] .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());Notare il riarrangiamento delle dichiarazioni di proprietà sul
TaskViewModel
.
Conclusione
Ora hai le tecniche per creare applicazioni più complesse!
Questi due tutorial ti hanno illustrato il processo di creazione di un'applicazione a pagina singola con Knockout.js e Sinatra. L'applicazione può scrivere e recuperare dati, tramite una semplice interfaccia JSON, e ha caratteristiche che vanno al di là delle semplici azioni CRUD, come la cancellazione, l'ordinamento e la ricerca di massa. Con questi strumenti ed esempi, ora avete le tecniche per creare applicazioni molto più complesse!