Création d'applications Web à une page avec Sinatra Partie 1

Avez-vous déjà voulu apprendre à créer une application d'une seule page avec Sinatra et Knockout.js? Eh bien, aujourd'hui est le jour où vous apprenez! Dans cette première section d'une série en deux parties, nous passons en revue le processus de création d'une application de liste de tâches d'une page où les utilisateurs peuvent afficher leurs tâches, les trier, les marquer comme terminées, les supprimer, les rechercher et les ajouter. nouvelles tâches.


Qu'est-ce que Sinatra??

Selon leur site web:

Sinatra est un DSL permettant de créer rapidement des applications Web en Ruby avec un minimum d'effort..

Sinatra vous permet de faire des choses comme:

get "/ task / new" do erb: form end

Il s’agit d’une route qui traite les demandes GET pour "/ task / new" et rend une erb formulaire nommé form.erb. Nous n'utiliserons pas Sinatra pour le rendu des modèles Ruby; au lieu de cela, nous l’utiliserons uniquement pour envoyer des réponses JSON à notre frontal géré Knockout.js (et à certaines fonctions utilitaires de jQuery comme $ .ajax). Nous n'utiliserons erb que pour rendre le fichier HTML principal.


Qu'est-ce que Knockout??

Knockout est un framework JavaScript Model-View-ViewModel (MVVM) qui vous permet de conserver vos modèles dans des objets "observables" spéciaux. Il maintient également votre interface utilisateur à jour, en fonction de ces objets observés.

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

Voici ce que vous allez construire:

Nous commencerons par définir notre modèle, puis nos actions CRUD dans Sinatra. Nous nous baserons sur DataMapper et SQLite pour le stockage persistant, mais vous pouvez utiliser n’importe quel ORM que vous préférez..

Ajoutons un modèle de tâche à la modèles.rb fichier:

 DataMapper.setup (: default, 'sqlite: ///path/to/project.db') Classe Tâche include DataMapper :: Propriété de la ressource: id, Propriété série: complete, Propriété booléenne: description, Propriété du texte: created_at, Propriété DateTime : updated_at, DateTime end DataMapper.auto_upgrade!

Ce modèle de tâches consiste essentiellement en quelques propriétés différentes que nous voulons manipuler dans notre application de tâches à faire..

Ensuite, écrivons notre serveur Sinatra JSON. dans le app.rb fichier, nous allons commencer par demander quelques modules différents:

 Requiert 'rubygems' Requiert 'Sinatra' Requiert 'Data_mapper' Requiert File.dirname (__ FILE__) + '/models.rb' Requiert 'json' Requiert 'Date'

L'étape suivante consiste à définir des valeurs globales par défaut. en particulier, nous avons besoin d'un type MIME envoyé avec chacun de nos en-têtes de réponse pour spécifier que chaque réponse est JSON.

avant que content_type 'application / json' se termine

le avant la fonction d'assistance s'exécute avant chaque correspondance d'itinéraire. Vous pouvez également spécifier des itinéraires correspondants après avant; Si, par exemple, vous vouliez exécuter des réponses JSON uniquement si l'URL finissait par ".json", vous utiliseriez ceci:

avant% r . + \. json $, tapez content_type 'application / json' end

Ensuite, nous définissons nos itinéraires CRUD, ainsi qu’un itinéraire pour desservir nos index.erb fichier:

 get "/" do content_type 'html' erb: index end get "/ tasks" do @tasks = Tâche.all @ tasks.to_json poste suivante "/ tasks / new" do @task = Tâche.new @ task.complete = false @ task.description = params [: description] @ task.created_at = DateTime.now @ task.updated_at = null fin "/ tasks /: id" do @task = Task.find (params [: id]) @task. complete = params [: complete] @ task.description = params [: description] @ task.updated_at = DateTime.Maintenant si @ 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]) si @ task.destroy : task = > @task,: status => "success". to_json else : task => @task,: status => "échec". to_json end end

Alors le app.rb Le fichier ressemble maintenant à ceci:

 require 'rubygems' require 'sinatra' require 'data_mapper' require File.dirname (__ FILE__) + '/models.rb' require 'json' require 'Date' avant do content_type 'application / json' end get "/" do content_type " html 'erb: index end obtenir "/ tasks" do @tasks = Task.all @ tasks.to_json message "/ tasks / new" do @task = Task.new @ task.complete = false @ task.description = params [ : description] @ task.created_at = DateTime.now @ task.updated_at = null si @ task.save : task => @task,: status => "success". to_json sinon : task => @task,: status => "échec". to_json end end put "/ tasks /: id" do @task = Task.find (params [: id]) @ task.complete = params [: complet] @ task.description = params [ : description] @ task.updated_at = DateTime.Maintenant si @ task.save : task => @task,: status => "success". to_json else : task => @task,: status => "échec"  .to_json end end delete "/ tasks /: id" do @task = Task.find (params [: id]) if @ task.destroy : task => @task,: status => "success". to_json else : tâche => @task,: status => "échec" .to_json end end

