Creazione di app Web a singola pagina con Sinatra parte 1

Hai mai desiderato imparare come creare un'applicazione singola con Sinatra e Knockout.js? Bene, oggi è il giorno che impari! In questa prima sezione di una serie in due parti, esamineremo il processo per creare un'applicazione di una sola pagina in cui gli utenti possono visualizzare i loro compiti, ordinarli, contrassegnarli come completi, eliminarli, cercarli e aggiungere nuovi compiti.


Cos'è Sinatra?

Secondo il loro sito web:

Sinatra è un DSL per la rapida creazione di applicazioni Web in Ruby con il minimo sforzo.

Sinatra ti permette di fare cose, come:

ottieni "/ task / new" fai erb: form end

Questa è una rotta che gestisce le richieste GET per "/ task / new" e rende un erb modulo chiamato form.erb. Non useremo Sinatra per rendere i modelli Ruby; invece, lo useremo solo per inviare risposte JSON al nostro front-end gestito da Knockout.js (e alcune funzioni di utilità da jQuery come $ .ajax). Useremo solo ERB per rendere il file HTML principale.


Che cos'è Knockout?

Knockout è un framework JavaScript Model-View-ViewModel (MVVM) che consente di mantenere i modelli in speciali oggetti "osservabili". Mantiene anche l'interfaccia utente aggiornata, basata su quegli oggetti osservati.

-ToDo / -app.rb -models.rb --views / -index.erb - public / --- scripts / - knockout.js - jquery.js - app.js --- styles / - styles.css

Ecco cosa costruirai:

Iniziamo definendo il nostro modello e quindi le nostre azioni CRUD in Sinatra. Ci affideremo a DataMapper e SQLite per l'archiviazione persistente, ma puoi utilizzare qualsiasi ORM che preferisci.

Aggiungiamo un modello di attività al models.rb file:

 DataMapper.setup (: default, 'sqlite: ///path/to/project.db') classe Task include DataMapper :: Proprietà risorsa: id, Proprietà seriale: completa, Proprietà booleana: descrizione, proprietà Text: created_at, proprietà DateTime : updated_at, DateTime end DataMapper.auto_upgrade!

Questo modello di attività consiste essenzialmente in alcune proprietà diverse che vogliamo manipolare nella nostra applicazione da fare.

Quindi, scriviamo il nostro server Sinatra JSON. Nel app.rb file, inizieremo richiedendo alcuni moduli diversi:

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

Il prossimo passo è definire alcune impostazioni globali; in particolare, abbiamo bisogno di un tipo MIME inviato con ciascuna delle nostre intestazioni di risposta per specificare che ogni risposta è JSON.

prima fai content_type 'application / json' end

Il prima la funzione di supporto viene eseguita prima di ogni corrispondenza percorso. Puoi anche specificare percorsi di corrispondenza dopo prima; se, ad esempio, volevi eseguire solo le risposte JSON se l'URL terminava in ".json", dovresti usare questo:

prima di% r . + \. json $ fai content_type 'application / json' end

Successivamente, definiamo le nostre rotte CRUD, così come una rotta per servire i nostri index.erb file:

 get "/" fai content_type 'html' erb: index end ottieni "/ 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 fine inserisci "/ tasks /: id" do @task = Task.find (params [: id]) @task. complete = params [: complete] @ task.description = params [: description] @ task.updated_at = DateTime.now se @ 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 end end

Così la app.rb il file ora è simile a questo:

 richiede 'rubygems' richiede 'sinatra' richiede 'data_mapper' richiede File.dirname (__ FILE__) + '/models.rb' richiede 'json' richiede 'Date' prima di content_type 'application / json' end get "/" fai 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 se @ task.save : task => @task,: status => "success". to_json else : task => @task,: status => "failure". to_json end end metti "/ tasks /: id" do @task = Task.find (params [: id]) @ task.complete = params [: complete] @ task.description = params [ : description] @ task.updated_at = DateTime.now se @ 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]) se @ task.destroy : task => @task,: status => "success". to_json else : task => @task,: status => "failure" .to_json end end

Ognuna di queste rotte si associa a un'azione. C'è solo una vista (la vista "tutte le attività") che ospita ogni azione. Ricorda: in Ruby, il valore finale ritorna implicitamente. È possibile tornare esplicitamente in anticipo, ma qualunque sia il contenuto restituito da tali percorsi sarà la risposta inviata dal server.


