Bitte behandeln Sie diese Frage als streng lehrreich. Ich bin immer noch daran interessiert, neue Antworten und Ideen zu hören, um dies umzusetzen
tl; dr
Wie würde ich die bidirektionale Datenbindung mit JavaScript implementieren?
Datenbindung an das DOM
Mit Datenbindung an das DOM meine ich beispielsweise ein JavaScript-Objekt a
mit einer Eigenschaft b
. Dann ein <input>
DOM-Element haben (zum Beispiel), wenn sich das DOM-Element ändert, a
ändert und umgekehrt (das heißt, ich meine bidirektionale Datenbindung).
Hier ist ein Diagramm von AngularJS, wie dies aussieht:
Im Grunde habe ich JavaScript ähnlich wie:
var a = {b:3};
Dann ein Eingabeelement (oder ein anderes Formularelement) wie:
<input type='text' value=''>
Ich möchte, dass der Wert der Eingabe a.b
(zum Beispiel) der Wert ist, und wenn sich der Eingabetext ändert, möchte ich auch a.b
ändern. Bei a.b
Änderungen in JavaScript ändert sich die Eingabe.
Die Frage
Was sind einige grundlegende Techniken, um dies in einfachem JavaScript zu erreichen?
Im Einzelnen möchte ich eine gute Antwort auf:
- Wie würde das Binden für Objekte funktionieren?
- Wie könnte es funktionieren, Änderungen in der Form zuzuhören?
- Ist es auf einfache Weise möglich, den HTML-Code nur auf Vorlagenebene ändern zu lassen? Ich möchte die Bindung nicht im HTML-Dokument selbst verfolgen, sondern nur in JavaScript (mit DOM-Ereignissen und JavaScript, das auf die verwendeten DOM-Elemente verweist).
Was habe ich versucht?
Ich bin ein großer Fan von Moustache, also habe ich versucht, es als Vorlage zu verwenden. Beim Versuch, die Datenbindung selbst durchzuführen, sind jedoch Probleme aufgetreten, da Moustache HTML als Zeichenfolge verarbeitet. Nachdem ich das Ergebnis erhalten habe, habe ich keinen Hinweis darauf, wo sich die Objekte in meinem Ansichtsmodell befinden. Die einzige Problemumgehung, die ich mir vorstellen konnte, war das Ändern der HTML-Zeichenfolge (oder des erstellten DOM-Baums) selbst mit Attributen. Es macht mir nichts aus, eine andere Template-Engine zu verwenden.
Grundsätzlich hatte ich das starke Gefühl, das vorliegende Problem zu komplizieren, und es gibt eine einfache Lösung.
Hinweis: Bitte geben Sie keine Antworten an, die externe Bibliotheken verwenden, insbesondere solche mit Tausenden von Codezeilen. Ich habe AngularJS und KnockoutJS verwendet (und mag es!). Ich möchte wirklich keine Antworten in der Form 'benutze Framework x'. Optimalerweise möchte ich einen zukünftigen Leser, der nicht weiß, wie man viele Frameworks verwendet, um zu verstehen, wie man bidirektionale Datenbindung selbst implementiert. Ich erwarte keine vollständige Antwort, sondern eine, die die Idee vermittelt.
quelle
Antworten:
Eine Abstraktion, die beide Objekte aktualisiert
Ich nehme an, es gibt andere Techniken, aber letztendlich hätte ich ein Objekt, das auf ein verwandtes DOM-Element verweist und eine Schnittstelle bereitstellt, die Aktualisierungen seiner eigenen Daten und seines verwandten Elements koordiniert.
Das
.addEventListener()
bietet dafür eine sehr schöne Oberfläche. Sie können ihm ein Objekt geben, das dieeventListener
Schnittstelle implementiert , und es ruft seine Handler mit diesem Objekt alsthis
Wert auf.Auf diese Weise erhalten Sie automatischen Zugriff auf das Element und die zugehörigen Daten.
Definieren Sie Ihr Objekt
Die prototypische Vererbung ist eine gute Möglichkeit, dies zu implementieren, obwohl dies natürlich nicht erforderlich ist. Zuerst würden Sie einen Konstruktor erstellen, der Ihr Element und einige Anfangsdaten empfängt.
Hier speichert der Konstruktor also das Element und die Daten zu den Eigenschaften des neuen Objekts. Es bindet auch ein
change
Ereignis an das Gegebeneelement
. Das Interessante ist, dass es das neue Objekt anstelle einer Funktion als zweites Argument übergibt. Aber das allein wird nicht funktionieren.Implementierung der
eventListener
SchnittstelleDamit dies funktioniert, muss Ihr Objekt die
eventListener
Schnittstelle implementieren . Um dies zu erreichen, muss dem Objekt lediglich einehandleEvent()
Methode zugewiesen werden.Hier kommt die Vererbung ins Spiel.
Es gibt viele verschiedene Möglichkeiten, wie dies strukturiert werden kann. Für Ihr Beispiel für die Koordinierung von Aktualisierungen habe ich mich jedoch dafür entschieden, dass die
change()
Methode nur einen Wert akzeptiert undhandleEvent
diesen Wert anstelle des Ereignisobjekts übergibt. Auf diese Weisechange()
kann das auch ohne Ereignis aufgerufen werden.Wenn das
change
Ereignis eintritt, werden sowohl das Element als auch die.data
Eigenschaft aktualisiert . Das gleiche passiert, wenn Sie.change()
Ihr JavaScript-Programm aufrufen .Verwenden des Codes
Jetzt erstellen Sie einfach das neue Objekt und lassen es Aktualisierungen durchführen. Aktualisierungen im JS-Code werden in der Eingabe angezeigt, und Änderungsereignisse in der Eingabe sind für den JS-Code sichtbar.
DEMO: http://jsfiddle.net/RkTMD/
quelle
Mustache.render(template,object)
, vorausgesetzt, ich möchte ein Objekt mit der Vorlage synchronisieren (nicht spezifisch für Moustache). Wie würde ich damit umgehen?input
einer dieser Punkte aktualisiert werden soll, gibt es eine Zuordnung von der Eingabe zu diesem Punkt. Ich werde sehen, ob ich ein kurzes Beispiel finden kann.Template
Konstruktor gibt, der die Analyse durchführt, die verschiedenenMyCtor
Objekte enthält und eine Schnittstelle bereitstellt , über die die einzelnen Objekte anhand ihrer Kennung aktualisiert werden können. Lass es mich wissen, falls du fragen hast. :) BEARBEITEN: ... benutze stattdessen diesen Link ... Ich hatte vergessen, dass der Eingabewert alle 10 Sekunden exponentiell anstieg, um JS-Updates zu demonstrieren. Dies begrenzt es.Also beschloss ich, meine eigene Lösung in den Topf zu werfen. Hier ist eine funktionierende Geige . Beachten Sie, dass dies nur in sehr modernen Browsern ausgeführt wird.
Was es benutzt
Diese Implementierung ist sehr modern - sie erfordert einen (sehr) modernen Browser und Benutzer zwei neue Technologien:
MutationObserver
s , um Änderungen im Dom zu erkennen (Ereignis-Listener werden ebenfalls verwendet)Object.observe
um Änderungen im Objekt zu erkennen und den Dom zu benachrichtigen. Gefahr, da diese Antwort geschrieben wurde Oo wurde vom ECMAScript TC diskutiert und dagegen entschieden, erwägen Sie eine Polyfüllung .Wie es funktioniert
domAttribute:objAttribute
beispielsweise eine Zuordnung hinzubind='textContent:name'
Die Lösung
Hier ist die
dataBind
Funktion, beachten Sie, dass es nur 20 Codezeilen sind und kürzer sein könnten:Hier ist eine Verwendung:
HTML:
JavaScript:
Hier ist eine funktionierende Geige . Beachten Sie, dass diese Lösung ziemlich allgemein ist. Das Shimmen von Object.observe und Mutation Observer ist verfügbar.
quelle
obj.name
ein Setter , wenn er vorhanden ist, nicht extern beobachtet werden kann, sondern senden muss, dass er sich innerhalb des Setters geändert hat - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - wirft einen Schraubenschlüssel in die Arbeit für Oo (), wenn Sie ein komplexeres, voneinander abhängiges Verhalten mit Setzern wünschen. Wenn diesobj.name
nicht konfigurierbar ist, ist eine Neudefinition des Setters (mit verschiedenen Tricks zum Hinzufügen von Benachrichtigungen) ebenfalls nicht zulässig. Daher werden Generika mit Oo () in diesem speziellen Fall vollständig verschrottet.Ich möchte meinen Preposter ergänzen. Ich schlage einen etwas anderen Ansatz vor, mit dem Sie Ihrem Objekt einfach einen neuen Wert zuweisen können, ohne eine Methode zu verwenden. Es muss jedoch beachtet werden, dass dies von besonders älteren Browsern nicht unterstützt wird und IE9 weiterhin die Verwendung einer anderen Schnittstelle erfordert.
Am bemerkenswertesten ist, dass mein Ansatz keine Ereignisse nutzt.
Getter und Setter
Mein Vorschlag nutzt das relativ junge Merkmal von Gettern und Setzern , insbesondere nur Setzern. Im Allgemeinen können wir mit Mutatoren das Verhalten anpassen, mit dem bestimmten Eigenschaften ein Wert zugewiesen und abgerufen wird.
Eine Implementierung, die ich hier verwenden werde, ist die Object.defineProperty- Methode. Es funktioniert in FireFox, GoogleChrome und - glaube ich - IE9. Ich habe andere Browser nicht getestet, aber da dies nur Theorie ist ...
Auf jeden Fall werden drei Parameter akzeptiert. Der erste Parameter ist das Objekt, für das Sie eine neue Eigenschaft definieren möchten, der zweite eine Zeichenfolge, die dem Namen der neuen Eigenschaft ähnelt, und der letzte ein "Deskriptorobjekt", das Informationen zum Verhalten der neuen Eigenschaft bereitstellt.
Zwei besonders interessante Deskriptoren sind
get
undset
. Ein Beispiel würde ungefähr so aussehen. Beachten Sie, dass die Verwendung dieser beiden die Verwendung der anderen 4 Deskriptoren verbietet.Jetzt wird es etwas anders, dies zu nutzen:
Ich möchte betonen, dass dies nur für moderne Browser funktioniert.
Arbeitsgeige: http://jsfiddle.net/Derija93/RkTMD/1/
quelle
Proxy
Objekte hätten :) Setter scheinen eine gute Idee zu sein, aber müssten wir dann nicht die tatsächlichen Objekte modifizieren? Nebenbei bemerkt -Object.create
könnte hier verwendet werden (wiederum unter der Annahme eines modernen Browsers, der den zweiten Parameter zulässt). Der Setter / Getter kann auch verwendet werden, um einen anderen Wert als das Objekt und das DOM-Element zu "projizieren" :). Ich frage mich, ob Sie auch Einblicke in das Templating haben, das scheint hier eine echte Herausforderung zu sein, insbesondere um gut zu strukturieren :)Proxy
, wie Sie sagten .;) Ich verstand die Herausforderung darin, zwei unterschiedliche Eigenschaften synchron zu halten. Meine Methode eliminiert eines von beiden.Proxy
würde die Notwendigkeit der Verwendung von Gettern / Setzern beseitigen, Sie könnten Elemente binden, ohne zu wissen, welche Eigenschaften sie haben. Was ich damit gemeint habe, ist, dass die Getter mehr ändern können als bindTo.value, sie können Logik (und vielleicht sogar eine Vorlage) enthalten. Die Frage ist, wie diese Art der bidirektionalen Bindung unter Berücksichtigung einer Vorlage aufrechterhalten werden kann. Nehmen wir an, ich ordne mein Objekt einem Formular zu. Ich möchte sowohl das Element als auch das Formular synchron halten und frage mich, wie ich mit so etwas umgehen soll. Sie können zum Beispiel überprüfen, wie das auf Knockout funktioniert. Learn.knockoutjs.com/#/?tutorial=introIch denke, meine Antwort wird technischer sein, aber nicht anders, da die anderen dasselbe mit unterschiedlichen Techniken präsentieren.
Als Erstes besteht die Lösung für dieses Problem in der Verwendung eines als "Beobachter" bezeichneten Entwurfsmusters. Sie können Ihre Daten von Ihrer Präsentation entkoppeln und die Änderung in einer Sache an ihre Zuhörer senden, aber in diesem Fall Es ist in beide Richtungen gemacht.
Für den DOM zu JS Weg
Um die Daten aus dem DOM an das js-Objekt zu binden, können Sie Markups in Form von
data
Attributen (oder Klassen, wenn Sie Kompatibilität benötigen) wie folgt hinzufügen :Auf diese Weise kann über js mit
querySelectorAll
(oder dem alten FreundgetElementsByClassName
aus Kompatibilitätsgründen) darauf zugegriffen werden .Jetzt können Sie das Ereignis, das die Änderungen abhört, auf folgende Weise binden: einen Listener pro Objekt oder einen großen Listener an den Container / das Dokument. Durch die Bindung an das Dokument / den Container wird das Ereignis für jede Änderung ausgelöst, die an dem Dokument oder dem untergeordneten Dokument vorgenommen wurde. Es hat einen geringeren Speicherbedarf, führt jedoch zu Ereignisaufrufen.
Der Code sieht ungefähr so aus:
Für den JS do DOM Weg
Sie benötigen zwei Dinge: Ein Metaobjekt, das die Referenzen des Hex-DOM-Elements enthält, ist an jedes js-Objekt / Attribut gebunden und bietet eine Möglichkeit, Änderungen in Objekten abzuhören. Grundsätzlich ist es genauso: Sie müssen eine Möglichkeit haben, Änderungen im Objekt abzuhören und es dann an den DOM-Knoten zu binden, da Ihr Objekt keine Metadaten haben kann. Sie benötigen ein anderes Objekt, das Metadaten in gewisser Weise enthält dass der Eigenschaftsname den Eigenschaften des Metadatenobjekts zugeordnet ist. Der Code wird ungefähr so aussehen:
Ich hoffe, dass ich geholfen habe.
quelle
Object.observe
da die Unterstützung derzeit nur in Chrom vorhanden ist. caniuse.com/#feat=object-observeGestern habe ich angefangen, meinen eigenen Weg zu schreiben, um Daten zu binden.
Es ist sehr lustig damit zu spielen.
Ich finde es schön und sehr nützlich. Zumindest bei meinen Tests mit Firefox und Chrome muss Edge auch funktionieren. Ich bin mir bei anderen nicht sicher, aber wenn sie Proxy unterstützen, wird es funktionieren.
https://jsfiddle.net/2ozoovne/1/
Hier ist der Code:
Dann, um zu setzen, einfach:
Im Moment habe ich gerade die HTMLInputElement-Wertebindung hinzugefügt.
Lassen Sie mich wissen, wenn Sie wissen, wie Sie es verbessern können.
quelle
Es gibt eine sehr einfache Barebone-Implementierung der 2-Wege-Datenbindung in diesem Link "Einfache Zwei- Wege-Datenbindung in JavaScript".
Der vorherige Link führte zusammen mit Ideen von knockoutjs, backbone.js und agility.js zu diesem leichten und schnellen MVVM-Framework, ModelView.js
basierend auf jQuerydas spielt gut mit jQuery und von dem ich der bescheidene (oder vielleicht nicht so bescheidene) Autor bin.Beispielcode unten reproduzieren (vom Blog-Post-Link ):
Beispielcode für DataBinder
quelle
Das Ändern des Werts eines Elements kann ein DOM-Ereignis auslösen . Listener, die auf Ereignisse reagieren, können zum Implementieren der Datenbindung in JavaScript verwendet werden.
Beispielsweise:
Hier finden Sie Code und eine Demo, die zeigt, wie DOM-Elemente miteinander oder mit einem JavaScript-Objekt verknüpft werden können.
quelle
Binden Sie alle HTML-Eingaben
Definieren Sie zwei Funktionen:
Verwenden Sie die Funktionen:
quelle
Hier ist eine Idee, mit
Object.defineProperty
der der Zugriff auf eine Eigenschaft direkt geändert wird.Code:
Verwendung:
Geige: Hier
quelle
Ich habe einige grundlegende Javascript-Beispiele mit onkeypress- und onchange-Ereignishandlern durchgearbeitet, um eine verbindliche Ansicht für unsere js und js zu erstellen
Hier Beispiel Plunker http://plnkr.co/edit/7hSOIFRTvqLAvdZT4Bcc?p=preview
quelle
quelle
Eine einfache Möglichkeit, eine Variable an eine Eingabe zu binden (bidirektionale Bindung), besteht darin, direkt auf das Eingabeelement im Getter und Setter zuzugreifen:
In HTML:
Und zu verwenden:
Eine schickere Art, das oben Genannte ohne Getter / Setter zu tun:
Benutzen:
quelle
Es ist eine sehr einfache bidirektionale Datenbindung in Vanille-Javascript ....
quelle
Spät zur Party, besonders da ich vor Monaten / Jahren 2 Bibliotheken geschrieben habe, werde ich sie später erwähnen, aber sie sehen für mich immer noch relevant aus. Um es wirklich kurz zu machen, sind die Technologien meiner Wahl:
Proxy
zur Beobachtung des ModellsMutationObserver
für die Verfolgung von Änderungen von DOM (aus verbindlichen Gründen, keine Wertänderungen)addEventListener
Handler behandeltIMHO ist es zusätzlich zum OP wichtig, dass die Implementierung der Datenbindung:
user.address.block
shift
,splice
und gleichermaßen)Unter Berücksichtigung all dieser Faktoren ist es meiner Meinung nach unmöglich, nur ein paar Dutzend JS-Zeilen zu werfen. Ich habe versucht, es eher als Muster als als lib zu machen - hat bei mir nicht funktioniert.
Als nächstes
Object.observe
wird das Entfernen entfernt, und dennoch ist die Beobachtung des Modells ein entscheidender Teil - dieser ganze Teil MUSS von einer anderen Bibliothek getrennt sein. Nun zu den Prinzipien, wie ich dieses Problem aufgenommen habe - genau so, wie OP gefragt hat:Modell (JS-Teil)
Meine Einstellung zur Modellbeobachtung ist Proxy , es ist der einzig vernünftige Weg, es zum Laufen zu bringen, IMHO. Voll ausgestattet
observer
verdient eine eigene Bibliothek, daher habe ich eineobject-observer
Bibliothek nur für diesen Zweck entwickelt.Die Modelle sollten über eine dedizierte API registriert werden. Dies ist der Punkt, an dem POJOs zu
Observable
s werden. Hier kann keine Verknüpfung angezeigt werden. Die DOM-Elemente, die als gebundene Ansichten betrachtet werden (siehe unten), werden zuerst und dann bei jeder Datenänderung mit den Werten des Modells / der Modelle aktualisiert.Ansichten (HTML-Teil)
IMHO, der sauberste Weg, um die Bindung auszudrücken, ist über Attribute. Viele haben dies vorher getan und viele werden es danach tun, also keine Neuigkeiten hier, dies ist nur ein richtiger Weg, dies zu tun. In meinem Fall habe ich die folgende Syntax gewählt :
<span data-tie="modelKey:path.to.data => targerProperty"></span>
, aber das ist weniger wichtig. Was ist wichtig für mich, keine komplexe Skriptsyntax im HTML - das ist falsch, wieder, IMHO.Alle Elemente, die als gebundene Ansichten bezeichnet werden, werden zunächst gesammelt. Von der Leistungsseite aus scheint es mir unvermeidlich, eine interne Zuordnung zwischen den Modellen und den Ansichten zu verwalten. Dies scheint ein richtiger Fall zu sein, in dem Speicher + eine gewisse Verwaltung geopfert werden sollte, um Laufzeitsuchen und -aktualisierungen zu speichern.
Die Ansichten werden zunächst vom Modell aus aktualisiert, sofern verfügbar, und bei späteren Modelländerungen, wie bereits erwähnt. Noch mehr sollte das gesamte DOM mittels beobachtet werden,
MutationObserver
um auf die dynamisch hinzugefügten / entfernten / geänderten Elemente zu reagieren (binden / entbinden). Darüber hinaus sollte all dies in das ShadowDOM repliziert werden (offenes natürlich), um keine ungebundenen schwarzen Löcher zu hinterlassen.Die Liste der Einzelheiten mag zwar noch weiter gehen, aber dies sind meiner Meinung nach die Hauptprinzipien, die dazu geführt haben, dass die Datenbindung mit einem ausgewogenen Verhältnis zwischen Vollständigkeit der Funktionen von der einen und vernünftiger Einfachheit von der anderen Seite implementiert wurde.
Und so
object-observer
habe ich zusätzlich zu den oben genannten auch tatsächlich einedata-tier
Bibliothek geschrieben, die die Datenbindung entlang der oben genannten Konzepte implementiert.quelle
Die Dinge haben sich in den letzten 7 Jahren sehr verändert. Wir haben jetzt native Webkomponenten in den meisten Browsern. IMO ist der Kern des Problems das Teilen des Status zwischen Elementen, sobald Sie das haben, ist es trivial, die Benutzeroberfläche zu aktualisieren, wenn sich der Status ändert und umgekehrt.
Um Daten zwischen Elementen auszutauschen, können Sie eine StateObserver-Klasse erstellen und Ihre Webkomponenten daraus erweitern. Eine minimale Implementierung sieht ungefähr so aus:
hier fummeln
Ich mag diesen Ansatz, weil:
data-
Eigenschaften zu findenquelle