Création d'applications Web à une page avec Sinatra partie 2

Dans la première partie de cette mini-série, nous avons créé la structure de base d'une application de tâche utilisant une interface JSON de Sinatra pour une base de données SQLite, ainsi qu'une interface frontale optimisée par Knockout qui nous permet d'ajouter des tâches à notre base de données. Dans cette dernière partie, nous aborderons certaines fonctionnalités légèrement plus avancées de Knockout, notamment le tri, la recherche, la mise à jour et la suppression..

Commençons là où nous nous sommes arrêtés; voici la partie pertinente de notre index.erb fichier.


Trier

Le tri est une tâche courante utilisée dans de nombreuses applications. Dans notre cas, nous souhaitons trier la liste de tâches par n’importe quel champ d’en-tête de notre table de liste de tâches. Nous allons commencer par ajouter le code suivant au TaskViewModel:

t.sortedBy = []; t.sort = fonction (champ) if (t.sortedBy.length && t.sortedBy [0] == champ && t.sortedBy [1] == 1) t.sortedBy [1] = 0; t.tasks.sort (function (premier, suivant) if (! suivant [champ] .call ()) retour 1; retour (suivant [champ] .call () < first[field].call()) ? 1 : (next[field].call() == first[field].call()) ? 0 : -1; );  else  t.sortedBy[0] = field; t.sortedBy[1] = 1; t.tasks.sort(function(first,next) if (!first[field].call()) return 1;  return (first[field].call() < next[field].call()) ? 1 : (first[field].call() == next[field].call()) ? 0 : -1; );  

Knockout fournit une fonction de tri pour les tableaux observables

Tout d'abord, nous définissons un trié par tableau en tant que propriété de notre modèle de vue. Cela nous permet de stocker si et comment la collection est triée.

Suivant est le Trier() une fonction. Il accepte un champ argument (le champ que nous voulons trier) et vérifie si les tâches sont triées selon le schéma de tri actuel. Nous voulons trier en utilisant un type de processus "à bascule". Par exemple, triez une fois par description et les tâches sont classées par ordre alphabétique. Triez à nouveau par description et les tâches sont classées par ordre alphabétique inverse. Ce Trier() La fonction prend en charge ce comportement en vérifiant le schéma de tri le plus récent et en le comparant à ce que l'utilisateur souhaite trier..

Knockout fournit une fonction de tri pour les tableaux observables. Il accepte une fonction en tant qu'argument qui contrôle la manière dont le tableau doit être trié. Cette fonction compare deux éléments du tableau et renvoie 1, 0, ou -1 à la suite de cette comparaison. Toutes les valeurs similaires sont regroupées (ce qui sera utile pour regrouper des tâches complètes et incomplètes).

Remarque: les propriétés des éléments du tableau doivent être appelées plutôt que simplement accédées; ces propriétés sont en fait des fonctions qui renvoient la valeur de la propriété si elles sont appelées sans aucun argument.

Ensuite, nous définissons les liaisons sur les en-têtes de table..

ID de base de données La description date ajoutée Date modifiée Achevée? Effacer

Ces liaisons permettent à chacun des en-têtes de déclencher un tri basé sur la valeur de chaîne transmise; chacun de ces cartes directement à la Tâche modèle.


Marquer comme complet

Ensuite, nous voulons pouvoir marquer une tâche comme terminée, et nous y parviendrons simplement en cochant la case associée à une tâche particulière. Commençons par définir une méthode dans le TaskViewModel:

t.markAsComplete = function (tâche) if (task.complete () == true) task.complete (true);  else task.complete (false);  task._method = "put"; t.saveTask (tâche); retourne vrai; 

le markAsComplete () La méthode accepte la tâche sous forme d'argument, qui est automatiquement transmis par Knockout lors de l'itération d'une collection d'éléments. Nous basculons ensuite le Achevée propriété, et ajouter un ._method = "put" propriété à la tâche. Ceci permet DataMapper utiliser le HTTP METTRE verbe par opposition à POSTER. Nous utilisons ensuite notre pratique t.saveTask () méthode pour enregistrer les modifications dans la base de données. Enfin, nous revenons vrai car revenant faux empêche la case à cocher de changer d'état.

Ensuite, nous changeons la vue en remplaçant le code de case à cocher dans la boucle de tâches par ce qui suit:

Cela nous dit deux choses:

  1. La case est cochée si Achevée est vrai.
  2. Au clic, lancez le markAsComplete () fonction du parent (TaskViewModel dans ce cas). Cela passe automatiquement la tâche en cours dans la boucle.

Suppression de tâches

Pour supprimer une tâche, nous utilisons simplement quelques méthodes pratiques et appelons saveTask (). Dans notre TaskViewModel, ajoutez ce qui suit:

t.destroyTask = function (task) task._method = "delete"; t.tasks.destroy (tâche); t.saveTask (tâche); ;

Cette fonction ajoute une propriété similaire à la méthode "put" pour compléter une tâche. Le intégré détruire() La méthode supprime la tâche transmise du tableau observable. Enfin, en appelant saveTask () détruit la tâche; c’est-à-dire tant que le ._méthode est réglé sur "supprimer".

