Veuillez traiter cette question comme strictement éducative. Je suis toujours intéressé à entendre de nouvelles réponses et idées pour mettre en œuvre ce
tl; dr
Comment implémenter la liaison de données bidirectionnelle avec JavaScript?
Liaison de données au DOM
Par liaison de données au DOM, je veux dire par exemple, avoir un objet JavaScript a
avec une propriété b
. Ensuite, avoir un <input>
élément DOM (par exemple), lorsque l'élément DOM change, a
change et vice versa (c'est-à-dire, je veux dire la liaison de données bidirectionnelle).
Voici un diagramme d'AngularJS sur ce à quoi cela ressemble:
Donc, fondamentalement, j'ai JavaScript similaire à:
var a = {b:3};
Ensuite, un élément d'entrée (ou autre formulaire) comme:
<input type='text' value=''>
Je voudrais que la valeur de l'entrée soit a.b
la valeur de (par exemple), et lorsque le texte d'entrée change, je voudrais a.b
aussi le changer. En cas de a.b
modification de JavaScript, l'entrée change.
La question
Quelles sont les techniques de base pour y parvenir en JavaScript simple?
En particulier, j'aimerais qu'une bonne réponse fasse référence à:
- Comment la reliure fonctionnerait-elle pour les objets?
- Comment l'écoute du changement dans le formulaire pourrait-elle fonctionner?
- Est-il possible de manière simple de ne modifier le code HTML qu'au niveau du modèle? Je ne voudrais pas garder la trace de la liaison dans le document HTML lui-même, mais uniquement en JavaScript (avec les événements DOM et JavaScript en faisant référence aux éléments DOM utilisés).
Qu'est-ce que j'ai essayé?
Je suis un grand fan de Moustache, j'ai donc essayé de l'utiliser pour les modèles. Cependant, j'ai rencontré des problèmes lorsque j'essayais d'effectuer la liaison de données elle-même, car Moustache traite le HTML comme une chaîne, donc après avoir obtenu son résultat, je n'ai aucune référence à l'endroit où se trouvent les objets dans mon modèle de vue. La seule solution de contournement à laquelle je pouvais penser était de modifier la chaîne HTML (ou l'arborescence DOM créée) elle-même avec des attributs. Cela ne me dérange pas d'utiliser un moteur de modélisation différent.
Fondamentalement, j'ai eu le sentiment fort de compliquer le problème et il existe une solution simple.
Remarque: veuillez ne pas fournir de réponses utilisant des bibliothèques externes, en particulier des milliers de lignes de code. J'ai utilisé (et j'aime!) AngularJS et KnockoutJS. Je ne veux vraiment pas de réponses sous la forme «utiliser le cadre x». De manière optimale, j'aimerais qu'un futur lecteur ne sachant pas comment utiliser de nombreux frameworks pour comprendre comment implémenter la liaison de données bidirectionnelle elle-même. Je ne m'attends pas à une réponse complète , mais à une idée claire.
la source
Réponses:
Une abstraction qui met à jour les deux objets
Je suppose qu'il existe d'autres techniques, mais en fin de compte, j'aurais un objet contenant une référence à un élément DOM associé et fournissant une interface qui coordonne les mises à jour de ses propres données et de son élément associé.
Le
.addEventListener()
fournit une interface très agréable pour cela. Vous pouvez lui donner un objet qui implémente l'eventListener
interface, et il invoquera ses gestionnaires avec cet objet commethis
valeur.Cela vous donne un accès automatique à la fois à l'élément et à ses données associées.
Définir votre objet
L'héritage prototypique est un bon moyen de l'implémenter, bien que cela ne soit pas obligatoire bien sûr. Vous devez d'abord créer un constructeur qui reçoit votre élément et certaines données initiales.
Ici, le constructeur stocke donc l'élément et les données sur les propriétés du nouvel objet. Il lie également un
change
événement au donnéelement
. La chose intéressante est qu'il passe le nouvel objet au lieu d'une fonction comme deuxième argument. Mais cela seul ne fonctionnera pas.Implémentation de l'
eventListener
interfacePour que cela fonctionne, votre objet doit implémenter l'
eventListener
interface. Pour cela, il suffit de donner à l'objet unehandleEvent()
méthode.C'est là que l'héritage entre en jeu.
Il existe de nombreuses façons différentes de structurer cela, mais pour votre exemple de coordination des mises à jour, j'ai décidé de faire en sorte que la
change()
méthode n'accepte qu'une valeur ethandleEvent
que cette valeur soit transmise au lieu de l'objet événement. De cette façon, lechange()
peut également être invoqué sans événement.Alors maintenant, lorsque l'
change
événement se produit, il mettra à jour à la fois l'élément et la.data
propriété. Et la même chose se produira lorsque vous appellerez.change()
votre programme JavaScript.Utiliser le code
Il vous suffit maintenant de créer le nouvel objet et de le laisser effectuer des mises à jour. Les mises à jour dans le code JS apparaîtront sur l'entrée et les événements de modification sur l'entrée seront visibles pour le code JS.
DÉMO: http://jsfiddle.net/RkTMD/
la source
Mustache.render(template,object)
, en supposant que je souhaite garder un objet synchronisé avec le modèle (non spécifique à Moustache), comment pourrais-je procéder à ce sujet?input
doivent mettre à jour l'un de ces points, il y aurait un mappage de l'entrée à ce point. Je vais voir si je peux trouver un exemple rapide.Template
constructeur principal qui effectue l'analyse, contient les différentsMyCtor
objets et fournit une interface pour mettre à jour chacun par son identifiant. Faites moi savoir si vous avez des questions. :) EDIT: ... utilisez ce lien à la place ... J'avais oublié que j'avais une augmentation exponentielle de la valeur d'entrée toutes les 10 secondes pour démontrer les mises à jour JS. Cela le limite.J'ai donc décidé de jeter ma propre solution dans le pot. Voici un violon de travail . Notez que cela ne fonctionne que sur les navigateurs très modernes.
Ce qu'il utilise
Cette implémentation est très moderne - elle nécessite un navigateur (très) moderne et les utilisateurs de deux nouvelles technologies:
MutationObserver
s pour détecter les changements dans le dom (les écouteurs d'événements sont également utilisés)Object.observe
pour détecter les changements dans l'objet et notifier le dom. Danger, puisque cette réponse a été écrite Oo a été discutée et décidée par le TC ECMAScript, envisagez un polyfill .Comment ça fonctionne
domAttribute:objAttribute
mappage - par exemplebind='textContent:name'
La solution
Voici la
dataBind
fonction, notez que ce n'est que 20 lignes de code et pourrait être plus court:Voici quelques utilisations:
HTML:
JavaScript:
Voici un violon de travail . Notez que cette solution est assez générique. Le calage Object.observe et l'observateur de mutation est disponible.
la source
obj.name
a un setter, il ne peut pas être observé de l'extérieur, mais doit diffuser qu'il a changé de l'intérieur du setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - jette une clé dans les travaux pour Oo () si vous voulez un comportement plus complexe et interdépendant en utilisant des setters. De plus, lorsqu'ilobj.name
n'est pas configurable, la redéfinition de son setter (avec diverses astuces pour ajouter une notification) n'est pas non plus autorisée - les génériques avec Oo () sont donc totalement supprimés dans ce cas spécifique.Je voudrais ajouter à ma pré-publication. Je suggère une approche légèrement différente qui vous permettra d'attribuer simplement une nouvelle valeur à votre objet sans utiliser de méthode. Il convient de noter cependant que cela n'est pas pris en charge par les navigateurs particulièrement anciens et IE9 nécessite toujours l'utilisation d'une interface différente.
Plus particulièrement, mon approche ne fait pas appel aux événements.
Getters et Setters
Ma proposition utilise la caractéristique relativement récente des getters et setters , en particulier des setters uniquement. De manière générale, les mutateurs nous permettent de «personnaliser» le comportement de l'attribution et de la récupération de certaines propriétés.
Une implémentation que j'utiliserai ici est la méthode Object.defineProperty . Cela fonctionne dans FireFox, GoogleChrome et - je pense - IE9. Je n'ai pas testé d'autres navigateurs, mais comme ce n'est qu'une théorie ...
Quoi qu'il en soit, il accepte trois paramètres. Le premier paramètre étant l'objet pour lequel vous souhaitez définir une nouvelle propriété, le second une chaîne ressemblant au nom de la nouvelle propriété et le dernier un "objet descripteur" fournissant des informations sur le comportement de la nouvelle propriété.
Deux descripteurs particulièrement intéressants sont
get
etset
. Un exemple ressemblerait à quelque chose comme ceci. Notez que l'utilisation de ces deux interdit l'utilisation des 4 autres descripteurs.Maintenant, utiliser cela devient légèrement différent:
Je tiens à souligner que cela ne fonctionne que pour les navigateurs modernes.
Violon de travail: http://jsfiddle.net/Derija93/RkTMD/1/
la source
Proxy
objets Harmony :) Les setters semblent être une bonne idée, mais cela ne nous obligerait-il pas à modifier les objets réels? En outre, sur une note secondaire -Object.create
pourrait être utilisé ici (encore une fois, en supposant que le navigateur moderne autorise le deuxième paramètre). En outre, le setter / getter pourrait être utilisé pour «projeter» une valeur différente pour l'objet et l'élément DOM :). Je me demande si vous avez aussi des idées sur les modèles, cela semble être un vrai défi ici, surtout pour bien structurer :)Proxy
, comme vous l'avez dit.;) J'ai compris que le défi était de garder deux propriétés distinctes synchronisées. Ma méthode élimine l'un des deux.Proxy
éliminerait la nécessité d'utiliser des getters / setters, vous pouvez lier des éléments sans savoir quelles propriétés ils ont. Ce que je voulais dire, c'est que les getters peuvent changer plus que bindTo.value, ils peuvent contenir de la logique (et peut-être même un modèle). La question est de savoir comment conserver ce type de liaison bidirectionnelle avec un modèle en tête? Disons que je mappe mon objet à un formulaire, je voudrais maintenir à la fois l'élément et le formulaire synchronisés et je me demande comment je procéderais à ce sujet. Vous pouvez vérifier comment cela fonctionne sur knockout learn.knockoutjs.com/#/?tutorial=intro par exempleJe pense que ma réponse sera plus technique, mais pas différente car les autres présentent la même chose en utilisant des techniques différentes.
Donc, tout d'abord, la solution à ce problème est l'utilisation d'un modèle de conception connu sous le nom d '"observateur", il vous permet de découpler vos données de votre présentation, en faisant en sorte que le changement d'une chose soit diffusé à leurs auditeurs, mais dans ce cas c'est fait dans les deux sens.
Pour la voie DOM vers JS
Pour lier les données du DOM à l'objet js, vous pouvez ajouter du balisage sous forme d'
data
attributs (ou de classes si vous avez besoin de compatibilité), comme ceci:De cette façon, il est accessible via js en utilisant
querySelectorAll
(ou le vieil amigetElementsByClassName
pour la compatibilité).Vous pouvez maintenant lier l'événement en écoutant les modifications de différentes manières: un écouteur par objet ou un gros écouteur au conteneur / document. La liaison au document / conteneur déclenchera l'événement pour chaque modification apportée à celui-ci ou à son enfant, il aura une empreinte mémoire plus petite mais générera des appels d'événement.
Le code ressemblera à ceci:
Pour la voie JS do DOM
Vous aurez besoin de deux choses: un méta-objet qui contiendra les références de l'élément DOM sorcière est lié à chaque objet / attribut js et un moyen d'écouter les changements dans les objets. C'est fondamentalement la même manière: vous devez avoir un moyen d'écouter les changements dans l'objet et ensuite le lier au nœud DOM, car votre objet "ne peut pas avoir" de métadonnées, vous aurez besoin d'un autre objet qui contient les métadonnées d'une manière que le nom de la propriété correspond aux propriétés de l'objet de métadonnées. Le code ressemblera à ceci:
J'espère que j'ai été utile.
la source
Object.observe
car le support n'est présent qu'en chrome pour l'instant. caniuse.com/#feat=object-observeHier, j'ai commencé à écrire ma propre façon de lier des données.
C'est très drôle de jouer avec.
Je pense que c'est beau et très utile. Au moins lors de mes tests avec Firefox et Chrome, Edge doit aussi fonctionner. Je ne suis pas sûr des autres, mais s'ils prennent en charge Proxy, je pense que cela fonctionnera.
https://jsfiddle.net/2ozoovne/1/
Voici le code:
Ensuite, pour régler, juste:
Pour l'instant, je viens d'ajouter la liaison de valeur HTMLInputElement.
Faites-moi savoir si vous savez comment l'améliorer.
la source
Il existe une implémentation barebones très simple de la liaison de données bidirectionnelle dans ce lien "Liaison de données bidirectionnelle facile en JavaScript"
Le lien précédent avec les idées de knockoutjs, backbone.js et agility.js, a conduit à ce framework MVVM léger et rapide, ModelView.js
basé sur jQueryqui joue bien avec jQuery et dont je suis l'auteur humble (ou peut-être pas si humble).Reproduire l'exemple de code ci-dessous (à partir du lien de l' article de blog ):
Exemple de code pour DataBinder
la source
La modification de la valeur d'un élément peut déclencher un événement DOM . Les écouteurs qui répondent aux événements peuvent être utilisés pour implémenter la liaison de données en JavaScript.
Par exemple:
Voici du code et une démo qui montre comment les éléments DOM peuvent être liés entre eux ou avec un objet JavaScript.
la source
Lier n'importe quelle entrée html
définir deux fonctions:
utiliser les fonctions:
la source
Voici une idée
Object.defineProperty
qui modifie directement la façon dont une propriété est accessible.Code:
Usage:
violon: ici
la source
J'ai parcouru un exemple de base de javascript en utilisant onkeypress et des gestionnaires d'événements onchange pour créer une vue de liaison avec nos js et js à afficher
Voici un exemple de plunker http://plnkr.co/edit/7hSOIFRTvqLAvdZT4Bcc?p=preview
la source
la source
Un moyen simple de lier une variable à une entrée (liaison bidirectionnelle) consiste à accéder directement à l'élément d'entrée dans le getter et le setter:
En HTML:
Et pour utiliser:
Une façon plus sophistiquée de faire ce qui précède sans getter / setter:
Utiliser:
la source
Il s'agit d'une liaison de données bidirectionnelle très simple en javascript vanille ....
la source
Tard dans la soirée, surtout depuis que j'ai écrit 2 livres liés il y a des mois / années, je les mentionnerai plus tard, mais ça me semble toujours pertinent. Pour faire un spoiler vraiment court, les technologies de mon choix sont:
Proxy
pour l'observation du modèleMutationObserver
pour le suivi des modifications du DOM (pour des raisons contraignantes, pas des modifications de valeur)addEventListener
gestionnaires réguliersÀ mon humble avis, en plus du PO, il est important que la mise en œuvre de la liaison de données:
user.address.block
shift
,splice
et similaires)Compte tenu de tous ces éléments, à mon avis, il est impossible de lancer simplement quelques dizaines de lignes JS. J'ai essayé de le faire comme modèle plutôt que lib - ça n'a pas marché pour moi.
Ensuite, l'avoir
Object.observe
est supprimé, et pourtant, étant donné que l'observation du modèle est une partie cruciale - cette partie entière DOIT être séparée des préoccupations d'une autre lib. Maintenant, au point de savoir comment j'ai pris ce problème - exactement comme OP l'a demandé:Modèle (partie JS)
Ma prise pour l'observation du modèle est Proxy , c'est la seule façon sensée de le faire fonctionner, à mon humble avis. La fonctionnalité complète
observer
mérite sa propre bibliothèque, j'ai donc développé uneobject-observer
bibliothèque dans ce seul but.Le ou les modèles doivent être enregistrés via une API dédiée, c'est le point où les POJO se transforment en
Observable
s, vous ne pouvez voir aucun raccourci ici. Les éléments DOM qui sont considérés comme des vues liées (voir ci-dessous), sont mis à jour avec les valeurs du / des modèle (s) dans un premier temps puis à chaque changement de données.Vues (partie HTML)
À mon humble avis, la façon la plus propre d'exprimer la liaison est via les attributs. Beaucoup l'ont fait avant et beaucoup le feront après, donc pas de nouvelles ici, c'est juste une bonne façon de le faire. Dans mon cas, je suis allé avec la syntaxe suivante:,
<span data-tie="modelKey:path.to.data => targerProperty"></span>
mais c'est moins important. Ce qui est important pour moi, pas de syntaxe de script complexe dans le HTML - c'est faux, encore une fois, à mon humble avis.Tous les éléments désignés comme des vues liées doivent être collectés dans un premier temps. Du point de vue des performances, il me semble inévitable de gérer un mappage interne entre les modèles et les vues, semble un bon cas où la mémoire + une certaine gestion devraient être sacrifiées pour économiser les recherches et les mises à jour d'exécution.
Les vues sont mises à jour dans un premier temps à partir du modèle, si disponibles et lors de modifications ultérieures du modèle, comme nous l'avons dit. Plus encore, l'ensemble du DOM doit être observé au moyen de
MutationObserver
afin de réagir (lier / délier) sur les éléments ajoutés / supprimés / modifiés dynamiquement. De plus, tout cela doit être répliqué dans le ShadowDOM (un ouvert, bien sûr) afin de ne pas laisser de trous noirs non liés.La liste des détails peut aller plus loin en effet, mais ce sont à mon avis les principaux principes qui rendraient la liaison de données implémentée avec un bon équilibre de l'exhaustivité des fonctionnalités d'une part et d'une simplicité saine de l'autre côté.
Et donc, en plus de ce qui est
object-observer
mentionné ci-dessus, j'ai également écrit unedata-tier
bibliothèque qui implémente la liaison de données le long des concepts mentionnés ci-dessus.la source
Les choses ont beaucoup changé au cours des 7 dernières années, nous avons maintenant des composants Web natifs dans la plupart des navigateurs. OMI, le cœur du problème est le partage d'état entre les éléments, une fois que vous avez que c'est trivial de mettre à jour l'interface utilisateur lorsque l'état change et vice versa.
Pour partager des données entre des éléments, vous pouvez créer une classe StateObserver et étendre vos composants Web à partir de cela. Une implémentation minimale ressemble à ceci:
jouer du violon ici
J'aime cette approche car:
data-
propriétésla source