Ecrire TodoMVC sur dap. 2e partie

Il s'agit de la deuxième, dernière partie du didacticiel dans laquelle nous écrivons le client TodoMVC en utilisant le framework ds js réactif minimaliste.

Résumé de la première partie : nous avons reçu une liste de tâches du serveur au format JSON, créé une liste HTML à partir de celui-ci, ajouté la possibilité de modifier le nom et le signe d'achèvement pour chaque cas et implémenté une notification du serveur concernant ces modifications.

Cela reste à réaliser: suppression de cas arbitraires, ajout de nouveaux cas, installation / réinitialisation de masse et filtrage des cas en fonction de l'achèvement et fonction de suppression de tous les cas terminés. Voilà ce que nous allons faire. La version finale du client, à laquelle nous reviendrons dans cet article, peut être consultée ici .



L'option sur laquelle nous nous sommes décidés la dernière fois peut être rafraîchie ici .

Voici son code:

'#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("*@ todos:query" ,'LI'.d("$completed=.completed $editing= $patch=; a!" ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=($completed=#.checked)") ,'LABEL.view' .d("? $editing:!; ! .title") .e("dblclick","$editing=`yes") ,'INPUT.edit' .d("? $editing; !! .title@value") .ui("$patch=(.title=#.value)") .e("blur","$editing=") ,'BUTTON.destroy'.d("") ) .a("!? $completed $editing") .u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=") ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) .DICT({ todos : "//todo-backend-express.herokuapp.com/", headers: {"Content-type":"application/json"} }) .FUNC({ convert:{ dehttp: url=>url.replace(/^https?\:/,'') } }) .RENDER() 


Maintenant, il n'y a que cinquante lignes ici, mais à la fin de l'article, il y en aura deux fois - jusqu'à 100. Il y aura de nombreuses requêtes HTTP vers le serveur, alors veuillez ouvrir les outils de développement (dans Chrome, comme vous vous en souvenez, Ctrl + Shift + I) - il y aura Tout d'abord, l'onglet Réseau est intéressant, et deuxièmement, la console. N'oubliez pas non plus d'afficher le code de chaque version de notre page - dans Chrome, c'est Ctrl + U.

Ici, je dois faire une petite digression. Si vous n'avez pas lu la première partie du didacticiel, je vous recommande tout de même de commencer par celui-ci. Si vous le lisez mais ne comprenez rien, il vaut mieux le relire. Comme le montrent les commentaires sur mes deux articles précédents, la syntaxe et le principe de dap ne sont pas toujours immédiatement compris par un lecteur non préparé. Un autre article n'est pas recommandé pour la lecture aux personnes qui ne sont pas à l'aise avec l'apparence d'une syntaxe non similaire à la SI.



Cette deuxième partie du tutoriel sera un peu plus compliquée et intéressante que la première. [À FAIRE: demandez un jeton pour trouver une photo d'écolier en train d'exploser un cerveau sur Internet] .

Avec votre permission, je continuerai à numéroter les chapitres avec la partie 1. Là, nous avons compté jusqu'à 7. Donc,

8. Faire une liste de tâches de la variable d'état


Pour supprimer un cas de la liste, il existe un bouton BUTTON.destroy . La suppression consiste à envoyer une requête DELETE au serveur et à supprimer de la vue l'élément UL#todo-list > LI correspondant UL#todo-list > LI avec tout le contenu. Avec l'envoi d'une demande DELETE, tout est clair:

  ,'BUTTON.destroy'.ui("(@method`DELETE .url:dehttp):query") 


