Å bygge enkelsidede webapps med Sinatra Del 1

Har du noen gang ønsket å lære å bygge en enkelt side-app med Sinatra og Knockout.js? Nå, i dag er dagen du lærer! I denne første delen av en todelt serie vurderer vi prosessen for å bygge en enkelt sideoppgave-applikasjon der brukere kan se oppgavene deres, sortere dem, merke dem som komplette, slette dem, søke gjennom dem og legge til nye oppgaver.


Hva er Sinatra?

Ifølge deres nettside:

Sinatra er en DSL for å raskt lage webapplikasjoner i Ruby med minimal innsats.

Sinatra lar deg gjøre ting, som:

få "/ oppgave / ny" gjør erb: skjema ende

Dette er en rute som håndterer GET-forespørsler for "/ task / new" og gjør en erb skjema kalt form.erb. Vi vil ikke bruke Sinatra til å gjøre Ruby-maler; I stedet bruker vi det bare for å sende JSON-svar til vår Knockout.js-klarte forside (og noen verktøyfunksjoner fra jQuery som $ .ajax). Vi vil bare bruke erb for å gjengi hoved HTML-filen.


Hva er Knockout?

Knockout er en modell-View-ViewModel (MVVM) JavaScript rammeverk som lar deg holde modellene dine i spesielle "observerbare" objekter. Det holder også brukergrensesnittet oppdatert, basert på de observerte objektene.

-ToDo / -app.rb -models.rb --visninger / -index.erb - offentlig / --- skript / - knockout.js - jquery.js - app.js --- stiler / - styles.css

Her er hva du skal bygge:

Vi kommer i gang ved å definere vår modell og deretter våre CRUD handlinger i Sinatra. Vi vil stole på DataMapper og SQLite for vedvarende lagring, men du kan bruke hvilken som helst ORM du foretrekker.

La oss legge til en oppgavemodell til models.rb fil:

 DataMapper.setup (: standard, 'sqlite: ///path/to/project.db') klasse Oppgave inkluderer DataMapper :: Ressursegenskap: id, seriell egenskap: fullstendig, boolsk eiendom: beskrivelse, tekstegenskap: created_at, DateTime property : updated_at, DateTime-slutt DataMapper.auto_upgrade!

Denne oppgavemodellen består i hovedsak av noen få forskjellige egenskaper som vi ønsker å manipulere i vårt gjøremålsprogram.

Deretter skriver vi vår Sinatra JSON-server. I app.rb fil, starter vi ved å kreve noen forskjellige moduler:

 krever 'rubygems' krever 'sinatra' krever 'data_mapper' krever File.dirname (__ FILE__) + '/models.rb' krever 'json' krever 'Date'

Det neste trinnet er å definere noen globale standardinnstillinger; Spesielt trenger vi en MIME-type som sendes med hver av våre responsoverskrifter for å angi at hvert svar er JSON.

før gjør content_type 'application / json' slutten

De før Hjelperfunksjonen går før hver rute-kamp. Du kan også spesifisere samsvarende ruter etter før; hvis du for eksempel bare ville kjøre JSON-svar hvis nettadressen endte i ".json", ville du bruke dette:

før% r . + \. json $ gjør content_type 'application / json' slutten

Deretter definerer vi våre CRUD-ruter, samt en rute for å betjene våre index.erb fil:

 få "/" gjør content_type 'html' erb: indeks end get "/ tasks" gjør @tasks = Task.all @ tasks.to_json slutt innlegg "/ tasks / new" do @task = Task.new @ task.complete = false @ task.description = params [: description] @ task.created_at = DateTime.now @ task.updated_at = null slutt satt "/ tasks /: id" do @task = Task.find (params [: id]) @task. complete = params [: complete] @ task.description = params [: beskrivelse] @ task.updated_at = DateTime.now hvis @ task.save : task => @task,: status => "suksess". to_json else  : task => @task,: status => "failure". to_json slutten slett "/ tasks /: id" do @task = Task.find (params [: id]) hvis @ task.destroy : task = > @task,: status => "suksess". to_json else : task => @task,: status => "failure".

app.rb filen ser nå slik ut:

 krever 'rubygems' krever 'sinatra' krever 'data_mapper' krever File.dirname (__ FILE__) + '/models.rb' krever 'json' krever 'Dato' før innhold_type'applikasjon / json 'end get' / "gjør content_type' html 'erb: index end get' / tasks 'gjør @tasks = Task.all @ tasks.to_json end innlegg "/ oppgaver / nytt" do @task = Task.new @ task.complete = false @ task.description = params [ : beskrivelse] @ task.created_at = DateTime.now @ task.updated_at = null hvis @ task.save : task => @task,: status => "suksess". to_json else : task => @task,: status => "feil". to_json slutten satt "/ tasks /: id" do @task = Task.find (params [: id]) @ task.complete = params [: complete] @ task.description = params [ : beskrivelse] @ task.updated_at = DateTime.now hvis @ task.save : task => @task,: status => "suksess". to_json else : task => @task,: status => "failure"  .to_json ende end delete "/ oppgaver /: id" do @task = Task.find (params [: id]) hvis @ task.destroy : task => @task,: status => "suksess". to_json ellers : task => @task,: status => "failure" .to_json slutten

