Webpagina's met één pagina bouwen met Sinatra deel 2

In het eerste deel van deze miniserie hebben we de basisstructuur van een taak gemaakt met een Sinatra JSON-interface naar een SQLite-database en een front-end met knock-out waarmee we taken aan onze database kunnen toevoegen. In dit laatste deel behandelen we iets meer geavanceerde functionaliteit in Knockout, inclusief sorteren, zoeken, bijwerken en verwijderen.

Laten we beginnen waar we waren gebleven; hier is het relevante gedeelte van onze index.erb het dossier.


Soort

Sorteren is een veelvoorkomende taak die in veel toepassingen wordt gebruikt. In ons geval willen we de takenlijst sorteren op elk veld in onze takenlijst. We beginnen met het toevoegen van de volgende code aan de TaskViewModel:

t.gesorteerdBy = []; t.sort = function (field) if (t.sortedBy.length && t.sortedBy [0] == field && t.sortedBy [1] == 1) t.sortedBy [1] = 0; t.tasks.sort (functie (eerst, volgende) if (! volgende [veld]. call ()) return 1; return (volgende [veld] .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 biedt een sorteerfunctie voor waarneembare matrices

Eerst definiëren we een sortedBy array als een eigenschap van ons kijkmodel. Hiermee kunnen we opslaan of en hoe de verzameling is gesorteerd.

Het volgende is het soort() functie. Het accepteert a veld- argument (het veld dat we willen sorteren op) en controleert of de taken zijn gesorteerd op basis van het huidige sorteringsschema. We willen sorteren met behulp van een "schakel" type proces. Sorteer bijvoorbeeld één keer op omschrijving en de taken worden op alfabetische volgorde gerangschikt. Sorteer opnieuw op omschrijving en de taken schikken in omgekeerde alfabetische volgorde. Deze soort() functie ondersteunt dit gedrag door het meest recente sorteerschema te controleren en te vergelijken met wat de gebruiker wil sorteren.

Knockout biedt een sorteerfunctie voor waarneembare matrices. Het accepteert een functie als een argument dat bepaalt hoe de array moet worden gesorteerd. Deze functie vergelijkt twee elementen uit de array en retourneert 1, 0, of -1 als resultaat van die vergelijking. Alle dezelfde waarden zijn gegroepeerd (wat handig zal zijn om complete en onvolledige taken samen te groeperen).

Opmerking: de eigenschappen van de array-elementen moeten worden opgeroepen in plaats van eenvoudig worden geopend; deze eigenschappen zijn eigenlijk functies die de waarde van de eigenschap retourneren als deze zonder argumenten wordt aangeroepen.

Vervolgens definiëren we de bindingen op de tabelkoppen in onze mening.

DB ID Omschrijving Datum toegevoegd Datum gewijzigd Compleet? Verwijder

Met deze bindingen kan elk van de headers een sortering activeren op basis van de doorgegeven tekenreekswaarde; elk van deze kaarten is direct gekoppeld aan de Taak model-.


Markeren als voltooid

Vervolgens willen we een taak als voltooid kunnen markeren en we zullen dit doen door eenvoudigweg op het selectievakje voor een specifieke taak te klikken. Laten we beginnen met het definiëren van een methode in de TaskViewModel:

t.markAsComplete = function (task) if (task.complete () == true) task.complete (true);  else task.complete (false);  task._method = "put"; t.saveTask (taak); geef waar terug; 

De markAsComplete () methode accepteert de taak als een argument, dat automatisch wordt doorgegeven door Knockout bij het itereren over een verzameling items. We schakelen vervolgens de compleet eigendom, en voeg een toe ._method = "put" eigendom van de taak. Dit staat toe DataMapper om de HTTP te gebruiken LEGGEN werkwoord in tegenstelling tot POST. We gebruiken dan onze handige t.saveTask () methode om de wijzigingen in de database op te slaan. Eindelijk keren we terug waar omdat ze terugkeren vals voorkomt dat het selectievakje van status verandert.

Vervolgens veranderen we de weergave door het selectievakje in de taakkring te vervangen door het volgende:

Dit vertelt ons twee dingen:

  1. Het vakje is aangevinkt als compleet is waar.
  2. Op klik, voer de markAsComplete () functie van de ouder (TaskViewModel in dit geval). Hierdoor wordt de huidige taak automatisch in de lus doorgegeven.

Taken verwijderen

Om een ​​taak te verwijderen, gebruiken we eenvoudigweg een paar gemaksmethoden en bellen saveTask (). In onze TaskViewModel, voeg het volgende toe:

t.destroyTask = function (task) task._method = "delete"; t.tasks.destroy (taak); t.saveTask (taak); ;

Met deze functie wordt een eigenschap toegevoegd die lijkt op de "put" -methode voor het voltooien van een taak. De ingebouwde vernietigen() methode verwijdert de doorgegeven taak uit de waarneembare array. Eindelijk bellen saveTask () vernietigt de taak; dat is, zolang het ._methode is ingesteld op "verwijderen".

Nu moeten we onze mening wijzigen; voeg het volgende toe:

X

Dit komt qua functionaliteit overeen met het complete selectievakje. Merk op dat de class = "destroytask" is puur voor stylingdoeleinden.


Alles verwijderen voltooid

Vervolgens willen we de functie "Alle volledige taken verwijderen" toevoegen. Voeg eerst de volgende code toe aan de TaskViewModel:

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

Deze functie herhaalt eenvoudigweg de taken om te bepalen welke ervan compleet zijn en we noemen het destroyTask () methode voor elke volledige taak. Voeg in onze optiek het volgende toe voor de link "verwijder alles".

 0 "> Verwijder alle voltooide taken

Onze klikbinding werkt correct, maar we moeten definiëren completeTasks (). Voeg het volgende toe aan onze TaskViewModel:

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

Deze methode is een berekende eigendom. Deze eigenschappen retourneren een waarde die 'on the fly' wordt berekend wanneer het model wordt bijgewerkt. In dit geval retourneren we een gefilterde array die alleen volledige taken bevat die niet zijn gemarkeerd voor verwijdering. Vervolgens gebruiken we deze array's gewoon lengte eigenschap om de link "Verwijder alle voltooide taken" te verbergen of weer te geven.


Onvolledige taken blijven

Onze interface moet ook de hoeveelheid onvolledige taken weergeven. Gelijk aan onze completeTasks () functie hierboven, definiëren we een incompleteTasks () functie in TaskViewModel:

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

We hebben dan toegang tot deze berekende gefilterde array in onze mening, als volgt:

Nog niet voltooide taken:


Stijl voltooide taken

We willen de voltooide items anders indelen dan de taken in de lijst, en we kunnen dit in onze ogen doen met Knockout's css verbindend. Wijzig de tr openingstag in onze taak arrayForEach () loop naar het volgende.

 

Dit voegt een toe compleet CSS-klasse voor de tabelrij voor elke taak als deze compleet eigendom is waar.


Data opschonen

Laten we die lelijke Ruby-datakoorden verwijderen. We beginnen met het definiëren van een datumnotatie functie in onze TaskViewModel:

t.MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", " december "]; t.dateFormat = function (date) if (! date) retourneer "verversen om de serverdatum te zien";  var d = new Datum (datum); return d.getHours () + ":" + d.getMinutes () + "," + d.getDate () + "" + t.MONTHS [d.getMonth ()] + "," + d.getFullYear () ; 

Deze functie is redelijk eenvoudig. Als de datum om welke reden dan ook niet is gedefinieerd, hoeven we alleen de browser te vernieuwen om de datum in de initiaal in te voeren Taak ophaalfunctie. Anders maken we een door de mens leesbare datum met het gewone JavaScript Datum object met behulp van de MAANDEN matrix. (Opmerking: het is niet nodig om de naam van de array te kapitaliseren MAANDEN, natuurlijk; dit is gewoon een manier om te weten dat dit een constante waarde is die niet mag worden veranderd.)

Vervolgens voegen we de volgende wijzigingen toe aan onze weergave voor de gemaakt bij en updated_at eigenschappen:

 

Dit verstrijkt de gemaakt bij en updated_at eigenschappen van de datumnotatie() functie. Nogmaals, het is belangrijk om te onthouden dat eigenschappen van elke taak geen normale eigenschappen zijn; het zijn functies. Om hun waarde op te halen, moet u de functie aanroepen (zoals in het bovenstaande voorbeeld). Notitie: $ wortel is een sleutelwoord, gedefinieerd door Knockout, dat verwijst naar het ViewModel. De datumnotatie() methode, bijvoorbeeld, is gedefinieerd als een methode van de root ViewModel (TaskViewModel).


Taken zoeken

We kunnen onze taken op verschillende manieren doorzoeken, maar we houden de dingen eenvoudig en voeren een front-end zoekopdracht uit. Houd er echter rekening mee dat het waarschijnlijk is dat deze zoekresultaten door de database worden gegenereerd terwijl de gegevens groeien omwille van paginering. Maar voor nu, laten we onze definiëren zoeken() methode op TaskViewModel:

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);) return true;