Mais avec la suppression d'un élément de l'écran, des options sont possibles. On pourrait simplement introduire une autre variable d'état, par exemple $deleted , et masquer l'élément avec CSS, y compris la classe CSS deleted

  ,'LI'.d("$completed=.completed $editing= $patch= $deleted=; a!" //  $deleted   "" ... ,'BUTTON.destroy'.d("(@method`DELETE .url:dehttp):query $deleted=`yes") //  $deleted -     ) .a("!? $completed $editing $deleted") //   CSS  .deleted{display:none} 


Et ça marcherait. Mais ce serait tricher. De plus, plus loin sur la ligne, nous aurons des filtres et des compteurs de cas actifs et terminés (ce qui se trouve dans #footer ). Par conséquent, il est préférable de retirer immédiatement l'objet de la liste des tâches honnêtement, «physiquement». Autrement dit, nous devons pouvoir modifier le tableau lui-même, que nous avons initialement reçu du serveur - ce qui signifie que ce tableau doit également devenir une variable d'état. Appelons-la $todos .

La portée de la variable $todos est de sélectionner l'ancêtre commun de tous les éléments qui accéderont à cette variable. Et il sera accessible par INPUT#new-todo partir de #header , et les compteurs à partir de #footer , et en fait UL#todo-list . L'ancêtre commun de tous est l'élément racine du modèle, #todoapp . Par conséquent, dans sa règle d, nous définirons la variable $todos . Au même endroit, nous y téléchargeons immédiatement les données du serveur. Et pour construire la liste UL#todo-list , nous en serons désormais aussi:

 '#todoapp'.d("$todos=todos:query" //   $todos      ... ,'UL#todo-list'.d("*@ $todos" //     $todos 


C'est important. Si pendant les tests, la liste de tâches ne se charge pas soudainement - il est fort possible que quelqu'un les ait tous supprimés (il s'agit d'un serveur public et tout peut y arriver).
Dans ce cas, veuillez consulter un exemple complet et créer quelques cas à expérimenter.

Nous regardons . Ici, $todos déclaré dans la règle d de l'élément #todoapp et est immédiatement initialisé avec les données nécessaires. Tout semble fonctionner, mais une caractéristique désagréable est apparue. Si le serveur répond à la demande depuis longtemps (Chrome vous permet de simuler cette situation: sur l'onglet Réseau des outils de développement, vous pouvez choisir différents modes de simulation de réseaux lents), alors notre nouvelle version de l'application jusqu'à ce que la demande soit terminée semble un peu triste - il n'y a que du CSS artefacts. Une telle image n'ajoutera certainement pas d'enthousiasme à l'utilisateur. Bien que la version précédente n'en souffre pas - jusqu'à ce que les données soient reçues sur la page, seule la liste elle-même manquait, mais d'autres éléments sont apparus immédiatement, sans attendre les données.

Voici le truc. Comme vous vous en souvenez, le :query converter est asynchrone. Cette asynchronie s'exprime par le fait que jusqu'à la fin de la requête, seule l'exécution de la règle courante est bloquée, c'est-à-dire la génération de l'élément, qui, en fait, a besoin des données demandées (ce qui est logique). La génération d'autres éléments n'est pas bloquée. Par conséquent, lorsque UL#todo-list accédé au serveur, seul celui-ci a été bloqué, mais pas #header et pas #footer , qui ont été dessinés immédiatement. Maintenant, #todoapp attend la fin de la demande.

9. Chargement différé des données


Pour corriger la situation et éviter de bloquer les éléments non impliqués, nous reporterons le chargement initial des données jusqu'à ce que tout soit déjà tracé. Pour ce faire, nous ne chargerons pas immédiatement les données dans la variable $todos , mais nous les initialisons simplement avec «rien»

 '#todoapp'.d("$todos=" //   $todos    "" 


Elle ne bloquera donc rien et le modèle entier fonctionnera - bien que pour l'instant avec une «liste de tâches» vide. Mais maintenant, avec un écran initial ennuyeux, vous pouvez modifier en toute sécurité $todos en y téléchargeant une liste de tâches. Pour ce faire, ajoutez ce descendant à #todoapp

  ,'loader' .u("$todos=todos:query") //  $todos,       .d("u") //   (u-)    


Cet élément a une règle u qui ressemble exactement à la règle de blocage que nous avons rejetée, mais il y a une différence fondamentale.
Permettez-moi de vous rappeler que la règle d (de bas en haut ) est la règle de génération d'élément qui est exécutée lorsque le modèle est construit de haut en bas , du parent aux descendants; et les règles u (à partir du haut ) sont des règles de réaction qui sont exécutées en réponse à un événement qui apparaît de bas en haut , de l'enfant au parent.
Donc, si quelque chose (y compris «rien») est assigné à une variable dans la règle d , cela signifie sa déclaration et son initialisation dans la portée de cet élément et ses descendants (les portées imbriquées sont implémentées dans dap, comme dans JS ) L'affectation dans les règles ascendantes signifie une modification d'une variable déclarée précédemment dans la portée. La déclaration et l'initialisation des variables dans la règle d permettent au parent de transmettre aux descendants de la hiérarchie les informations nécessaires à la construction, et la modification permet au parent de transmettre les mises à jour de ces informations vers le haut et ainsi de lancer la restructuration correspondante de tous les éléments qui en dépendent.

L'élément loader , étant un descendant de #todoapp , dans sa règle u modifie la variable $todos , chargeant des données depuis le serveur, ce qui provoque la régénération automatique de tous les éléments consommateurs de cette variable (et seulement eux, ce qui est important!). Les consommateurs d'une variable sont des éléments dont les règles d contiennent cette variable en tant que valeur r, c'est-à-dire Ceux qui lisent cette variable (en tenant compte de la portée) lors de la construction.

Nous avons maintenant un consommateur de la variable $todos - la liste de UL#todo-list très UL#todo-list , qui, en conséquence, sera reconstruite après le chargement des données.

  ,'UL#todo-list'.d("*@ $todos" //  ,   $todos 


Donc, maintenant nous avons une liste de choses à faire est une variable d'état dans #todoapp , sans bloquer le rendu initial du modèle.

10. Suppression et ajout de tâches


Maintenant, nous pouvons modifier $todos tous les sens. Commençons par supprimer les éléments. Nous avons déjà un bouton croisé BUTTON.destroy , qui jusqu'à présent envoie simplement les demandes de suppression du serveur

  ,'BUTTON.destroy'.ui("(@method`DELETE .url:dehttp):query") 


Il est nécessaire de s'assurer que l'objet correspondant est également supprimé de la variable $todos - et puisqu'il s'agit d'une modification, UL#todo-list , en tant que consommateur de cette variable, sera automatiquement reconstruit, mais sans l'élément supprimé.

En soi, dap ne fournit aucun moyen spécial pour manipuler les données. Les manipulations peuvent être parfaitement écrites dans des fonctions dans JS, et les règles dap leur fournissent simplement des données et récupèrent le résultat. Nous écrivons une fonction JS pour supprimer un objet d'un tableau sans connaître son numéro. Par exemple, ceci:

 const remove = (arr,tgt)=> arr.filter( obj => obj!=tgt ); 


Vous pouvez probablement écrire quelque chose de plus efficace, mais ce n'est pas le cas maintenant. Il est peu probable que notre application doive fonctionner avec des listes de tâches de millions d'éléments. L'important est que la fonction retourne un nouvel objet tableau, et pas seulement supprime l'élément de ce qu'il est.

Pour rendre cette fonction accessible à partir des règles dap, vous devez l'ajouter à la section .FUNC , mais avant cela, décidez comment nous voulons l'appeler. L'option la plus simple dans ce cas est, peut-être, de l'appeler à partir du convertisseur, qui accepte l'objet { todos, tgt } et renvoie un tableau filtré

 .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), //        remove: x => remove(x.todos,x.tgt) //     } }) 


mais rien ne vous empêche de définir cette fonction directement à l'intérieur de .FUNC (j'ai déjà dit que .FUNC est en fait une méthode JS régulière, et son argument est un objet JS normal?)

 .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), remove: x => x.todos.filter( todo => todo!=x.tgt ) } }) 