Hver av disse rutene kartlegger en handling. Det er bare en visning ("alle oppgaver" -visningen) som huser alle handlinger. Husk: i Ruby returnerer den endelige verdien implisitt. Du kan eksplisitt returnere tidlig, men uansett innhold vil disse ruterne bli svaret som sendes fra serveren.


Knockout: Modeller

Deretter begynner vi med å definere våre modeller i Knockout. I app.js, plasser følgende kode:

 funksjon Oppgave (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); 

Som du kan se, er disse egenskapene direkte kartlagt til vår modell i models.rb. EN ko.observable holder verdien oppdatert over brukergrensesnittet når den endres uten å stole på serveren eller på DOM for å holde oversikt over sin tilstand.

Deretter legger vi til en TaskViewModel.

 funksjon TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); $ .getJSON ("/ tasks", funksjon (rå) var tasks = $. map (raw, function (item) returner ny oppgave (element)); self.tasks (oppgaver););  ko.applyBindings (ny TaskListViewModel ());

Dette er starten på hva som blir kjøttet i vår søknad. Vi begynner med å lage en TaskViewModel konstruktør funksjon; En ny forekomst av denne funksjonen blir sendt til Knockout applyBindings () funksjonen på slutten av filen vår.

Inne i vår TaskViewModel er en innledende samtale for å hente oppgaver fra databasen, via "/ oppgaver" -adressen. Disse blir deretter kartlagt i ko.observableArray, som er satt til t.tasks. Denne serien er hjertet av programmets funksjonalitet.

Så nå har vi en henting funksjon som viser oppgaver. La oss lage en opprettelsesfunksjon, og deretter lage vår faktiske malvisning. Legg til følgende kode i TaskViewModel:

 t.newTaskDesc = ko.observable (); t.addTask = funksjon () var newtask = ny oppgave (beskrivelse: this.newTaskDesc ()); $ .getJSON ("/ getdate", funksjon (data) newtask.created_at (data.date); newtask.updated_at (data.date); t.tasks.push (newtask); t.saveTask (newtask); t. newTaskDesc ("");); t.saveTask = funksjon (oppgave) var t = ko.toJS (oppgave); $ .ajax (url: "http: // localhost: 9393 / tasks", type: "POST", data: t). ferdig (funksjon (data) task.id (data.task.id); ); 

Knockout gir en praktisk iterasjonsevne ...

Først setter vi newTaskDesc som en observerbar. Dette gjør det mulig for oss å bruke et inntastingsfelt enkelt for å skrive en oppgavebeskrivelse. Deretter definerer vi vår addTask () funksjon, som legger til en oppgave til observableArray; det kaller saveTask () funksjon, passerer i det nye oppgaveobjektet.

De saveTask () funksjon er agnostisk av hva slags lagring det utfører. (Senere bruker vi saveTask () funksjon for å slette oppgaver eller markere dem som komplette.) Et viktig notat her: Vi stoler på en praktisk funksjon for å ta tak i dagens tidsstempel. Dette vil ikke være nøyaktig tidsstempel lagret i databasen, men det gir noen data å slippe inn i visningen.

Ruten er veldig enkel:

få "/ getdate" gjøre : date => DateTime.now .to_json end

Det skal også bemerkes at oppgavens ID ikke er angitt før Ajax-forespørselen fullføres, da vi må tilordne den basert på serverens svar.

La oss lage HTML-koden som vår nyskapede JavaScript kontrollerer. En stor del av denne filen kommer fra HTML5 boilerplate indeksfilen. Dette går inn i index.erb fil:

           Å gjøre        

Lag en ny oppgave

Søkeoppgaver

Ufullstendige oppgaver som gjenstår:

Slett alle komplette oppgaver
DB ID Beskrivelse Dato lagt til Dato endret Fullstendig? Slett
X

La oss ta denne malen og fylle ut bindingene som Knockout bruker for å holde brukergrensesnittet synkronisert. For denne delen dekker vi opprettelsen av To-Do-elementer. I del to vil vi dekke mer avansert funksjonalitet (inkludert søking, sortering, sletting og merking som komplett).

