Construirea de aplicații web cu o singură pagină cu Sinatra Partea 1

Ai vrut vreodată să înveți cum să construiești o aplicație cu o singură pagină cu Sinatra și Knockout.js? Astăzi este ziua în care înveți! În această primă secțiune a unei serii în două părți, vom examina procesul de creare a unei aplicații pentru o singură pagină, în care utilizatorii își pot vedea sarcinile, le pot sorta, le pot marca ca fiind complete, le pot șterge, căuta prin ele și pot adăuga noi sarcini.


Ce este Sinatra?

Potrivit site-ului lor:

Sinatra este un DSL pentru crearea rapidă a aplicațiilor web în Ruby, cu un efort minim.

Sinatra vă permite să faceți lucruri, cum ar fi:

obțineți "/ task / new" do erb: formularul final

Acesta este un traseu care gestionează cererile GET pentru "/ task / new" și redă un ERB formular numit form.erb. Nu vom folosi Sinatra pentru a da șabloane Ruby; în schimb, îl vom folosi numai pentru a trimite răspunsurile JSON la capătul frontal gestionat de Knockout.js (și câteva funcții de utilitate de la jQuery ca $ .ajax). Vom folosi erb doar pentru a reda fișierul HTML principal.


Ce este Knockout?

Knockout este un cadru JavaScript Model-View-ViewModel (MVVM) care vă permite să vă păstrați modelele în obiecte "observabile" speciale. De asemenea, vă menține UI actualizat, pe baza obiectelor observate.

-ToDo / -app.rb -models.rb --views / -index.erb - public / --- scripturi / - knockout.js - jquery.js - app.js --- stiluri / - stiles.css

Iată ce veți construi:

Vom începe prin definirea modelului nostru și apoi a acțiunilor noastre CRUD în Sinatra. Vom baza pe DataMapper și SQLite pentru stocare persistentă, dar puteți utiliza ORM pe care îl preferați.

Să adăugăm un model de sarcină la models.rb fişier:

 DataMapper.setup (: implicit, clasa 'sqlite: ///path/to/project.db') Task include DataMapper :: Resource property: id, Proprietatea serial: completă, proprietate booleană: descriere, proprietate Text: created_at, DateTime property : updated_at, DateTime end DataMapper.auto_upgrade!

Acest model de activitate constă, în esență, din câteva proprietăți diferite pe care vrem să le manipulăm în aplicația noastră de rezolvat.

Apoi, să scriem serverul nostru Sinatra JSON. În app.rb fișier, vom începe prin a solicita câteva module diferite:

 cere "rubygems" necesita 'sinatra' necesita 'data_mapper' necesita File.dirname (__FILE__) + '/models.rb' necesita 'json'

Următorul pas este definirea unor valori implicite globale; în special, avem nevoie de un tip MIME trimis cu fiecare antet de răspuns pentru a specifica că fiecare răspuns este JSON.

înainte de a face content_type 'application / json' end

inainte de funcția helper rulează înainte de fiecare potrivire a traseului. De asemenea, puteți specifica rute de potrivire după inainte de; dacă, de exemplu, v-ați dorit să rulați răspunsurile JSON numai dacă adresa URL s-a terminat cu ".json", ați folosi acest lucru:

înainte de% r . + \. json $ face content_type 'application / json' end

Apoi, definim rutele noastre CRUD, precum și o rută care ne servește index.erb fişier:

 "/ tasks" / "do / task = Task.new @ task.complete = false" / "a face content_type" html 'erb: @ task.description = paramuri [: descriere] @ task.created_at = DateTime.now @ task.updated_at = null end put "/ tasks /: id" nu @task = Task.find (params [: id]) @task. complete = paramale [: complete] @ task.description = params [: descriere] @ task.updated_at = DateTime.now dacă @ task.save : task => @task,: status = : task => @task,: status => "esec" to_json end end delete "/ tasks /: id" > @task,: status => "succes" to_json else : task => @task,: status => "eșec".

Asa ca app.rb fișierul arată acum astfel:

 '' require '' cer 'impune' data_mapper 'necesita' data_mapper 'necesita File.dirname (__ FILE__) +' /models.rb ' html 'erb: end end get' / tasks 'do @tasks = Task.all @ tasks.to_json sfarsit post "/ tasks / new" do @ task = Task.new @ task.complete = false @ task.description = : descriere] @ task.created_at = DateTime.now @ task.updated_at = null dacă @ task.save : task => @task,: status => "succes") to_json else : task => @task,: (param [: id]) @ task.complete = params [: complete] @ task.description = paramale [ : descriere] @ task.updated_at = DateTime.now dacă @ task.save : task => @task,: status => "succes".  .to_json sfârșitul final șterge "/ tasks /: id" face @task = Task.find (params [: id]) dacă @ task.destroy : task => @task,: status => "succes". altceva : task => @task,: status => "eșec" .to_json sfârșitul final