Knockout: Modelli

Successivamente, iniziamo definendo i nostri modelli in Knockout. Nel app.js, inserire il seguente codice:

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

Come puoi vedere, queste proprietà sono direttamente mappate al nostro modello in models.rb. UN ko.observable mantiene il valore aggiornato nell'interfaccia utente quando cambia senza dover fare affidamento sul server o sul DOM per tenere traccia del suo stato.

Successivamente, aggiungeremo a TaskViewModel.

 function TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); $ .getJSON ("/ tasks", function (raw) var tasks = $ .map (raw, function (item) return new Task (item)); self.tasks (tasks););  ko.applyBindings (new TaskListViewModel ());

Questo è l'inizio di ciò che sarà la carne della nostra applicazione. Iniziamo creando un TaskViewModel funzione di costruzione; una nuova istanza di questa funzione viene passata al Knockout applyBindings () funzione alla fine del nostro file.

Dentro il nostro TaskViewModel è una chiamata iniziale per recuperare attività dal database, tramite l'url "/ tasks". Questi sono poi mappati nel ko.observableArray, che è impostato su t.tasks. Questo array è il cuore delle funzionalità dell'applicazione.

Quindi, ora, abbiamo una funzione di recupero che mostra le attività. Creiamo una funzione di creazione e quindi creiamo la nostra visualizzazione del modello reale. Aggiungi il seguente codice al TaskViewModel:

 t.newTaskDesc = ko.observable (); t.addTask = function () var newtask = new Task (description: this.newTaskDesc ()); $ .getJSON ("/ getdate", function (data) newtask.created_at (data.date); newtask.updated_at (data.date); t.tasks.push (newtask); t.saveTask (newtask); t. newTaskDesc ("");); t.saveTask = function (task) var t = ko.toJS (task); $ .ajax (url: "http: // localhost: 9393 / tasks", digitare: "POST", data: t). done (function (data) task.id (data.task.id); ); 

Knockout fornisce una comoda capacità di iterazione ...

Per prima cosa, abbiamo impostato newTaskDesc come osservabile. Ciò ci consente di utilizzare facilmente un campo di input per digitare una descrizione dell'attività. Successivamente, definiamo il nostro addTask () funzione, che aggiunge un'attività al observableArray; chiama il saveTask () funzione, passando il nuovo oggetto compito.

Il saveTask () la funzione è indipendente dal tipo di salvataggio eseguito. (Più tardi, usiamo il saveTask () funzione per cancellare compiti o contrassegnarli come completi.) Una nota importante qui: ci basiamo su una funzione di convenienza per afferrare il timestamp corrente. Questo non sarà il esatto data / ora salvata nel database, ma fornisce alcuni dati da rilasciare nella vista.

Il percorso è molto semplice:

ottieni "/ getdate" do : date => DateTime.now .to_json end

Va inoltre notato che l'ID dell'attività non è impostato fino al completamento della richiesta Ajax, poiché è necessario assegnarlo in base alla risposta del server.

Creiamo l'HTML che i nostri controlli JavaScript appena creati. Una gran parte di questo file proviene dal file indice di HTMLplatere. Questo va nel index.erb file:

           Fare        

Crea una nuova attività

Cerca attività

Attività incomplete rimanenti:

Elimina tutte le attività complete
DB ID Descrizione Data aggiunta Data modificata Completare? Elimina
X

Prendiamo questo modello e inseriamo i collegamenti che Knockout utilizza per mantenere l'interfaccia utente in sincronia. Per questa parte, copriamo la creazione di elementi To-Do. Nella seconda parte illustreremo le funzionalità più avanzate (incluse ricerca, ordinamento, eliminazione e marcatura complete).

