Dap en action. Écrire TodoMVC. Partie 1

Le premier article sur le dap , évidemment, n'est pas devenu mon succès d'écriture: la grande majorité des commentaires se résumaient à "niasilil" et "niasilil, mais je condamne". Et le prix du seul commentaire constructif de haut niveau va à OldVitus , pour des conseils pour démontrer le dap en utilisant TodoMVC comme exemple, afin qu'il y ait quelque chose à comparer. Que vais-je faire dans cet article.

TodoMVC , si quelqu'un ne le sait pas, il s'agit d'un monde d'appel UI standard, qui vous permet de comparer des solutions au même problème - la "liste de tâches " conditionnelle - en utilisant différents cadres. La tâche, avec toute sa simplicité (sa solution sur les dap breaks «en un seul écran»), est très illustrative. Par conséquent, en utilisant son exemple, j'essaierai de montrer comment les tâches typiques d'un frontal Web sont implémentées à l'aide de dap.

Je n'ai pas cherché et étudié la description formelle du problème, mais j'ai décidé de simplement inverser l'un des exemples. Le backend de cet article ne nous intéresse pas, nous ne l'écrirons donc pas nous-mêmes, mais utilisez l' un des prêts sur le site www.todobackend.com , et à partir de là, nous prendrons un exemple de client et un fichier CSS standard.

Pour utiliser dap, vous n'avez pas besoin de télécharger et d'installer quoi que ce soit. Aucune npm install et c'est tout. Il n'est pas nécessaire de créer des projets avec une structure de répertoires spécifique, des manifestes et d'autres attributs de réussite informatique. Éditeur de texte et navigateur suffisants. Pour déboguer les demandes XHR, vous pouvez également avoir besoin d'un serveur Web - assez simple, comme cette extension pour Chrome. L'ensemble de notre frontend consistera en un seul fichier .html (faisant bien sûr référence au script du moteur DAP et au fichier CSS TodoMVC standard)

Donc, à partir de zéro.

1. Créez un fichier .html


 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo -- dap sample</title> <link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/> <script src="https://dap.js.org/0.4.js"></script> </head> <body> <script> //   dap </script> </body> </html> 

La préparation html habituelle dans laquelle nous incluons le fichier CSS, gracieusement fourni par le site www.todobackend.com et le moteur dap, non moins gracieusement fourni par le site dap.js.org

2. Copiez la structure DOM de l'exemple d'origine


Pour utiliser le fichier CSS standard sans modifications, nous respecterons la même structure DOM que l' exemple d'origine . Ouvrez-le dans le navigateur Chrome, appuyez sur Ctr + Maj + I, sélectionnez l'onglet Éléments et voyez que l'application elle-même est dans la section id="todo-app"> élément section id="todo-app">



En ouvrant ce sous-arbre un par un, nous réécrivons sa structure dans notre fichier .html. Maintenant, nous esquissons simplement d'une manière rapide, et non pas en écrivant du code, nous écrivons donc simplement les signatures des éléments entre «guillemets simples» et entre parenthèses de leurs enfants. S'il n'y a pas d'enfants, nous dessinons des crochets vides. Nous surveillons les indices et l'équilibre des parenthèses.

 //   dap '#todoapp'( '#header'( 'H1'() 'INPUT#new-todo placeholder="What needs to be done?" autofocus'() ) '#main'( '#toggle-all type=checkbox'() 'UL#todo-list'( 'LI'( 'INPUT.toggle type=checkbox'() 'LABEL'() 'BUTTON.destroy'() ) ) ) '#footer'( '#todo-count'() 'UL#filters'( 'LI'() ) '#clear-completed'() ) ) 

Remarque: les éléments répétitifs (par exemple, ici ce sont des éléments LI ) nous écrivons une fois dans la structure, même s'il y en a plusieurs dans l'original; évidemment, ce sont des tableaux du même modèle.

Le format de signature, je pense, est compréhensible pour tous ceux qui ont écrit du HTML et du CSS avec leurs mains, donc je ne m'attarderai pas là-dessus en détail pour l'instant. Je peux seulement dire que les balises sont écrites en majuscules, et l'absence d'une balise équivaut à la présence d'une balise DIV. L'abondance d'éléments # (avec id) ici est due aux spécificités du fichier CSS inclus, qui utilise principalement des sélecteurs id.

3. N'oubliez pas que le programme dap est Javascript