Før vi går videre, la oss gi vår side litt stil. Siden denne opplæringen ikke handler om CSS, vil vi bare slippe dette inn og flytte rett sammen. Følgende kode er inne i HTML5 Boilerplate CSS-filen, som inkluderer en tilbakestilling og et par andre ting.

 del bredde: 800px; margin: 20px auto;  tabell bredde: 100%;  th markør: pointer;  tr border-bottom: 1px solid #ddd;  tr.complete, tr.complete: nth-child (oddetall) bakgrunn: # efffd7; farge: #ddd;  tr: nth-barn (merkelig) bakgrunnsfarge: #dedede;  td polstring: 10px 20px;  td.destroytask bakgrunn: #ffeaea; farge: # 943c3c; font-weight: bold; opasitet: 0,4;  td.destroytask: svever markør: pointer; bakgrunn: #ffacac; farge: # 792727; opasitet: 1; . femti bredde: 50%;  input bakgrunn: #fefefe; boks-skygge: innsett 0 0 6px #aaa; polstring: 6px; grense: ingen; bredde: 90%; margin: 4px;  inngang: fokus disposisjon: ingen; boks-skygge: innsett 0 0 6px rgb (17, 148, 211); -webkit-overgang: 0,2s alle; bakgrunn: rgba (17, 148, 211, 0,05);  input [type = send inn] bakgrunnsfarge: # 1194d3; bakgrunnsbilde: -webkit-gradient (lineær, venstre topp, venstre bunn, fra (rgb (17, 148, 211)), til (rgb (59, 95, 142))); bakgrunnsbilde: -webkit-lineær gradient (topp, rgb (17, 148, 211), rgb (59, 95, 142)); bakgrunnsbilde: -moz-lineær-gradient (topp, rgb (17, 148, 211), rgb (59, 95, 142)); bakgrunnsbilde: -o-lineær gradient (topp, rgb (17, 148, 211), rgb (59, 95, 142)); bakgrunnsbilde: -ms-lineær gradient (topp, rgb (17, 148, 211), rgb (59, 95, 142)); bakgrunnsbilde: lineær gradient (topp, rgb (17, 148, 211), rgb (59, 95, 142)); filter: progid: DXImageTransform.Microsoft.gradient (GradientType = 0, StartColorStr = '# 1194d3', EndColorStr = "# 3b5f8e"); polstring: 6px 9px; border-radius: 3px; farge: #fff; tekstskygge: 1px 1px 1px # 0a3d52; grense: ingen; bredde: 30%;  input [type = submit]: svever bakgrunn: # 0a3d52;  .floatleft float: left;  .floatright float: right; 

Legg denne koden til din styles.css fil.

La oss nå dekke den nye oppgaveskjemaet. Vi vil legge til data-bind attributter til skjemaet for å få Knockout-bindingene til å fungere. De data-bind Attributt er hvordan Knockout holder brukergrensesnittet synkronisert, og muliggjør hendelsesbinding og annen viktig funksjonalitet. Erstatt "ny oppgave" skjema med følgende kode.

 

Lag en ny oppgave

Vi vil gå gjennom disse en etter en. For det første har formelementet en binding for sende inn begivenhet. Når skjemaet er sendt, vil addTask () funksjon definert på TaskViewModel utfører. Det første inputelementet (som er implisitt av type = "tekst") inneholder verdi av ko.observable newTaskDesc som vi definerte tidligere. Uansett hva som er i dette feltet når du sender inn skjemaet, blir oppgaven beskrivelse eiendom.

Så vi har mulighet til å legge til oppgaver, men vi må vise oppgavene. Vi må også legge til hver av oppgavens egenskaper. La oss iterere over oppgavene og legge dem inn i bordet. Knockout gir en praktisk iterasjon evne til å lette dette; definer en kommentarblokk med følgende syntaks:

         X 

I Ruby er den endelige verdien returnert implisitt.

Dette bruker Knockout's iterasjonskapasitet. Hver oppgave er spesifikt definert på TaskViewModel (t.tasks), og den forblir synkronisert over brukergrensesnittet. Hver oppgaves ID legges til etter at vi har fullført DB-anropet (det er ingen måte å sikre at vi har riktig ID fra databasen til den er skrevet), men grensesnittet trenger ikke å gjenspeile uoverensstemmelser som disse.

Du burde nå kunne bruke shotgun app.rb (perle installasjon hagle) fra arbeidskatalogen din og test appen din i nettleseren på http: // localhost: 9393. (Merk: Pass på at du har perle installasjon'd alle dine avhengigheter / nødvendige biblioteker før du prøver å kjøre programmet.) Du bør kunne legge til oppgaver og se dem umiddelbart vises.


Inntil del to

I denne opplæringen lærte du hvordan du oppretter et JSON-grensesnitt med Sinatra, og deretter hvordan du speiler disse modellene i Knockout.js. Du lærte også å lage bindinger for å holde brukergrensesnittet synkronisert med dataene våre. I neste del av denne opplæringen snakker vi bare om Knockout, og forklarer hvordan du lager sortering, søking og oppdatering av funksjonalitet.