Chacune de ces routes correspond à une action. Une seule vue (la vue "Toutes les tâches") regroupe toutes les actions. Rappelez-vous: en Ruby, la valeur finale est renvoyée implicitement. Vous pouvez explicitement revenir plus tôt, mais quel que soit le contenu renvoyé par ces itinéraires, la réponse sera envoyée par le serveur..


Knockout: Modèles

Ensuite, nous commençons par définir nos modèles dans Knockout. Dans app.js, Placez le code suivant:

 fonction Tâche (données) 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); 

Comme vous pouvez le constater, ces propriétés sont directement mappées sur notre modèle dans modèles.rb. UNE ko.observable maintient la valeur mise à jour à travers l'interface utilisateur lorsqu'elle change sans qu'il soit nécessaire de faire appel au serveur ou au DOM pour suivre son état.

Ensuite, nous allons ajouter un TaskViewModel.

 function TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); $ .getJSON ("/ tasks", fonction (raw) var taches = $ .map (raw, fonction (item) retour nouvelle tâche (item)); self.tasks (tâches););  ko.applyBindings (new TaskListViewModel ());

C'est le début de ce qui sera la viande de notre application. Nous commençons par créer un TaskViewModel fonction constructeur; une nouvelle instance de cette fonction est transmise au knockout applyBindings () fonction à la fin de notre dossier.

À l'intérieur de notre TaskViewModel est un appel initial pour récupérer des tâches de la base de données, via l’URL "/ tasks". Ceux-ci sont ensuite mappés dans le ko.observableArray, qui est mis à t.tasks. Ce tableau est le coeur de la fonctionnalité de notre application.