Prima di andare avanti, diamo un po 'di stile alla nostra pagina. Dato che questo tutorial non riguarda i CSS, lo abbandoneremo e andremo avanti. Il seguente codice si trova all'interno del file CSS HTML5 Boilerplate, che include un reset e alcune altre cose.

 section width: 800px; margine: 20px auto;  table width: 100%;  th cursor: pointer;  tr border-bottom: 1px solid #ddd;  tr.complete, tr.complete: nth-child (dispari) background: # efffd7; colore: #ddd;  tr: nth-child (dispari) background-color: #dedede;  td padding: 10px 20px;  td.destroytask background: #ffeaea; colore: # 943c3c; font-weight: bold; opacità: 0.4;  td.destroytask: hover cursor: pointer; sfondo: #ffacac; colore: # 792727; opacità: 1;  .fifty width: 50%;  input background: #fefefe; box-shadow: inset 0 0 6px #aaa; imbottitura: 6px; confine: nessuno; larghezza: 90%; margine: 4px;  input: focus outline: none; box-shadow: inset 0 0 6px rgb (17, 148, 211); -webkit-transition: 0.2s tutti; sfondo: rgba (17, 148, 211, 0,05);  input [type = submit] background-color: # 1194d3; background-image: -webkit-gradient (lineare, in alto a sinistra, in basso a sinistra, da (rgb (17, 148, 211)), a (rgb (59, 95, 142))); background-image: -webkit-linear-gradient (in alto, rgb (17, 148, 211), rgb (59, 95, 142)); background-image: -moz-linear-gradient (in alto, rgb (17, 148, 211), rgb (59, 95, 142)); background-image: -o-linear-gradient (in alto, rgb (17, 148, 211), rgb (59, 95, 142)); background-image: -ms-linear-gradient (in alto, rgb (17, 148, 211), rgb (59, 95, 142)); background-image: linear-gradient (top, rgb (17, 148, 211), rgb (59, 95, 142)); filter: progid: DXImageTransform.Microsoft.gradient (GradientType = 0, StartColorStr = '# 1194d3', EndColorStr = "# 3b5f8e"); imbottitura: 6px 9px; border-radius: 3px; colore: #fff; text-shadow: 1px 1px 1px # 0a3d52; confine: nessuno; larghezza: 30%;  input [type = submit]: hover background: # 0a3d52;  .floatleft float: left;  .floatright float: right; 

Aggiungi questo codice al tuo styles.css file.

Ora, copriamo il modulo "nuova attività". Aggiungeremo Dati-bind attribuisce al form per far funzionare i binding Knockout. Il Dati-bind l'attributo è il modo in cui Knockout mantiene sincronizzata l'interfaccia utente e consente l'associazione degli eventi e altre funzionalità importanti. Sostituire il modulo "nuova attività" con il seguente codice.

 

Crea una nuova attività

Li faremo uno dopo l'altro. Innanzitutto, l'elemento del modulo ha un'associazione per il Sottoscrivi evento. Quando il modulo è presentato, il addTask () funzione definita sul TaskViewModel esegue. Il primo elemento di input (che è implicitamente di type = "text") contiene il valore del ko.observable newTaskDesc che abbiamo definito in precedenza. Qualunque cosa sia in questo campo quando si invia il modulo diventa l'attività descrizione proprietà.

Quindi abbiamo un modo per aggiungere attività, ma abbiamo bisogno di mostrare quelle attività. Inoltre, è necessario aggiungere ciascuna delle proprietà dell'attività. Andiamo a scorrere le attività e aggiungiamole nella tabella. Knockout fornisce una comoda possibilità di iterazione per facilitare questo; definire un blocco di commento con la seguente sintassi:

         X 

In Ruby, il valore finale viene restituito implicitamente.

Questo utilizza la capacità di iterazione di Knockout. Ogni attività è specificamente definita sul TaskViewModel (t.tasks) e rimane sincronizzato nell'interfaccia utente. L'ID di ogni attività viene aggiunto solo dopo aver terminato la chiamata al DB (poiché non c'è modo di garantire che l'ID del database sia corretto fino alla sua scrittura), ma l'interfaccia non deve riflettere incoerenze come queste.

Ora dovresti essere in grado di usarlo shotgun app.rb (gem installare shotgun) dalla directory di lavoro e testare l'app nel browser all'indirizzo http: // localhost: 9393. (Nota: assicurati di averlo gem installazione'd tutte le tue dipendenze / librerie richieste prima di provare a eseguire l'applicazione. Dovresti essere in grado di aggiungere attività e vederle immediatamente.


Fino alla seconda parte

In questo tutorial, hai imparato come creare un'interfaccia JSON con Sinatra e successivamente come rispecchiare quei modelli in Knockout.js. Hai anche imparato a creare associazioni per mantenere la nostra interfaccia utente in sincronia con i nostri dati. Nella parte successiva di questo tutorial, parleremo esclusivamente di Knockout e spiegheremo come creare funzionalità di ordinamento, ricerca e aggiornamento.