Heb je ooit al eens willen leren hoe je een app met één pagina kunt bouwen met Sinatra en Knockout.js? Nou, vandaag is de dag dat je leert! In dit eerste deel van een tweedelige serie bekijken we het proces voor het bouwen van een taaktoepassing met één pagina waar gebruikers hun taken kunnen bekijken, sorteren, markeren als voltooid, verwijderen, doorzoeken en toevoegen nieuwe taken.
Volgens hun website:
Sinatra is een DSL voor het snel maken van webtoepassingen in Ruby met minimale inspanning.
Met Sinatra kun je dingen doen, zoals:
krijg "/ task / new" do erb: form end
Dit is een route die GET-verzoeken voor "/ task / new" afgehandeld en een rendert erb
vorm met de naam form.erb
. We zullen Sinatra niet gebruiken om Ruby-sjablonen te maken; in plaats daarvan gebruiken we het alleen om JSON-antwoorden te verzenden naar onze Knockout.js managed front-end (en enkele hulpprogramma-functies van jQuery zoals $ .ajax
). We zullen erb alleen gebruiken om het hoofd-HTML-bestand te renderen.
Knockout is een JavaScript-raamwerk met een model-aanzicht-aanzichtmodel (MVVM) waarmee u uw modellen kunt bewaren in speciale "waarneembare" objecten. Het houdt ook uw UI up-to-date, gebaseerd op die waargenomen objecten.
-ToDo / -app.rb -models.rb --views / -index.erb - public / --- scripts / - knockout.js - jquery.js - app.js --- styles / - styles.css
Dit is wat je gaat bouwen:
We beginnen met het definiëren van ons model en vervolgens onze CRUD-acties in Sinatra. We vertrouwen op DataMapper en SQLite voor permanente opslag, maar u kunt elke gewenste ORM gebruiken.
Laten we een taakmodel toevoegen aan de models.rb
het dossier:
DataMapper.setup (: standaard, 'sqlite: ///path/to/project.db') class Task include DataMapper :: Resource-eigenschap: id, Serial property: complete, Boolean-eigenschap: description, Text property: created_at, DateTime property : updated_at, DateTime end DataMapper.auto_upgrade!
Dit taakmodel bestaat in wezen uit een paar verschillende eigenschappen die we willen manipuleren in onze to-do-applicatie.
Laten we vervolgens onze Sinatra JSON-server schrijven. In de app.rb
bestand, zullen we beginnen met het vereisen van een paar verschillende modules:
vereist 'rubygems' vereist 'sinatra' vereist 'data_mapper' vereist File.dirname (__ FILE__) + '/models.rb' vereist 'json' vereist 'Datum'
De volgende stap is het definiëren van enkele algemene standaardwaarden; in het bijzonder hebben we een MIME-type nodig dat met elk van onze antwoordheaders is verzonden om aan te geven dat elk antwoord JSON is.
daarvoor eindigt content_type 'application / json'
De voor
de helperfunctie werkt vóór elke routeaanpassing. U kunt ook bijpassende routes opgeven na voor
; als je bijvoorbeeld alleen JSON-antwoorden wilt uitvoeren als de URL eindigt op ".json", zou je dit gebruiken:
vóór% r . + \. json $ do content_type 'application / json' end
Vervolgens definiëren we onze CRUD-routes, evenals een route om ons te dienen index.erb
het dossier:
"/" doen content_type 'html' erb: index einde krijgen "/ tasks" doen @tasks = Task.all @ tasks.to_json end post "/ tasks / new" do @task = Task.new @ task.complete = false @ task.description = params [: description] @ task.created_at = DateTime.now @ task.updated_at = null end put "/ tasks /: id" do @task = Task.find (params [: id]) @task. complete = params [: compleet] @ task.description = params [: description] @ task.updated_at = DateTime.now if @ task.save : task => @task,: status => "success". to_json else : task => @task,: status => "failure". to_json end end delete "/ tasks /: id" do @task = Task.find (params [: id]) if @ task.destroy : task = > @task,: status => "succes". to_json else : task => @task,: status => "failure". to_json end end
Dus de app.rb
bestand ziet er nu als volgt uit:
vereist 'rubygems' vereist 'sinatra' vereist 'data_mapper' vereist File.dirname (__ FILE__) + '/models.rb' vereist 'json' vereist 'Date' voordat doen content_type 'application / json' end get "/" do content_type ' html 'erb: index end get "/ tasks" do @tasks = Task.all @ tasks.to_json end post "/ tasks / new" do @task = Task.new @ task.complete = false @ task.description = params [ : description] @ task.created_at = DateTime.now @ task.updated_at = null if @ task.save : task => @task,: status => "success". to_json else : task => @task,: status => "fout". to_json end end put "/ tasks /: id" do @task = Task.find (params [: id]) @ task.complete = params [: volledig] @ task.description = params [ : description] @ task.updated_at = DateTime.now if @ task.save : task => @task,: status => "success". to_json else : task => @task,: status => "failure" .to_json end end delete "/ tasks /: id" do @task = Task.find (params [: id]) if @ task.destroy : task => @task,: status => "success". to_json else : task => @task,: status => "failure" .to_json einde
Elk van deze routes wijst naar een actie. Er is slechts één weergave (de weergave "Alle taken") waarin elke actie is ondergebracht. Onthoud: in Ruby retourneert de uiteindelijke waarde impliciet. Je kunt expliciet vroeg terugkeren, maar welke inhoud deze routes ook retourneren, is het antwoord dat door de server wordt verzonden.
Vervolgens beginnen we met het definiëren van onze modellen in Knockout. In app.js
, plaats de volgende code:
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);
Zoals je ziet, zijn deze eigenschappen direct in kaart gebracht naar ons model in models.rb
. EEN ko.observable
houdt de waarde bijgewerkt in de gebruikersinterface wanneer deze verandert zonder afhankelijk te zijn van de server of op de DOM om de status bij te houden.
Vervolgens voegen we een toe TaskViewModel
.
function TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); $ .getJSON ("/ tasks", functie (onbewerkt) var tasks = $ .map (raw, function (item) retourneer nieuwe taak (item)); self.tasks (tasks);); ko.applyBindings (nieuw TaskListViewModel ());
Dit is het begin van wat het vlees van onze applicatie zal zijn. We beginnen met het maken van een TaskViewModel
constructorfunctie; een nieuw exemplaar van deze functie wordt doorgegeven aan de Knockout applyBindings ()
functie aan het einde van ons bestand.
In onze TaskViewModel
is een eerste oproep om taken uit de database op te halen, via de URL "/ tasks". Deze worden dan in kaart gebracht in de ko.observableArray
, die is ingesteld op t.tasks
. Deze array vormt de kern van de functionaliteit van onze applicatie.
Dus nu hebben we een ophaalfunctie die taken toont. Laten we een aanmaakfunctie maken en vervolgens onze eigenlijke sjabloonweergave maken. Voeg de volgende code toe aan de TaskViewModel
:
t.newTaskDesc = ko.observable (); t.addTask = function () var newtask = new Task (description: this.newTaskDesc ()); $ .getJSON ("/ getdate", functie (gegevens) newtask.created_at (data.date); newtask.updated_at (data.date); t.tasks.push (newtask); t.saveTask (newtask); t. newTaskDesc ("");); t.saveTask = functie (taak) var t = ko.toJS (task); $ .ajax (url: "http: // localhost: 9393 / tasks", type: "POST", data: t). done (function (data) task.id (data.task.id); );
Knockout biedt een handige iteratievermogen ...
Eerst stellen we in newTaskDesc
als een waarneembaar. Hierdoor kunnen we gemakkelijk een invoerveld gebruiken om een taakbeschrijving in te voeren. Vervolgens definiëren we onze Voeg taak toe()
functie, die een taak toevoegt aan de observableArray
; het noemt het saveTask ()
functie, waarbij het nieuwe taakobject wordt doorgegeven.
De saveTask ()
functie is agnostisch voor wat voor soort save het uitvoert. (Later gebruiken we de saveTask ()
functie om taken te verwijderen of ze als voltooid te markeren.) Een belangrijke opmerking hier: we vertrouwen op een handige functie om de huidige tijdstempel te bemachtigen. Dit zal niet het exact timestamp opgeslagen in de database, maar het biedt enkele gegevens die in de weergave kunnen worden geplaatst.
De route is heel eenvoudig:
krijg "/ getdate" do : date => DateTime.now .to_json end
Er moet ook worden opgemerkt dat de id van de taak niet is ingesteld totdat het Ajax-verzoek is voltooid, omdat we deze moeten toewijzen op basis van het antwoord van de server.
Laten we de HTML maken die onze nieuwe JavaScript-besturingselementen maken. Een groot deel van dit bestand is afkomstig van het HTML5 boilerplate-indexbestand. Dit gaat in de index.erb
het dossier:
Te doen Maak een nieuwe taak
Zoektaken
Nog niet voltooide taken:
Verwijder alle voltooide taken
DB ID Omschrijving Datum toegevoegd Datum gewijzigd Compleet? Verwijder X
Laten we deze sjabloon nemen en de bindingen invullen die Knockout gebruikt om de gebruikersinterface gesynchroniseerd te houden. Voor dit deel behandelen we het maken van To-Do-items. In deel twee behandelen we geavanceerdere functionaliteit (inclusief zoeken, sorteren, verwijderen en markeren als voltooid).
Laten we, voordat we verder gaan, onze pagina een beetje stijl geven. Aangezien deze zelfstudie niet over CSS gaat, laten we dit gewoon achterwege en gaan we meteen verder. De volgende code bevindt zich in het CSS-bestand HTML5 Boilerplate, dat een reset en een paar andere dingen bevat.
sectie breedte: 800px; marge: 20px auto; tabel width: 100%; th cursor: pointer; tr border-bottom: 1px solid #ddd; tr.complete, tr.complete: nth-child (oneven) background: # efffd7; kleur: #ddd; tr: nth-child (oneven) background-colour: #dedede; td opvulling: 10px 20px; td.destroytask background: #ffeaea; kleur: # 943c3c; lettertype: vet; opaciteit: 0,4; td.destroytask: hover cursor: pointer; achtergrond: #ffacac; kleur: # 792727; dekking: 1; .frequentie breedte: 50%; input background: #fefefe; vakschaduw: inzet 0 0 6px #aaa; opvulling: 6px; rand: geen; breedte: 90%; marge: 4px; invoer: focus outline: none; box-shadow: inzet 0 0 6px rgb (17, 148, 211); -webkit-overgang: 0.2s allemaal; achtergrond: rgba (17, 148, 211, 0,05); invoer [type = verzenden] achtergrondkleur: # 1194d3; achtergrondafbeelding: -webkit-gradiënt (lineair, linksboven, linksonder, van (rgb (17, 148, 211)), naar (rgb (59, 95, 142))); achtergrondafbeelding: -webkit-lineaire gradiënt (boven, rgb (17, 148, 211), rgb (59, 95, 142)); achtergrondafbeelding: -moz-lineaire gradiënt (bovenste, rgb (17, 148, 211), rgb (59, 95, 142)); achtergrondafbeelding: -o-lineaire gradiënt (top, rgb (17, 148, 211), rgb (59, 95, 142)); achtergrondafbeelding: -ms-lineaire gradiënt (top, rgb (17, 148, 211), rgb (59, 95, 142)); achtergrondafbeelding: lineaire gradiënt (boven, rgb (17, 148, 211), rgb (59, 95, 142)); filter: progid: DXImageTransform.Microsoft.gradient (GradientType = 0, StartColorStr = '# 1194d3', EndColorStr = "# 3b5f8e"); opvulling: 6px 9px; grensradius: 3px; kleur: #fff; tekstschaduw: 1px 1px 1px # 0a3d52; rand: geen; breedte: 30%; input [type = submit]: hover background: # 0a3d52; .floatleft float: left; .floatright float: right;
Voeg deze code toe aan uw styles.css
het dossier.
Laten we nu het formulier "nieuwe taak" behandelen. We zullen toevoegen data-bind
attributen aan het formulier om de Knockout-bindingen te laten werken. De data-bind
kenmerk is hoe Knockout de UI gesynchroniseerd houdt en zorgt voor event-binding en andere belangrijke functionaliteit. Vervang het formulier "nieuwe taak" door de volgende code.
Maak een nieuwe taak
We zullen deze één voor één bekijken. Ten eerste heeft het formulierelement een binding voor de voorleggen
evenement. Wanneer het formulier wordt ingediend, de Voeg taak toe()
functie gedefinieerd op de TaskViewModel
uitvoert. Het eerste invoerelement (dat impliciet van type = "tekst" is) bevat de waarde
van de ko.observable newTaskDesc
die we eerder hebben gedefinieerd. Wat zich ook op dit gebied voordoet bij het indienen van het formulier, wordt de taak Omschrijving
eigendom.
Dus we hebben een manier om taken toe te voegen, maar we moeten die taken weergeven. We moeten ook alle eigenschappen van de taak toevoegen. Laten we de taken opnieuw uitvoeren en deze aan de tabel toevoegen. Knockout biedt een gemakkelijk iteratievermogen om dit te vergemakkelijken; definieer een commentaarblok met de volgende syntaxis:
X
In Ruby is de uiteindelijke waarde impliciet rendement.
Dit maakt gebruik van de iteratiemogelijkheid van Knockout. Elke taak is specifiek gedefinieerd op de TaskViewModel
(t.tasks
) en blijft synchroon in de gebruikersinterface. De id van elke taak wordt pas toegevoegd nadat de DB-aanroep is voltooid (omdat er geen manier is om ervoor te zorgen dat we de juiste ID uit de database hebben totdat deze is geschreven), maar de interface hoeft geen inconsistenties als deze weer te geven.
Je zou nu in staat moeten zijn om te gebruiken shotgun app.rb
(gem installeren jachtgeweer
) uit uw werkmap en test uw app in de browser op http: // localhost: 9393. (Opmerking: zorg ervoor dat je hebt gem installeren
'al uw afhankelijkheden / vereiste bibliotheken voordat u uw toepassing probeert uit te voeren.) U zou taken moeten kunnen toevoegen en ze onmiddellijk zien verschijnen.
In deze zelfstudie leer je hoe je een JSON-interface met Sinatra maakt en vervolgens hoe je die modellen in Knockout.js kunt spiegelen. Je hebt ook geleerd hoe je bindingen kunt maken om onze gebruikersinterface te synchroniseren met onze gegevens. In het volgende deel van deze tutorial zullen we alleen over Knockout praten en uitleggen hoe je sorteer-, zoek- en bijwerkfunctionaliteit creëert.