Nous avons donc maintenant une fonction de récupération qui affiche les tâches. Créons une fonction de création, puis créons notre vue de modèle réelle. Ajoutez le code suivant au TaskViewModel:

 t.newTaskDesc = ko.observable (); t.addTask = function () var newtask = nouvelle tâche (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 = fonction (tâche) var t = ko.toJS (tâche); $ .ajax (url: "http: // localhost: 9393 / tasks", tapez: "POST", données: t). done (function (data) task.id (data.task.id); ) 

Knockout fournit une capacité d'itération pratique…

Tout d'abord, nous avons mis newTaskDesc comme observable. Cela nous permet d’utiliser facilement un champ de saisie pour saisir une description de tâche. Ensuite, nous définissons notre Ajouter une tâche() fonction, qui ajoute une tâche à la observableArray; il appelle le saveTask () fonction en passant dans le nouvel objet de tâche.

le saveTask () fonction est agnostique quant au type de sauvegarde qu’elle effectue. (Plus tard, nous utilisons le saveTask () fonction pour supprimer des tâches ou les marquer comme complètes.) Remarque importante: nous nous appuyons sur une fonction pratique pour saisir l’horodatage actuel. Ce ne sera pas le exact horodatage enregistré dans la base de données, mais il fournit certaines données à placer dans la vue.

Le parcours est très simple:

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

Il convient également de noter que l'id de la tâche n'est pas défini tant que la demande Ajax n'est pas terminée, car nous devons l'affecter en fonction de la réponse du serveur..

Créons le code HTML que notre code JavaScript nouvellement créé contrôle. Une grande partie de ce fichier provient du fichier d'index HTML5 boilerplate. Cela va dans le index.erb fichier:

           Faire                

Prenons ce modèle et complétons les liaisons que Knockout utilise pour maintenir l'interface utilisateur synchronisée. Pour cette partie, nous couvrons la création de tâches. Dans la deuxième partie, nous aborderons des fonctionnalités plus avancées (y compris la recherche, le tri, la suppression et le marquage comme étant terminé)..

Avant de poursuivre, donnons un peu de style à notre page. Étant donné que ce tutoriel ne concerne pas les CSS, nous allons simplement insérer ceci et continuer tout droit. Le code suivant se trouve dans le fichier CSS Boilerplate HTML5, qui inclut une réinitialisation et quelques autres éléments..

 section width: 800px; marge: auto 20px;  table largeur: 100%;  th curseur: pointeur;  tr border-bottom: 1px solide #ddd;  tr.complete, tr.complete: nth-child (impair) background: # efffd7; couleur: #ddd;  tr: nième enfant (impair) background-color: #dedede;  td padding: 10px 20px;  td.destroytask background: #ffeaea; couleur: # 943c3c; poids de police: gras; opacité: 0,4;  td.destroytask: survoler curseur: pointeur; fond: #ffacac; couleur: # 792727; opacité: 1;  .fifty width: 50%;  input background: #fefefe; box-shadow: insert 0 0 6px #aaa; rembourrage: 6px; bordure: aucune; largeur: 90%; marge: 4px;  entrée: focus contour: aucun; encadré: encadré 0 0 6px rgb (17, 148, 211); -webkit-transition: 0.2s tous; arrière-plan: rgba (17, 148, 211, 0,05);  input [type = submit] background-color: # 1194d3; background-image: -webkit-gradient (linéaire, gauche en haut, gauche en bas, de (RGB (17, 148, 211)) à (RGB (59, 95, 142)); image d'arrière-plan: -webkit-linear-gradient (haut, rgb (17, 148, 211), rgb (59, 95, 142)); image d'arrière-plan: -moz-linear-gradient (haut, rgb (17, 148, 211), rgb (59, 95, 142)); image d'arrière-plan: -o-linéaire-gradient (haut, rgb (17, 148, 211), rgb (59, 95, 142)); image d'arrière-plan: -ms-linear-gradient (haut, rgb (17, 148, 211), rgb (59, 95, 142)); image d'arrière-plan: gradient linéaire (en haut, rgb (17, 148, 211), rgb (59, 95, 142)); filter: progid: DXImageTransform.Microsoft.gradient (GradientType = 0, StartColorStr = '# 1194d3', EndColorStr = "# 3b5f8e"); rembourrage: 6px 9px; border-radius: 3px; couleur: #fff; text-shadow: 1px 1px 1px # 0a3d52; bordure: aucune; largeur: 30%;  input [type = submit]: survoler background: # 0a3d52;  .floatleft float: left;  .floatright float: right; 

Ajoutez ce code à votre styles.css fichier.

Couvrons maintenant le formulaire "nouvelle tâche". Nous allons ajouter liaison de données Attributs au formulaire pour que les liaisons Knockout fonctionnent. le liaison de données L'attribut indique comment Knockout maintient l'interface utilisateur synchronisée et permet la liaison d'événement et d'autres fonctionnalités importantes. Remplacez le formulaire "nouvelle tâche" par le code suivant.

 

Créer une nouvelle tâche

Nous allons les parcourir un par un. Tout d’abord, l’élément de formulaire a une liaison pour le soumettre un événement. Lorsque le formulaire est soumis, le Ajouter une tâche() fonction définie sur le TaskViewModel exécute. Le premier élément d’entrée (qui est implicitement de type = "text") contient le valeur du ko.observable newTaskDesc que nous avons défini plus tôt. Tout ce qui se trouve dans ce champ lors de la soumission du formulaire devient celui de la tâche. la description propriété.

Nous avons donc un moyen d’ajouter des tâches, mais nous devons les afficher. Nous devons également ajouter chacune des propriétés de la tâche. Parcourons les tâches et ajoutons-les dans le tableau. Knockout fournit une capacité d'itération pratique pour faciliter cela; définir un bloc de commentaires avec la syntaxe suivante:

         X 

En Ruby, la valeur finale est retournée implicitement.

Ceci utilise la capacité d'itération de Knockout. Chaque tâche est spécifiquement définie sur le TaskViewModel (t.tasks), et il reste synchronisé sur l’interface utilisateur. L'ID de chaque tâche est ajouté uniquement une fois l'appel de base de données terminé (car il n'existe aucun moyen de nous assurer que l'ID correct de la base de données est écrit jusqu'à ce qu'elle soit écrite), mais l'interface n'a pas besoin de refléter des incohérences telles que celles-ci..

Vous devriez maintenant pouvoir utiliser carabine app.rb (bijou installer fusil de chasse) depuis votre répertoire de travail et testez votre application dans le navigateur à l'adresse http: // localhost: 9393. (Remarque: assurez-vous d'avoir bijou installerExaminez toutes vos dépendances / bibliothèques requises avant d’essayer d’exécuter votre application.) Vous devriez pouvoir ajouter des tâches et les voir apparaître immédiatement..


Jusqu'à la deuxième partie

Dans ce didacticiel, vous avez appris à créer une interface JSON avec Sinatra, puis à mettre en miroir ces modèles dans Knockout.js. Vous avez également appris à créer des liaisons pour maintenir notre interface utilisateur synchronisée avec nos données. Dans la suite de ce didacticiel, nous ne parlerons que de Knockout et expliquerons comment créer des fonctionnalités de tri, de recherche et de mise à jour..