Fiecare dintre aceste rute găsește o acțiune. Există o singură vizualizare (viziunea "toate sarcinile") care găzduiește fiecare acțiune. Rețineți: în Ruby, valoarea finală revine implicit. Vă puteți întoarce în mod explicit devreme, dar indiferent de conținutul acestor rute înapoi va fi răspunsul trimis de server.


Knockout: Modele

Apoi, începem prin definirea modelelor noastre în Knockout. În app.js, plasați următorul cod:

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

După cum puteți vedea, aceste proprietăți sunt cartografiate direct în modelul nostru models.rb. A ko.observable păstrează valoarea actualizată de-a lungul UI atunci când se modifică fără a trebui să se bazeze pe server sau pe DOM pentru a ține evidența stării sale.

Apoi, vom adăuga a TaskViewModel.

 funcția TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); $ .getJSON ("/ tasks", funcția (raw) var tasks = $ .map (raw, function (item) retur Task (item));  ko.applyBindings (nou TaskListViewModel ());

Acesta este începutul a ceea ce va fi carnea cererii noastre. Începem prin crearea unui TaskViewModel funcția constructorului; o nouă instanță a acestei funcții este trecută la Knockout applyBindings () funcție la sfârșitul fișierului nostru.

În interiorul nostru TaskViewModel este un apel inițial pentru a prelua sarcini din baza de date, prin intermediul adresei URL "/ tasks". Acestea sunt apoi cartografiate în ko.observableArray, care este setat la t.tasks. Această matrice reprezintă nucleul funcționalității aplicației noastre.

Deci, acum, avem o funcție de recuperare care arată sarcinile. Să facem o funcție de creare și apoi să creăm vizualizarea noastră reală a șablonului. Adăugați următorul cod la TaskViewModel:

 t.newTaskDesc = ko.observabil (); t.addTask = funcția () var newtask = task nou (description: this.newTaskDesc ()); $ .getJSON ("/ getdate", funcția (data) newtask.created_at (data.date); newtask.updated_at (data.date); t.tasks.push (newtask); t.saveTask (newtask); newTaskDesc ("");); t.saveTask = funcție (sarcină) var t = ko.toJS (sarcină); $ .ajax (url: "http: // localhost: 9393 / tasks", tastați: "POST", data: t) done (function (data) task.id (data.task.id) ); 

Knockout oferă o abilitate de iterație convenabilă ...

În primul rând, am stabilit newTaskDesc ca observabilă. Acest lucru ne permite să folosim cu ușurință un câmp de introducere pentru a introduce o descriere a sarcinii. Apoi, ne definim addTask () care adaugă o sarcină la observableArray; se cheamă saveTask () funcția, trecând în noul obiect de activitate.

saveTask () funcția este agnostică a felului în care o salvează. (Mai târziu, folosim saveTask () pentru a șterge sarcini sau pentru a le marca ca fiind complete.) O notă importantă aici: ne bazăm pe o funcție convenabilă pentru a apuca amprenta curentă curentă. Aceasta nu va fi exact marcajul temporal salvat în baza de date, dar oferă unele date care să scadă în vizualizare.

Traseul este foarte simplu:

obține "/ getdate" do : date => DateTime.now .to_json sfârșit

De asemenea, trebuie remarcat faptul că ID-ul sarcinii nu este setat până când cererea Ajax nu se termină, deoarece trebuie să o atribuim pe baza răspunsului serverului.

Să creăm codul HTML pe care noile controale JavaScript le-au creat. O mare parte din acest fișier provine din fișierul index de fișiere HTML5. Aceasta intră în index.erb fişier:

           A face        

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

Să luăm acest șablon și să completăm legăturile pe care le folosește Knockout pentru a păstra sincronizarea UI. Pentru această parte, acoperim crearea de articole de rezolvat. În partea a doua, vom acoperi funcționalități mai avansate (inclusiv căutarea, sortarea, ștergerea și marcarea ca fiind complete).

Înainte de a merge mai departe, să oferim paginii noastre un pic de stil. Deoarece acest tutorial nu se referă la CSS, vom renunța și vom trece imediat. Următorul cod este în interiorul fișierului CSS HTML5 Boilerplate, care include o resetare și câteva alte lucruri.

 secțiune width: 800px; margine: 20 pixeli automat;  tabel lățime: 100%;  cursorul: indicatorul;  tr frontieră-partea de jos: 1px solid #ddd;  tr.complete, tr.complete: nth-child (nui adevărat) background: # efffd7; culoare: #ddd;  tr: nth-child (nui adevărat) background-color: #dedede;  td padding: 10px 20px;  td.destroytask background: #ffeaea; culoare: # 943c3c; font-weight: bold; opacitate: 0,4;  td.destroytask: hover cursor: pointer; fundal: #ffacac; culoare: # 792727; opacitate: 1; . cincizeci (lățime: 50%;  intrare background: #fefefe; box-shadow: inserție 0 0 6px # aaa; umplutura: 6px; frontieră: nici una; lățime: 90%; margine: 4px;  intrare: focalizare outline: none; box-shadow: inserție 0 0 6px rgb (17, 148, 211); -webkit-transition: 0.2s toate; fundal: rgba (17, 148, 211, 0,05);  input [type = submit] culoarea fundalului: # 1194d3; fundal-imagine: -webkit-gradient (liniuță, stânga sus, stânga jos, de la (rgb (17, 148, 211)) la (rgb (59,95,142)); fundal-imagine: -webkit-gradient linear (top, rgb (17, 148, 211), rgb (59, 95, 142)); fundal-imagine: -moz-linear-gradient (top, rgb (17, 148, 211), rgb (59, 95, 142)); fundal-imagine: -o-gradient linear (top, rgb (17, 148, 211), rgb (59, 95, 142)); imagini de fundal: -ms-gradient linear (top, rgb (17, 148, 211), rgb (59, 95, 142)); fundal-imagine: gradient linear (top, rgb (17, 148, 211), rgb (59, 95, 142)); filtru: progid: DXImageTransform.Microsoft.gradient (GradientType = 0, StartColorStr = '# 1194d3', EndColorStr = "# 3b5f8e"); padding: 6px 9px; raza de graniță: 3 pixeli; culoare: #fff; text-shadow: 1px 1px 1px # 0a3d52; frontieră: nici una; lățime: 30%;  introducere [tip = trimite]: hover background: # 0a3d52;  floatleft float: left;  .floatright float: right; 

Adăugați acest cod la adresa dvs. styles.css fişier.

Acum, să acoperim formularul "nouă sarcină". Vom adăuga Date-bind atributele formei pentru a face legăturile Knockout să funcționeze. Date-bind atributul este modul în care Knockout păstrează interfața de utilizare în sincronizare și permite legarea evenimentelor și alte funcționalități importante. Înlocuiți formularul "nouă sarcină" cu următorul cod.

 

Creați o nouă sarcină

Vom trece prin acestea unul câte unul. În primul rând, elementul de formă are o obligație pentru a depune eveniment. Atunci când formularul este depus, addTask () funcție definită pe TaskViewModel execută. Primul element de intrare (care implicit este de tip = "text") conține valoare din ko.observable newTaskDesc pe care am definit-o mai devreme. Orice este în acest câmp atunci când trimiteți formularul devine task-ul Descriere proprietate.

Deci avem o modalitate de a adăuga sarcini, dar trebuie să afișăm acele sarcini. Trebuie, de asemenea, să adăugăm fiecare proprietate a sarcinii. Să repetăm ​​sarcinile și să le adăugăm în tabel. Knockout oferă o capacitate de iterație convenabilă pentru a facilita acest lucru; definiți un bloc de comentarii cu următoarea sintaxă:

         X 

În Ruby, valoarea finală se întoarce implicit.

Aceasta folosește capacitatea de iterație a Knockout. Fiecare sarcină este definită în mod specific pe TaskViewModel (t.tasks) și rămâne în sincronizare în interfața de utilizare. ID-ul fiecărei sarcini se adaugă numai după terminarea apelului DB (deoarece nu există nici o modalitate de a ne asigura că avem codul corect din baza de date până când este scris), dar interfața nu trebuie să reflecte neconcordanțe ca acestea.

Acum ar trebui să puteți folosi shotgun app.rb (gem instalează pușcă) din directorul dvs. de lucru și testați aplicația în browser la http: // localhost: 9393. (Notă: asigurați-vă că aveți gem installa făcut toate dependențele / bibliotecile necesare înainte de a încerca să rulați cererea dvs.) Ar trebui să puteți adăuga sarcini și să le vedeți imediat.


Până în partea a doua

În acest tutorial, ați învățat cum să creați o interfață JSON cu Sinatra și, ulterior, cum să oglindiți aceste modele în Knockout.js. De asemenea, ați învățat cum să creați legături pentru ca interfața dvs. utilizator să se sincronizeze cu datele noastre. În următoarea parte a acestui tutorial, vom vorbi numai despre Knockout și vom explica cum să creați funcții de sortare, căutare și actualizare.