Pour nous éviter des crochets inutiles dans le code, le moteur dap injecte plusieurs méthodes directement dans String.prototype (je suis conscient que l'implémentation de vos méthodes dans des objets standard est ayahay, mais ... en bref, nous avons passé), qui convertit la chaîne de signature en dap modèle. L'une de ces méthodes est .d(rule, ...children) . Le premier argument qu'il prend est une règle de génération (règle d ), et le reste des arguments est un nombre arbitraire d'enfants.

Sur la base de ces nouvelles connaissances, nous ajoutons notre code de sorte qu'au lieu de chaque accolade ouvrante, nous ayons la séquence .d("" , et avant chaque guillemet simple d'ouverture, sauf la toute première, il y a une virgule. Life hack: vous pouvez utiliser le remplacement automatique.

 '#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("" ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("") ,'LABEL'.d("") ,'BUTTON.destroy'.d("") ) ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) 

Voila! Nous avons obtenu un arbre d'appels à la méthode .d , qui est prête à se transformer en un modèle dap. Les chaînes vides "" sont les germes des futures règles d, et les enfants sont des arguments séparés par des virgules. Formellement, il s'agit d'un programme dap valide, mais pas encore complètement avec l'échappement dont nous avons besoin. Mais il peut déjà être lancé! Pour ce faire, après la fermeture du crochet racine, ajoutez la méthode .RENDER() . Cette méthode, comme son nom l'indique, rend le modèle résultant.

Donc, à ce stade, nous avons un fichier .html avec ce contenu:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo -- dap sample</title> <link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/> <script src="https://dap.js.org/0.4.js"></script> </head> <body> <script> '#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("" ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("") ,'LABEL'.d("") ,'BUTTON.destroy'.d("") ) ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) .RENDER() //   dap   </script> </body> </html> 

Vous pouvez l' ouvrir dans un navigateur pour vous assurer que les éléments DOM sont générés, que les styles CSS sont appliqués, il ne reste plus qu'à remplir ce modèle de données.

4. Obtenez les données


Nous allons à la page d'origine , ouvrons l'onglet Réseau dans les outils, activons le filtre XHR et voyons d'où viennent les données et sous quelle forme.





D'accord, d'accord. La liste des tâches est prise directement à partir de todo-backend-express.herokuapp.com en tant que tableau d'objets json. Super

Pour recevoir des données, dap a un convertisseur intégré :query qui "convertit" de manière asynchrone l'URL en données reçues de celle-ci. Nous n'écrirons pas l'URL elle-même directement dans la règle, mais nous la désignerons avec les todos constants; alors toute la conception de l'exploration de données ressemblera à ceci:

 todos:query 

et écrivez la constante todos elle-même dans le dictionnaire - dans la section .DICT , juste avant .RENDER() :

 '#todoapp'.d("" ... ) .DICT({ todos : "https://todo-backend-express.herokuapp.com/" }) .RENDER() 

Après avoir reçu le tableau de todos , nous en construisons une liste de .title : pour chaque cas, nous prenons le nom du champ .title et l'écrivons dans l'élément LABEL , et du champ .completed prenons le signe de «complétude» et écrivons dans la propriété checked de l'élément de checked INPUT.toggle . C'est fait comme ceci:

  ,'UL#todo-list'.d("*@ todos:query" //  *       ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("#.checked=.completed") // #  " " ,'LABEL'.d("! .title") //  !      ,'BUTTON.destroy'.d("") ) ) 

Nous mettons à jour cette page à nous dans le navigateur et ... si vous la démarrez à partir du système de fichiers, alors rien ne se passe. Le problème est que les navigateurs modernes n'autorisent pas les demandes XHR interdomaines à partir de documents locaux.



Il est temps de regarder notre page via http - en utilisant n'importe quel serveur Web local. Eh bien, ou si vous n'êtes pas prêt à écrire dap de vos propres mains, voir les versions séquentielles de la page en utilisant mes liens (n'oubliez pas de regarder la source - dans Chrome, cela se fait en utilisant Ctrl + U)

Donc, nous allons sur notre page à http: // et voyons que les données arrivent, la liste est en cours de construction. Super! Vous maîtrisez déjà les opérateurs * et ! , convertisseur :query , constantes et accès aux champs de l'élément de tableau courant. Regardez à nouveau le code résultant. Cela vous semble encore illisible?

5. Ajouter un état


Vous avez peut-être déjà essayé de cliquer sur les coches dans la liste des tâches. Les cases à cocher elles-mêmes changent de couleur, mais, contrairement à l'original, l'élément parent LI ne change pas de style (les «affaires terminées» devraient devenir grises et barrées, mais cela ne se produit pas) - les choses ne changent pas leur état . Mais ces éléments n'ont pas encore d'état et ne peuvent donc pas le changer. Maintenant, nous allons le réparer.

Ajoutez un état "d'achèvement" à l'élément LI . Pour ce faire, définissez la variable d'état $completed dans sa règle-d. À l' INPUT.toggle , qui peut changer cet état, nous assignerons une règle de réaction appropriée (règle ui ), qui définira la variable $completed conformément à son propre indicateur checked («daw est activé»). Selon l'état de $completed élément LI activera ou désactivera la classe CSS "terminée".

  ,'UL#todo-list'.d("*@ todos:query" ,'LI'.d("$completed=.completed"//  ,    .completed ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") //       .ui("$completed=#.checked") //    $completed ,'LABEL'.d("! .title") ,'BUTTON.destroy'.d("") ) .a("!? $completed") //     $completed,    css- completed ) 

De telles manipulations avec des classes CSS sont une chose assez courante, donc il y a un opérateur spécial dans dap pour elles!?
Veuillez noter que nous le faisons dans la règle a (à partir du mot accumuler). Pourquoi pas dans la règle D? La différence entre ces deux types de règles est que lors de la mise à jour, la règle d reconstruit complètement le contenu de l'élément, supprimant à nouveau l'ancien et le nouveau, tandis que la règle a ne touche pas le contenu existant de l'élément, mais «ajoute» le résultat à ce qui est déjà là. La modification d'un seul attribut d'un élément LI ne nécessite pas de restructurer le reste de son contenu, il est donc plus rationnel de le faire dans la règle a.

Nous regardons le résultat . C'est déjà mieux: en cliquant sur les cases à cocher, vous modifiez l'état de la tâche à effectuer correspondante et, conformément à cet état, le style visuel de l'élément change également. Mais il y a toujours un problème: si la liste des tâches terminées était initialement présente, elles ne seront pas grises, car par défaut la règle a n'est pas exécutée lors de la génération de l'élément. Pour l'exécuter même pendant la génération, nous ajoutons l'opérateur a! À la règle d de l'élément LI

  ,'LI'.d("$completed=.completed; a!" //      $completed    a- 

Nous regardons . Ok Avec le statut de $completed compris. Les cas terminés sont stylisés correctement à la fois au démarrage initial et lors de la commutation manuelle suivante.

6. Modification des noms de cas


Retour à l' original . En double-cliquant sur le nom du boîtier, le mode d'édition est activé, dans lequel ce nom peut être modifié. Là, il est implémenté de manière à ce que le modèle de mode de visualisation «view» (avec un daw, un titre et un bouton de suppression) soit complètement masqué, et l'élément INPUT class="edit" s'affiche. Nous le ferons un peu différemment - nous ne cacherons que l'élément LABEL , car les deux autres éléments n'interfèrent pas avec notre édition. Ajoutez simplement la classe view à l'élément LABEL .

Pour l'état «édition», définissez la variable $editing dans l'élément LI . Initialement, il (état) est réinitialisé, s'active par dblclick sur l'élément LABEL et s'éteint lorsque l'élément INPUT.edit est flou. Nous écrivons donc:

  ,'LI'.d("$completed=.completed $editing=; a!" //       ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$completed=#.checked") ,'LABEL.view' .d("? $editing:!; ! .title") //  $editing ,     .e("dblclick","$editing=`yes") //  dblclick  $editing ,'INPUT.edit' .d("? $editing; !! .title@value") //  $editing  .ui(".title=#.value") //  .title   change (ui     INPUT) .e("blur","$editing=") //  $editing   blur ,'BUTTON.destroy'.d("") ).a("!? $completed $editing") //   $completed  $editing  css-  'LI' 

Nous pouvons maintenant modifier les noms des cas.

7. Envoi de données au serveur


Ok, nous pouvons déjà éditer des choses dans le navigateur, mais ces changements doivent également être transférés au serveur. Nous regardons comment l'original le fait:



Les modifications sont envoyées au serveur en utilisant la méthode PATCH avec une certaine URL de la forme http://todo-backend-express.herokuapp.com/28185 , qui, évidemment, est unique pour chaque cas. Cette URL est indiquée par le serveur dans le champ .url pour chaque cas de la liste. Autrement dit, tout ce qui nous est demandé pour mettre à jour le cas sur le serveur est d'envoyer une demande PATCH à l'adresse spécifiée dans le champ .url , avec les données modifiées au format JSON:

  ,'INPUT.edit' .d("? $editing; !! .title@value") .ui(".title=#.value; (@method`PATCH .url (@Content-type`application/json)@headers (.title):json.encode@body):query") .e("blur","$editing=") 

Ici, nous utilisons le même convertisseur :query , mais dans une version plus détaillée. Lorsque :query est appliquée à une chaîne simple, cette chaîne est traitée comme une URL et une demande GET est exécutée. Si :query reçoit un objet complexe, comme dans ce cas, il le traite comme une description détaillée de la demande contenant les champs .method , .headers , .headers et .body , et exécute la demande conformément à ceux-ci. Ici, immédiatement après la mise à jour du .title envoyons au serveur une demande PATCH avec ce .title mis à jour

Mais il y a une nuance. Nous obtenons le champ .url du serveur, il ressemble à ceci: http://todo-backend-express.herokuapp.com/28185 , c'est-à-dire que le protocole http: // est codé en dur si notre client est également ouvert via http: // alors tout va bien. Mais si le client est ouvert via https: //, alors un problème se pose: pour des raisons de sécurité, le navigateur bloque le trafic http depuis la source https.

Il est résolu simplement: si vous supprimez le protocole de .url , la demande passera par le protocole de page. Alors faisons-le: écrivez le convertisseur approprié - dehttp , et nous passerons .url travers. Les convertisseurs personnalisés (et autres fonctionnalités) sont .FUNC dans la section .FUNC :

  .ui(".title=#.value; (@method`PATCH .url:dehttp (@Content-type`application/json)@headers (.title):json.encode@body):query") ... .FUNC({ convert:{ //  -         dehhtp: url=>url.replace(/^https?\:/,'')//    URL } }) 

Il est également judicieux de placer l'objet headers dans le dictionnaire afin qu'il puisse être utilisé dans d'autres requêtes:

  .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title):json.encode@body):query") ... .DICT({ todos : "//todo-backend-express.herokuapp.com/", headers: {"Content-type":"application/json"} }) 

Eh bien, pour le feng shui complet, nous utiliserons une autre propriété utile du convertisseur :query - encodage automatique du corps de la requête dans json conformément à l'en Content-type:application/json tête Content-type:application/json . Par conséquent, la règle ressemblera à ceci:

  .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title)):query") 

Alors regarde . D'accord, les noms de cas changent désormais non seulement dans le navigateur, mais aussi sur le serveur. Mais! Non seulement le nom de l'affaire peut changer, mais aussi son état d'avancement - completed . Il doit donc également être envoyé au serveur.
Vous pouvez ajouter la même demande PATCH à l'élément INPUT.toggle , envoyez simplement (.completed) au lieu de (.completed) :

  ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$completed=#.checked; (@method`PATCH .url:dehttp headers (.completed:?)):query") 

Et vous pouvez mettre cette demande PATCH «hors des crochets»:

  ,'LI'.d("$completed=.completed $editing= $patch=; a!" // $patch - ""   ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=($completed=#.checked)") //   $patch  completed ,'LABEL.view' .d("? $editing:!; ! .title") .e("dblclick","$editing=`yes") ,'INPUT.edit' .d("? $editing; !! .title@value") .ui("$patch=(.title=#.value)") //   $patch  title .e("blur","$editing=") ,'BUTTON.destroy'.d("") ) .a("!? $completed $editing") //  $patch  ,   ,   .u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=") 

Voici le truc. Les règles de réaction appartiennent au groupe «up-rules», qui sont exécutées «de bas en haut» - du descendant au parent, à la racine elle-même (cette séquence peut être interrompue si nécessaire). Cela ressemble un peu aux événements contextuels dans le DOM. Par conséquent, certains fragments de la réaction communs à plusieurs descendants peuvent être attribués à un ancêtre commun.

Plus précisément, dans notre cas, le gain d'une telle délégation n'est pas particulièrement perceptible, mais s'il y avait des champs plus modifiables, alors mettre cette demande volumineuse (selon les normes DAP, bien sûr) dans une règle générale aiderait grandement à garder le code simple et lisible. Je le recommande donc.

Nous regardons : Maintenant, les changements de nom et les changements d'état sont envoyés au serveur.

Dans le prochain article, si vous êtes intéressé, envisagez d'ajouter, de supprimer et de filtrer les cas. En attendant, vous pouvez voir le résultat final et d'autres exemples de code dap sur dap.js.org/docs

Source: https://habr.com/ru/post/fr480912/


All Articles