Zwei-Wege-Datenbindung bezieht sich auf die Fähigkeit, Änderungen in Objekteigenschaften an Änderungen in der Benutzeroberfläche zu binden und umgekehrt. Mit anderen Worten: Wenn wir ein Benutzerobjekt und ein Namensattribut haben, wird der neue Name auf der Benutzeroberfläche angezeigt, sobald wir user.name einen neuen Wert zuweisen. Wenn die Benutzeroberfläche ebenfalls ein Eingabefeld für den Namen des Benutzers enthält, sollte die Eingabe eines neuen Werts dazu führen, dass sich die Namenseigenschaft des Benutzerobjekts entsprechend ändert.
Viele beliebte JS-Framework-Clients wie Ember.js, Angular.js oder KnockoutJS haben in ihren neuesten Funktionen eine bidirektionale Datenbindung implementiert. Dies bedeutet nicht, dass es schwierig ist, es von Grund auf zu implementieren, und auch nicht, dass die Übernahme dieser Frameworks die einzige Option ist, wenn diese Funktionen benötigt werden. Die folgende Idee ist eigentlich sehr einfach und kann als 3-Stufen-Plan betrachtet werden:
Wir brauchen eine Möglichkeit, UI-Elemente und -Attribute miteinander zu verbinden
Wir müssen Änderungen an Eigenschaften und UI-Elementen überwachen
Wir müssen alle gebundenen Objekte und Elemente auf Änderungen aufmerksam machen
Es gibt noch viele Möglichkeiten, die oben genannte Idee umzusetzen. Eine einfache und effektive Möglichkeit ist die Verwendung des PubSub-Modus. Die Idee ist einfach: Wir verwenden Datenattribute, um HTML-Code zu binden, und alle miteinander verbundenen JavaScript-Objekte und DOM-Elemente abonnieren ein PubSub-Objekt. Solange ein JavaScript-Objekt oder ein HTML-Eingabeelement auf Datenänderungen lauscht, wird das an das PubSub-Objekt gebundene Ereignis ausgelöst und andere gebundene Objekte und Elemente nehmen entsprechende Änderungen vor.
Verwenden Sie jQuery, um eine einfache Implementierung durchzuführen
Zum Abonnieren und Veröffentlichen von DOM-Ereignissen ist es sehr einfach, es mit jQuery zu implementieren. Als nächstes verwenden wir Jquery, wie zum Beispiel das Folgende:
function DataBinder( object_id ) { // Use a jQuery object as simple PubSub var pubSub = jQuery({}); // We expect a `data` element specifying the binding // in the form: data-bind-<object_id>="<property_name>" var data_attr = "bind-" + object_id, message = object_id + ":change"; // Listen to change events on elements with the data-binding attribute and proxy // them to the PubSub, so that the change is "broadcasted" to all connected objects jQuery( document ).on( "change", "[data-" + data_attr + "]", function( evt ) { var $input = jQuery( this ); pubSub.trigger( message, [ $input.data( data_attr ), $input.val() ] ); }); // PubSub propagates changes to all bound elements, setting value of // input tags or HTML content of other tags pubSub.on( message, function( evt, prop_name, new_val ) { jQuery( "[data-" + data_attr + "=" + prop_name + "]" ).each( function() { var $bound = jQuery( this ); if ( $bound.is("input, textarea, select") ) { $bound.val( new_val ); } else { $bound.html( new_val ); } }); }); return pubSub; }
Für die obige Implementierung ist das Folgende die einfachste Implementierungsmethode eines Benutzermodells:
function User( uid ) { var binder = new DataBinder( uid ), user = { attributes: {}, // The attribute setter publish changes using the DataBinder PubSub set: function( attr_name, val ) { this.attributes[ attr_name ] = val; binder.trigger( uid + ":change", [ attr_name, val, this ] ); }, get: function( attr_name ) { return this.attributes[ attr_name ]; }, _binder: binder }; // Subscribe to the PubSub binder.on( uid + ":change", function( evt, attr_name, new_val, initiator ) { if ( initiator !== user ) { user.set( attr_name, new_val ); } }); return user; }
Wenn wir nun die Benutzermodellattribute an die Benutzeroberfläche binden möchten, müssen wir nur die entsprechenden Datenattribute an die entsprechenden HTML-Elemente binden.
// javascript var user = new User( 123 ); user.set( "name", "Wolfgang" ); // html <input type="number" data-bind-123="name" />
Auf diese Weise wird der Eingabewert automatisch dem Namensattribut des Benutzerobjekts zugeordnet und umgekehrt
Das Gleiche. Diese einfache Implementierung ist abgeschlossen!
Keine jQuery-Implementierung erforderlich
In den meisten Projekten wird jQuery heute wahrscheinlich bereits verwendet, daher ist das obige Beispiel völlig akzeptabel. Was aber, wenn wir versuchen müssen, ins andere Extrem zu gehen und auch die Abhängigkeit von jQuery zu beseitigen? Nun, es ist nicht so schwer zu beweisen (vor allem, da wir den Support auf IE 8 und höher beschränken). Letztendlich müssen wir ein benutzerdefiniertes PubSub mit normalem Javascript implementieren und DOM-Ereignisse beibehalten:
function DataBinder( object_id ) { // Create a simple PubSub object var pubSub = { callbacks: {}, on: function( msg, callback ) { this.callbacks[ msg ] = this.callbacks[ msg ] || []; this.callbacks[ msg ].push( callback ); }, publish: function( msg ) { this.callbacks[ msg ] = this.callbacks[ msg ] || [] for ( var i = , len = this.callbacks[ msg ].length; i < len; i++ ) { this.callbacks[ msg ][ i ].apply( this, arguments ); } } }, data_attr = "data-bind-" + object_id, message = object_id + ":change", changeHandler = function( evt ) { var target = evt.target || evt.srcElement, // IE compatibility prop_name = target.getAttribute( data_attr ); if ( prop_name && prop_name !== "" ) { pubSub.publish( message, prop_name, target.value ); } }; // Listen to change events and proxy to PubSub if ( document.addEventListener ) { document.addEventListener( "change", changeHandler, false ); } else { // IE uses attachEvent instead of addEventListener document.attachEvent( "onchange", changeHandler ); } // PubSub propagates changes to all bound elements pubSub.on( message, function( evt, prop_name, new_val ) { var elements = document.querySelectorAll("[" + data_attr + "=" + prop_name + "]"), tag_name; for ( var i = , len = elements.length; i < len; i++ ) { tag_name = elements[ i ].tagName.toLowerCase(); if ( tag_name === "input" || tag_name === "textarea" || tag_name === "select" ) { elements[ i ].value = new_val; } else { elements[ i ].innerHTML = new_val; } } }); return pubSub; }
Das Modell kann bis auf den Aufruf der jQuery-Trigger-Methode im Setter gleich bleiben. Der Aufruf der Trigger-Methode wird durch den Aufruf der Publish-Methode unseres angepassten PubSub mit anderen Eigenschaften ersetzt:
// In the model's setter: function User( uid ) { // ... user = { // ... set: function( attr_name, val ) { this.attributes[ attr_name ] = val; // Use the `publish` method binder.publish( uid + ":change", attr_name, val, this ); } } // ... }
Wir haben erneut die gewünschten Ergebnisse mit weniger als hundert Zeilen wartbarem reinem JavaScript erzielt.
Der obige Inhalt ist ein Tutorial zur bidirektionalen Datenbindung von js. Ich hoffe, dass es für alle hilfreich ist.