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!"
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"
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="
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")
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"
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?\:/,''),
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 ),
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
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=()")
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="
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")
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 #footer
ferons de l' élément un consommateur - pour cela, mentionnez simplement cette variable dans sa règle d '#todoapp'.d("$todos= $filter= $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, $todos
aprè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.hash
opé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 $filter
valeur de la barre d'adresse, puis la mettre à jour par l'événement hashchange à l' aide d'un pseudo-convertisseur :urlhash
qui 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.body
peut é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 listen
qui 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 selected
si la valeur dans le champ de .filter
contexte 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 head
ce 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.