We kunnen zien dat dit itereert door de reeks taken en controles om te zien of t.query () (een normale waarneembare waarde) staat in de taakomschrijving. Merk op dat deze controle daadwerkelijk in de setter functie voor de task.isvisible eigendom. Als de evaluatie is vals, de taak is niet gevonden en de is zichtbaar eigenschap is ingesteld op vals. Als de query gelijk is aan een lege tekenreeks, zijn alle taken ingesteld om zichtbaar te zijn. Als de taak geen beschrijving heeft en de query een niet-lege waarde is, maakt de taak geen deel uit van de geretourneerde gegevensset en is deze verborgen.

In onze index.erb bestand, stellen we onze zoekinterface in met de volgende code:

De invoerwaarde is ingesteld op de ko. waarneembare vraag. Vervolgens zien we dat het keyup evenement wordt specifiek geïdentificeerd als een valueUpdate evenement. Ten slotte hebben we een handmatige gebeurtenis gebonden aan keyup om de zoekopdracht uit te voeren (t.search ()) functie. Er is geen formulierinzending nodig; de lijst met overeenkomende items wordt weergegeven en kan nog steeds worden gesorteerd, verwijderd, enz. Daarom werken alle interacties altijd.


Eindcode

index.erb

          Te doen        

Maak een nieuwe taak