Maintenant, nous pouvons accéder à ce convertisseur à partir des règles DAP

  ,'BUTTON.destroy' .ui("$todos=($todos $@tgt):remove (@method`DELETE .url:dehttp):query") 


Ici, nous formons d'abord un objet qui, dans la notation JS, correspond à { todos, tgt:$ } , le transmettons au convertisseur :remove décrit dans .FUNC et .FUNC le résultat filtré à $todos , le modifiant ainsi. Ici $ est le contexte de données de l' élément, cet objet métier du tableau $todos sur lequel le modèle est construit. Après le symbole @ , l'alias de l'argument est indiqué. Si @ absent, alors le propre nom de l'argument est utilisé. Ceci est similaire à la récente innovation ES6 - raccourci immobilier .

De même, nous ajoutons un nouveau cas à la liste en utilisant l'élément INPUT#new-todo et la requête POST

  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$=(#.value@title) (@method`POST todos@url headers $):query $todos=($todos $@tgt):insert #.value=") ... .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), remove: x => x.todos.filter( todo => todo!=x.tgt ), //     insert: x => x.todos.concat( [x.tgt] ) //     } }) 


La règle de réaction de l'élément INPUT#new-todo à un événement d'interface utilisateur standard (pour les éléments INPUT , l'événement change est considéré comme le dap standard) comprend: la lecture des entrées utilisateur à partir de la propriété value de cet élément, la formation d'un contexte local $ avec cette valeur comme champ .title , l'envoi de $ context au serveur à l'aide de la méthode POST, en modifiant le tableau $todos en ajoutant le contexte $ tant que nouvel élément et enfin en effaçant la propriété value de l'élément INPUT .

Ici, un jeune lecteur peut se demander: pourquoi utiliser concat() lors de l'ajout d'un élément à un tableau, si cela peut être fait en utilisant push() normal? Un lecteur expérimenté comprendra immédiatement le problème et notera sa réponse dans les commentaires.

Nous regardons ce qui s'est passé: les cas sont ajoutés et supprimés normalement, les demandes correspondantes sont envoyées correctement au serveur (vous gardez l'onglet Réseau ouvert tout ce temps, non?). Mais que faire si nous voulons changer le nom ou le statut d'un cas fraîchement ajouté? Le problème est que pour notifier le serveur de ces modifications, nous avons besoin de .url , qui affecte le serveur à cette entreprise. Lorsque nous avons créé l'entreprise, .url ne connaissions pas son .url , respectivement, nous ne pouvons pas former la demande de modification PATCH correcte.

En fait, toutes les informations nécessaires sur le cas sont contenues dans la réponse du serveur à la demande POST, et il serait plus correct de créer un nouvel objet métier non seulement à partir de l'entrée utilisateur, mais à partir de la réponse du serveur, et d'ajouter cet objet à $todos - avec toutes les informations fournies informations sur le serveur, y compris le champ .url

  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$todos=($todos (@method`POST todos@url headers (#.value@title)):query@tgt ):insert #.value=") 


Nous regardons - d'accord, maintenant tout se déroule correctement. Les notifications au serveur concernant la modification des cas fraîchement créés vont bien.

On pourrait s'arrêter là, mais ... Mais si vous regardez de plus près, vous pouvez toujours remarquer un léger délai entre la saisie du nom du nouveau boîtier et le moment où il apparaît dans la liste. Ce délai est clairement visible si vous activez un réseau lent simulé. Comme vous l'avez peut-être deviné, il s'agit d'une demande au serveur: d'abord, nous demandons des données pour un nouveau cas au serveur, et seulement après les avoir reçus, nous modifions $todos . L'étape suivante, nous essaierons de corriger cette situation, mais je vais d'abord attirer votre attention sur un autre point intéressant. Si l'on revient un peu à la version précédente , on note: bien que la requête soit également là, le nouveau cas est ajouté à la liste instantanément, sans attendre la fin de la requête

  //    , :query   .ui("$=(#.value@title) (@method`POST todos@url headers $):query $todos=($todos $@tgt):insert #.value=") 


C'est une autre caractéristique de l'élaboration de convertisseurs asynchrones en dap: si le résultat du convertisseur asynchrone n'est pas utilisé (à savoir, il n'est affecté à rien), vous ne pouvez pas attendre sa fin - et l'exécution de la règle n'est pas bloquée. Ceci est souvent utile: vous avez peut-être remarqué que lors de la suppression de cas de la liste, ils disparaissent instantanément de l'écran, sans attendre le résultat de la demande DELETE. Cela est particulièrement visible si vous supprimez rapidement plusieurs cas consécutifs et suivez les demandes dans le panneau Réseau.

Mais, puisque nous utilisons le résultat de la requête POST - l'affectons au contexte $ - nous devons attendre qu'il se termine. Par conséquent, vous devez trouver un autre moyen de modifier $todos avant d'exécuter la demande POST. Solution: toujours, créez d'abord un nouvel objet métier et ajoutez-le immédiatement à $todos , laissez la liste dessiner, puis seulement, après le rendu, si l'entreprise .url pas .url (c'est-à-dire que l'entreprise vient d'être créée), exécutez une demande POST, et imposer son résultat au contexte de données de cette affaire.