Nous devons maintenant modifier notre vision. ajoutez ce qui suit:

X

La fonctionnalité est très similaire à la case à cocher complète. Notez que le class = "destroytask" est purement à des fins de style.


Supprimer tout terminé

Ensuite, nous voulons ajouter la fonctionnalité "supprimer toutes les tâches complètes". Tout d’abord, ajoutez le code suivant à la TaskViewModel:

t.removeAllComplete = function () ko.utils.arrayForEach (t.tasks (), function (tâche) if (task.complete ()) t.destroyTask (tâche);); 

Cette fonction itère simplement sur les tâches pour déterminer celles qui sont complètes, et nous appelons la destroyTask () méthode pour chaque tâche complète. À notre avis, ajouter ce qui suit pour le lien "supprimer tout le texte terminé".

 0 "> Supprimer toutes les tâches terminées

Notre liaison par clic fonctionnera correctement, mais nous devons définir tâches complètes (). Ajouter ce qui suit à notre TaskViewModel:

t.completeTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function (tâche) return (task.complete () && task._method! = "delete")); );

Cette méthode est un calculé propriété. Ces propriétés renvoient une valeur calculée "à la volée" lors de la mise à jour du modèle. Dans ce cas, nous renvoyons un tableau filtré contenant uniquement des tâches complètes non marquées pour suppression. Ensuite, nous utilisons simplement ce tableau longueur propriété pour masquer ou afficher le lien "Supprimer toutes les tâches terminées".


Tâches incomplètes restantes

Notre interface doit également afficher le nombre de tâches incomplètes. Semblable à notre tâches complètes () fonction ci-dessus, nous définissons un tâches incomplètes () fonctionner dans TaskViewModel:

t.incompleteTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function (tâche) return (! task.complete () && task._method! = "delete")) ;);

Nous avons ensuite accès à ce tableau filtré calculé dans notre vue, comme ceci:

Tâches incomplètes restantes:


Style Terminé Tâches

Nous voulons attribuer un style aux éléments terminés différemment des tâches de la liste, et nous pouvons le faire à notre avis avec Knockout. css contraignant. Modifier le tr balise d'ouverture dans notre tâche arrayForEach () boucle à la suivante.

 

Cela ajoute un Achevée Classe CSS à la ligne de la table pour chaque tâche si sa Achevée la propriété est vrai.


Dates de nettoyage

Débarrassons-nous de ces vilaines chaînes de date Ruby. Nous allons commencer par définir un format de date fonctionner dans notre TaskViewModel:

t.MONTHS = ["Jan", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Sept", "Oct", "Oct", "Nov", " Déc"]; t.dateFormat = function (date) if (! date) return "rafraichir pour voir la date du serveur";  var d = new Date (date); return d.getHours () + ":" + d.getMinutes () + "," + d.getDate () + "" + t.MONTHS [d.getMonth ()] + "," + d.getFullYear () ; 

Cette fonction est assez simple. Si, pour une raison quelconque, la date n’est pas définie, il suffit d’actualiser le navigateur pour extraire la date dans le champ initial. Tâche fonction de récupération. Sinon, nous créons une date lisible par l'homme avec le code JavaScript simple. Rendez-vous amoureux objet à l'aide de la MOIS tableau. (Remarque: il n'est pas nécessaire de mettre le nom du tableau en majuscule. MOIS, bien sûr; c’est simplement une façon de savoir qu’il s’agit d’une valeur constante qui ne devrait pas être modifiée.)

Ensuite, nous ajoutons les modifications suivantes à notre vue pour le créé à et updated_at Propriétés:

 

Cela passe le créé à et updated_at propriétés à la format de date() une fonction. Encore une fois, il est important de se rappeler que les propriétés de chaque tâche ne sont pas des propriétés normales. ce sont des fonctions. Afin de récupérer leur valeur, vous devez appeler la fonction (comme indiqué dans l'exemple ci-dessus). Remarque: $ root est un mot clé, défini par Knockout, qui fait référence au ViewModel. le format de date() La méthode, par exemple, est définie comme une méthode de la racine ViewModel (TaskViewModel).


Recherche de tâches

Nous pouvons effectuer des recherches dans nos tâches de différentes manières, mais nous allons garder les choses simples et effectuer une recherche frontale. Gardez toutefois à l'esprit qu'il est probable que ces résultats de recherche seront basés sur une base de données au fur et à mesure de la croissance des données, dans un souci de pagination. Mais pour le moment, définissons notre chercher() méthode sur TaskViewModel:

t.query = ko.observable ("); t.search = fonction (tâche) ko.utils.arrayForEach (t.tasks (), fonction (tâche) if (tâche.description () && t.query () ! = "") task.isvisible (task.description (). toLowerCase (). indexOf (t.query (). toLowerCase ())> = 0); sinon si (t.query () == "" ) task.isvisible (true); else task.isvisible (false);) renvoie true;

Nous pouvons voir que cela parcourt le tableau des tâches et vérifie si t.query () (valeur observable régulière) se trouve dans la description de la tâche. Notez que cette vérification s’effectue réellement dans le setter fonction pour le tâche.isvisible propriété. Si l'évaluation est faux, la tâche n'est pas trouvée et le est visible la propriété est définie sur faux. Si la requête est égale à une chaîne vide, toutes les tâches sont définies pour être visibles. Si la tâche n'a pas de description et que la requête est une valeur non vide, la tâche ne fait pas partie du jeu de données renvoyé et est masquée..

Dans notre index.erb fichier, nous avons configuré notre interface de recherche avec le code suivant:

La valeur d'entrée est définie sur le ko.observable requête. Ensuite, nous voyons que le keyup événement est spécifiquement identifié en tant que valueUpdate un événement. Enfin, nous avons défini une liaison d’événement manuelle sur keyup exécuter la recherche (t.search ()) une fonction. Aucune soumission de formulaire n'est nécessaire; la liste des éléments correspondants s'affichera et peut toujours être triée, supprimée, etc. Par conséquent, toutes les interactions fonctionnent à tout moment.


Code final

index.erb

          Faire        

Créer une nouvelle tâche

Rechercher des tâches

Tâches incomplètes restantes:

0 "> Supprimer toutes les tâches terminées
ID de base de données La description date ajoutée Date modifiée Achevée? Effacer
X

app.js

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); this.isvisible = ko.observable (true);  function TaskViewModel () var t = this; t.tasks = ko.observableArray ([]); t.newTaskDesc = ko.observable (); t.sortedBy = []; t.query = ko.observable ("); t.MONTHS = [" janvier "," février "," mars "," avril "," mai "," juin "," juin "," août "," sept. "," Oct "," Nov "," Dec "]; $ .getJSON (" http: // localhost: 9393 / tasks ", fonction (brute) var taches = $ .map (brute, fonction (élément)  retourne une nouvelle tâche (item)); t.tasks (tâches);); t.incompleteTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), function (task) return (! task.complete () && task._method! = "delete"));); t.completeTasks = ko.computed (function () return ko.utils.arrayFilter (t.tasks (), fonction ( task) return (task.complete () && task._method! = "delete")); // Opérations t.dateFormat = function (date) if (! date) return "actualiser pour voir le serveur date "; var d = new Date (date); renvoyer d.getHours () +": "+ d.getMinutes () +", "+ d.getDate () +" "+ t.MONTHS [d.getMonth ()] + "," + d.getFullYear (); t.addTask = function () var newtask = nouvelle tâche (description: this.newTaskDesc ()); $ .getJSON ("/ getdate", fonction (data) newtask.created_at (data.date); newtask.up du_daté (data.date); t.tasks.push (newtask); t.saveTask (newtask); t.newTaskDesc (""); ); t.search = fonction (tâche) ko.utils.arrayForEach (t.tasks (), fonction (tâche) if (tâche.description () && t.query ()! = "") tâche.isvisible (tâche .description (). toLowerCase (). indexOf (t.query (). toLowerCase ())> = 0); autrement if (t.query () == "") task.isvisible (true); autre task.isvisible (false);) renvoie true;  t.sort = fonction (champ) if (t.sortedBy.length && t.sortedBy [0] == champ && t.sortedBy [1] == 1) t.sortedBy [1] = 0; t.tasks.sort (function (premier, suivant) if (! suivant [champ] .call ()) retour 1; retour (suivant [champ] .call () < first[field].call()) ? 1 : (next[field].call() == first[field].call()) ? 0 : -1; );  else  t.sortedBy[0] = field; t.sortedBy[1] = 1; t.tasks.sort(function(first,next) if (!first[field].call()) return 1;  return (first[field].call() < next[field].call()) ? 1 : (first[field].call() == next[field].call()) ? 0 : -1; );   t.markAsComplete = function(task)  if (task.complete() == true) task.complete(true);  else  task.complete(false);  task._method = "put"; t.saveTask(task); return true;  t.destroyTask = function(task)  task._method = "delete"; t.tasks.destroy(task); t.saveTask(task); ; t.removeAllComplete = function()  ko.utils.arrayForEach(t.tasks(), function(task) if (task.complete()) t.destroyTask(task);  );  t.saveTask = function(task)  var t = ko.toJS(task); $.ajax( url: "http://localhost:9393/tasks", type: "POST", data: t ).done(function(data) task.id(data.task.id); );   ko.applyBindings(new TaskViewModel());

Notez le réarrangement des déclarations de propriété sur le TaskViewModel.


Conclusion

Vous avez maintenant les techniques pour créer des applications plus complexes!

Ces deux tutoriels vous ont guidé tout au long du processus de création d’une application d’une page avec Knockout.js et Sinatra. L'application peut écrire et récupérer des données, via une simple interface JSON, et possède des fonctionnalités qui vont au-delà des simples actions CRUD, telles que la suppression en masse, le tri et la recherche. Avec ces outils et exemples, vous avez maintenant les techniques pour créer des applications beaucoup plus complexes!