Zoektaken

Nog niet voltooide taken:

0 "> Verwijder alle voltooide taken
DB ID Omschrijving Datum toegevoegd Datum gewijzigd Compleet? Verwijder
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.gesorteerdBy = []; 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)  retourneer nieuwe taak (item)); t.tasks (taken);); t.incompleteTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function (task) ga terug (! task.complete () && task._method! = "delete"));); t.completeTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function ( taak) return (task.complete () && task._method! = "delete"));); // Operations t.dateFormat = function (date) if (! date) return "vernieuwen om server te zien datum "; var d = new Datum (datum); retourneer d.getHours () +": "+ d.getMinutes () +", "+ d.getDate () +" "+ t.MONTHS [d.getMonth ()] + "," + d.getFullYear (); t.addTask = function () var newtask = new Task (description: this.newTaskDesc ()); $ .getJSON ("/ getdate", function (gegevens) 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 (taak .description (). toLowerCase (). indexOf (t.query (). toLowerCase ())> = 0); else if (t.query () == "") task.isvisible (true); else task.isvisible (false);) return true;  t.sort = function (field) if (t.sortedBy.length && t.sortedBy [0] == field && t.sortedBy [1] == 1) t.sortedBy [1] = 0; t.tasks.sort (functie (eerst, volgende) if (! volgende [veld]. call ()) return 1; return (volgende [veld] .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());

Let op de herschikking van eigendomsverklaringen op de TaskViewModel.


Conclusie

Je hebt nu de technieken om complexere applicaties te maken!

Deze twee tutorials hebben je door het proces geleid van het maken van een applicatie met één pagina met Knockout.js en Sinatra. De applicatie kan gegevens schrijven en ophalen, via een eenvoudige JSON-interface, en heeft functies die verder gaan dan eenvoudige CRUD-acties, zoals massale verwijdering, sorteren en zoeken. Met deze hulpmiddelen en voorbeelden beschikt u nu over de technieken om veel complexere applicaties te maken!