Donc, tout d'abord, nous ajoutons simplement un blanc contenant uniquement .title à la .title

  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$todos=($todos (#.value@title)@tgt):insert #.value=") 


UL#todo-list > LI La règle de génération d'élément UL#todo-list > LI contient déjà a! démarrer une règle a après le premier dessin de l'élément. Nous pouvons y ajouter le lancement d'une requête POST en l'absence de .url . Pour injecter des champs supplémentaires dans le contexte, dap a l'opérateur &

  .a("!? $completed $editing; ? .url:!; & (@method`POST todos@url headers $):query") 


Nous regardons . Une autre chose! Même avec un réseau lent, la liste des tâches est mise à jour instantanément, et la notification du serveur et le chargement des données manquantes se produisent en arrière-plan, après avoir tracé la liste mise à jour.

11. Daw tout le monde!


Dans l'élément #header , il y a un bouton pour une installation en masse / réinitialisation du signe d'achèvement pour tous les cas de la liste. Pour l'affectation en masse de valeurs aux champs des éléments du tableau, nous écrivons simplement un autre convertisseur :assign , et l'appliquons à $todos en cliquant sur INPUT#toggle-all

  ,'INPUT#toggle-all type=checkbox' .ui("$todos=($todos (#.checked@completed)@src):assign") ... assign: x => x.todos && x.todos.map(todo => Object.assign(todo,x.src)) 


Dans ce cas, nous ne sommes intéressés que par le champ .completed , mais il est facile de voir qu'avec un tel convertisseur, vous pouvez modifier massivement les valeurs de tous les champs des éléments du tableau.
Ok, dans le tableau $todos , les $todos commutées, nous devons maintenant informer le serveur des modifications apportées. Dans l'exemple d'origine, cela se fait en envoyant des requêtes PATCH pour chaque cas - pas une stratégie très efficace, mais cela ne dépend plus de nous. Ok, pour chaque cas, nous envoyons une demande de PATCH

  .ui("*@ $todos=($todos (#.checked@completed)@src):assign; (@method`PATCH .url:dehttp headers (.completed)):query") 


Nous regardons : Un clic sur un daw commun aligne tous les daw individuels, et le serveur est averti par des requêtes PATCH appropriées. Norm

12. Filtrage des cas en fonction de l'achèvement


En plus de la liste de tâches réelle, l'application doit également être en mesure de filtrer les cas par le signe d'achèvement et d'afficher les compteurs des tâches terminées et inachevées. Bien sûr, pour le filtrage, il sera trivial d'utiliser la même méthode filter() fournie par JS lui-même.

Mais vous devez d'abord vous assurer que le champ .completed de chaque cas est toujours vrai, et lorsque vous cliquez sur l'index individuel du cas est mis à jour avec la variable $completed . Auparavant, cela n'était pas important pour nous, mais ce le sera désormais.

  ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=(.completed=$completed=#.checked) $recount=()") //  .completed        


Le point important ici est que le contexte de données de chaque cas est l'objet de cas lui-même, qui se trouve dans le tableau $todos . Pas une seule copie, ou construction connexe, mais l'objet lui-même. Et tous les appels aux champs .title , .completed url - à la fois en lecture et en écriture - s'applique directement à cet objet. Par conséquent, pour que le filtrage du tableau $todos fonctionne correctement, nous devons que l'achèvement du cas soit reflété non seulement dans le daw à l'écran, mais également dans le champ .completed de l'objet .completed .

Afin de n'afficher que les cas avec le signe nécessaire de complétude .completed dans la liste, nous .completed simplement $todos selon le filtre sélectionné. Le filtre sélectionné est, vous l'aurez deviné, une autre variable d'état de notre application, et nous l'appellerons: $filter . Pour filtrer $todos fonction du $filter sélectionné $filter allons le long de la vignette et ajoutons simplement un autre convertisseur, de la forme {list, filter} => la liste filtrée , et nous prendrons les noms et les fonctions de filtrage du "tableau associatif" (c'est-à-dire JS ordinaire objet) todoFilters

 const todoFilters={ "All": null, "Active": todo => !todo.completed, "Completed": todo => !!todo.completed }; '#todoapp'.d("$todos= $filter=" //   $filter ... ,'UL#todo-list'.d("* ($todos $filter):filter" ... ,'UL#filters'.d("* filter" //  filter      .DICT ,'LI' .d("! .filter") .ui("$filter=.") //    "$filter=.filter" ) ... .DICT({ ... filter: Object.keys(todoFilters) //["All","Active","Completed"] }) .FUNC({ convert:{ ... filter: x =>{ const a = x.todos, f = x.filter && todoFilters[x.filter]; return a&&f ? a.filter(f) : a; } } }) 


Nous vérifions . Les filtres fonctionnent correctement. Il y a une nuance dans le fait que les noms des filtres sont affichés ensemble, car ici, nous sommes un peu sortis de la structure DOM de l'original et sommes sortis de CSS. Mais nous y reviendrons un peu plus tard.

13. Compteurs de cas terminés et actifs.


Pour afficher les compteurs des cas terminés et actifs, il suffit de filtrer $todos avec les filtres appropriés et d'afficher les longueurs des tableaux résultants

  ,'#footer'.d("$active=($todos @filter`Active):filter $completed=($todos @filter`Completed):filter" ,'#todo-count'.d("! (active $active.length)format") //  length    active ... ,'#clear-completed'.d("! (completed $completed.length)format") ) ... .DICT({ ... active: "{length} items left", completed: "Clear completed items ({length})" }) 


Dans ce formulaire, les compteurs affichent les valeurs correctes au démarrage, mais ne répondent pas aux changements ultérieurs dans la fin des affaires (en cliquant sur les daws). Le fait est que les clics sur les choucas, changeant l'état de chaque cas individuel, ne changent pas l'état de $todos - une modification d'un élément de tableau n'est pas une modification du tableau lui-même. Par conséquent, nous avons besoin d'un signal supplémentaire sur la nécessité de réenregistrer les cas. Un tel signal peut être une variable d'état supplémentaire, qui est modifiée à chaque fois qu'un nouveau comptage est nécessaire. Appelez ça $recount .Nous déclarerons un ancêtre commun dans la règle d, nous le mettrons à jour lorsque nous cliquons sur les mâles, et nous #footerferons de l' élément un consommateur - pour cela, mentionnez simplement cette variable dans sa règle d

 '#todoapp'.d("$todos= $filter= $recount=" //  $recount     ... ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=(.completed=$completed=#.checked) $recount=()") //  $recount    ... ,'#footer'.d("$active=($todos @filter`Active):filter $completed=($todos @filter`Completed):filter $recount" //  $recount 


Maintenant, tout fonctionne comme il se doit, les compteurs sont mis à jour correctement.

14. Suppression de tous les cas terminés.


La suppression par lot des cas dans TodoMVC est implémentée comme non casher en tant que modification par lot - par plusieurs demandes. Eh bien, soupirons, haussons les mains et exécutons par DELETE-request pour chaque cas terminé - et nous les avons déjà tous $completed. En conséquence, $todosaprès la suppression des dossiers terminés, ce qui devrait déjà être$active

  ,'#clear-completed' .d("! (completed $completed.length)format") .ui("$todos=$active; *@ $completed; (@method`DELETE .url:dehttp):query") 


Nous regardons : nous créons des affaires inutiles, les marquons avec des griffes et les supprimons. L'onglet Réseau montrera l'horreur de cette approche des opérations par lots.

15. Statut dans la barre d'adresse


Retour à la sélection des filtres. Dans l'exemple d'origine, le filtre sélectionné se reflète dans la barre d'adresse après #. Lorsque vous modifiez le # -fragment dans la barre d'adresse manuellement ou pendant la navigation, le filtre sélectionné change également. Cela vous permet d'accéder à la page de l'application par URL avec le filtre de tâches déjà sélectionné.

Vous pouvez écrire avec un location.hashopérateur urlhash, par exemple, dans la règle a d'un élément #todoapp(ou l'un de ses descendants), qui sera exécutée à chaque fois$filter

 .a("urlhash $filter") 


Et vous pouvez initialiser la variable avec une $filtervaleur de la barre d'adresse, puis la mettre à jour par l'événement hashchange à l' aide d'un pseudo-convertisseur :urlhashqui retourne l'état actuel location.hash(sans #)

 .d("$todos= $filter=:urlhash $recount=" .e("hashchange","$filter=:urlhash") 


L'événement hashchange est déclenché par le navigateur lorsqu'un # -fragment dans la barre d'adresse change. Cependant, pour une raison que window, et document.bodypeut écouter cet événement. Pour suivre cet événement à partir d'un élément #todoapp, vous devrez ajouter un opérateur à sa règle d listenqui signe l'élément pour relayer les événements de l'objetwindow

 '#todoapp' .a("urlhash $filter") .e("hashchange","$filter=:urlhash") .d("$todos= $filter=:urlhash $recount=; listen @hashchange" 


Nous regardons : changer de filtre, suivre les changements dans la barre d'adresse, suivre les liens avec #Active , #All , #Completed . Tout fonctionne. Mais revenons à l'original. Là, il semble, le choix du filtre est mis en œuvre - en cliquant sur les liens. Bien que ce ne soit pas très pratique, nous ferons de même pour être complet.

  ,'UL#filters'.d("* filter" ,'LI'.d("" ,'A'.d("!! (`# .filter)concat@href .filter@") ) ) 


Et pour faire ressortir le filtre sélectionné, ajoutez un opérateur de stylisation conditionnelle !?qui ajoutera une classe CSS à l'élément selectedsi la valeur dans le champ de .filtercontexte est égale à la valeur de la variable$filter

  ,'A'.d("!! (`# .filter)concat@href .filter@; !? (.filter $filter)eq@selected") 


Sous cette forme, la fonctionnalité de notre application dap est déjà pleinement (pour autant que je sache) cohérente avec ce que fait l' original .

16. Quelques touches finales


Je n'aime pas vraiment que dans l'original la forme du curseur ne change pas sur les éléments actifs, nous allons donc ajouter headce style à notre document HTML

  [ui=click]{cursor:pointer} 


Donc, au moins, nous verrons où vous pouvez cliquer.

Oh oui! Il reste à écrire le mot "todos" en majuscules. Mais ici, je me permettrai peut-être enfin de montrer un peu d'imagination et de créativité, et au lieu de simplement "todos" j'écrirai "dap todos"

  ,'H1'.d("","dap todos") 


Ouah. Maintenant, notre application peut être considérée comme complète et le tutoriel peut être tenu (si vous lisez honnêtement ces lignes).

En conclusion


Peut-être qu'en lisant, vous avez eu l'impression que le programme dap est écrit par essais et erreurs - ce sont tous «voyons ce qui s'est passé», «il semble fonctionner, mais il y a une nuance», etc. Ce n'est en fait pas le cas.Toutes ces nuances sont assez évidentes et prévisibles lors de l'écriture de code. Mais j'ai pensé qu'il serait utile de montrer par l'exemple de ces nuances pourquoi telle ou telle décision est présente dans les règles et pourquoi elle se fait de cette façon et pas autrement.

Posez, comme on dit, des questions.

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


All Articles