web-dev-qa-db-de.com

Implementieren der DOM-Datenbindung in JavaScript

Bitte behandeln Sie diese Frage als rein pädagogisch. 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 zum Beispiel ein JavaScript-Objekt a mit einer Eigenschaft b. Wenn Sie dann ein <input>-DOM-Element (zum Beispiel) haben, ändert sich a, wenn sich das DOM-Element ändert, und umgekehrt (d. H. Ich meine bidirektionale Datenbindung). 

Hier ist ein Diagramm von AngularJS, wie das aussieht:

two way data binding

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 der Wert von a.b ist (zum Beispiel). Wenn sich der Eingabetext ändert, möchte ich auch, dass a.b geändert wird. Wenn a.b in JavaScript geändert wird, ändert sich die Eingabe.

Die Frage

Was sind einige grundlegende Techniken, um dies in reinem JavaScript zu erreichen?

Im Besonderen möchte ich auf eine gute Antwort verweisen:

  • Wie würde die Bindung von Objekten funktionieren?
  • Wie kann das Zuhören im Formular funktionieren?
  • Ist es auf einfache Weise möglich, den HTML-Code nur auf Vorlagenebene zu ändern? Ich möchte die Bindung nicht im HTML-Dokument selbst nachverfolgen, sondern nur in JavaScript (mit DOM-Ereignissen und JavaScript-Verweisen auf die verwendeten DOM-Elemente).

Was habe ich versucht?

Ich bin ein großer Fan von Moustache, also habe ich es für das Templating verwendet. Beim Versuch, die Datenbindung selbst durchzuführen, bin ich jedoch auf Probleme gestoßen, da Mustache HTML als Zeichenfolge verarbeitet. Nachdem ich das Ergebnis erhalten habe, habe ich keinen Hinweis darauf, wo sich die Objekte in meinem Viewmodel befinden. Die einzige Problemlösung, die ich mir vorstellen konnte, war das Ändern der HTML-Zeichenfolge (oder der erstellten DOM-Struktur) selbst mit Attributen. Ich habe nichts dagegen, eine andere Template-Engine zu verwenden.

Im Grunde hatte ich das starke Gefühl, dass ich das Thema komplizierter machte und es eine einfache Lösung gibt.

Hinweis: Bitte geben Sie keine Antworten an, die externe Bibliotheken verwenden, insbesondere solche, die aus Tausenden von Codezeilen bestehen. Ich habe (und wie!) AngularJS und KnockoutJS verwendet. Ich möchte wirklich keine Antworten in der Form 'use framework x'. Optimalerweise möchte ich einen zukünftigen Leser, der nicht weiß, wie er viele Frameworks verwendet, um die bidirektionale Datenbindung selbst zu implementieren. Ich erwarte keine complete - Antwort, sondern eine, die die Idee vermittelt.

225
  • Wie würde die Bindung von Objekten funktionieren?
  • Wie kann das Zuhören im Formular funktionieren?

Eine Abstraktion, die beide Objekte aktualisiert

Ich nehme an, es gibt andere Techniken, aber letztendlich würde ich ein Objekt haben, das auf ein verwandtes DOM-Element verweist und eine Schnittstelle bietet, die Aktualisierungen an seinen eigenen Daten und dem zugehörigen Element koordiniert.

Die .addEventListener() bietet dafür eine sehr schöne Schnittstelle. Sie können ihm ein Objekt zuweisen, das die eventListener-Schnittstelle implementiert, und es wird seine Handler mit diesem Objekt als this-Wert aufrufen.

Dadurch haben Sie automatischen Zugriff auf das Element und die zugehörigen Daten.

Objekt definieren

Die prototypische Vererbung ist eine schöne Möglichkeit, dies umzusetzen, obwohl dies natürlich nicht erforderlich ist. Zuerst erstellen Sie einen Konstruktor, der Ihr Element und einige Anfangsdaten empfängt.

function MyCtor(element, data) {
    this.data = data;
    this.element = element;
    element.value = data;
    element.addEventListener("change", this, false);
}

Hier speichert der Konstruktor das Element und die Daten zu den Eigenschaften des neuen Objekts. Es bindet außerdem ein change -Ereignis an die angegebene element. Das Interessante ist, dass das neue Objekt anstelle einer Funktion als zweites Argument übergeben wird. Aber das alleine wird nicht funktionieren. 

Implementierung der eventListener-Schnittstelle

Damit dies funktioniert, muss Ihr Objekt die eventListener-Schnittstelle implementieren. Dazu müssen Sie dem Objekt lediglich eine handleEvent()-Methode geben. 

Hier kommt die Erbschaft ins Spiel.

MyCtor.prototype.handleEvent = function(event) {
    switch (event.type) {
        case "change": this.change(this.element.value);
    }
};

MyCtor.prototype.change = function(value) {
    this.data = value;
    this.element.value = value;
};

Es gibt viele verschiedene Möglichkeiten, um dies zu strukturieren, aber für Ihr Beispiel der Koordinierung von Aktualisierungen habe ich mich entschieden, die change()-Methode nur dazu zu verwenden, einen Wert zu akzeptieren, und die handleEvent diesen Wert anstelle des Ereignisobjekts übergeben. Auf diese Weise kann change() auch ohne Ereignis aufgerufen werden.

Wenn nun das change-Ereignis auftritt, werden sowohl das Element als auch die .data-Eigenschaft aktualisiert. Dasselbe passiert, wenn Sie in Ihrem JavaScript-Programm .change() aufrufen.

Verwendung des Codes

Jetzt erstellen Sie einfach das neue Objekt und lassen es Aktualisierungen durchführen. Aktualisierungen im JS-Code werden an der Eingabe angezeigt, und Änderungsereignisse an der Eingabe sind für den JS-Code sichtbar.

var obj = new MyCtor(document.getElementById("foo"), "20");

// simulate some JS based changes.
var i = 0;
setInterval(function() {
    obj.change(parseInt(obj.element.value) + ++i);
}, 3000);

DEMO:http://jsfiddle.net/RkTMD/

100
user1106925

Also entschied ich mich, meine eigene Lösung in den Topf zu werfen. Hier ist eine Arbeitsfiedel . Beachten Sie, dass dies nur auf sehr modernen Browsern möglich ist.

Was benutzt es?

Diese Implementierung ist sehr modern - es erfordert einen (sehr) modernen Browser und zwei neue Technologien:

  • MutationObservers um Änderungen im Dom zu erkennen (Ereignis-Listener werden ebenfalls verwendet)
  • Object.observe um Änderungen am Objekt zu erkennen und den dom zu benachrichtigen. Gefahr, da diese Antwort geschrieben wurde. O.o wurde vom ECMAScript-TC diskutiert und entschieden, entscheiden Sie sich für eine Polyfill.

Wie es funktioniert

  • Fügen Sie auf dem Element eine domAttribute:objAttribute-Zuordnung ein - zum Beispiel bind='textContent:name'
  • Lesen Sie das in der dataBind-Funktion. Beobachten Sie Änderungen am Element und am Objekt. 
  • Wenn eine Änderung auftritt, aktualisieren Sie das relevante Element.

Die Lösung

Hier ist die Funktion dataBind. Beachten Sie, dass dies nur 20 Zeilen Code ist und kürzer sein könnte:

function dataBind(domElement, obj) {    
    var bind = domElement.getAttribute("bind").split(":");
    var domAttr = bind[0].trim(); // the attribute on the DOM element
    var itemAttr = bind[1].trim(); // the attribute the object

    // when the object changes - update the DOM
    Object.observe(obj, function (change) {
        domElement[domAttr] = obj[itemAttr]; 
    });
    // when the dom changes - update the object
    new MutationObserver(updateObj).observe(domElement, { 
        attributes: true,
        childList: true,
        characterData: true
    });
    domElement.addEventListener("keyup", updateObj);
    domElement.addEventListener("click",updateObj);
    function updateObj(){
        obj[itemAttr] = domElement[domAttr];   
    }
    // start the cycle by taking the attribute from the object and updating it.
    domElement[domAttr] = obj[itemAttr]; 
}

Hier ist etwas Verwendung:

HTML: 

<div id='projection' bind='textContent:name'></div>
<input type='text' id='textView' bind='value:name' />

JavaScript: 

var obj = {
    name: "Benjamin"
};
var el = document.getElementById("textView");
dataBind(el, obj);
var field = document.getElementById("projection");
dataBind(field,obj);

Hier ist eine Arbeitsfiedel . Beachten Sie, dass diese Lösung ziemlich allgemein ist. Das Beobachten von Objekten, Beobachtungen und Mutationen ist verfügbar.

34

Ich möchte zu meinem Preposter hinzufügen. 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 insbesondere von älteren Browsern nicht unterstützt wird und IE9 die Verwendung einer anderen Schnittstelle erfordert.

Am bemerkenswertesten ist, dass mein Ansatz keine Ereignisse nutzt.

Getter und Setter

Mein Vorschlag macht Gebrauch von der relativ jungen Eigenschaft von Getter und Setzer , insbesondere nur von Setzern. Im Allgemeinen ermöglichen Mutatoren das Verhalten, wie bestimmten Eigenschaften ein Wert zugewiesen und abgerufen wird.

Eine Implementierung, die ich hier verwenden werde, ist die Methode Object.defineProperty . Es funktioniert in Firefox, GoogleChrome und - glaube ich - IE9. Habe keine anderen Browser getestet, aber da dies nur Theorie ist ...

Auf jeden Fall akzeptiert es drei Parameter. 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 enthält.

Zwei besonders interessante Deskriptoren sind get und set. Ein Beispiel würde ungefähr wie folgt aussehen. Beachten Sie, dass die Verwendung dieser beiden die Verwendung der anderen vier Deskriptoren verbietet.

function MyCtor( bindTo ) {
    // I'll omit parameter validation here.

    Object.defineProperty(this, 'value', {
        enumerable: true,
        get : function ( ) {
            return bindTo.value;
        },
        set : function ( val ) {
            bindTo.value = val;
        }
    });
}

Jetzt wird es etwas anders:

var obj = new MyCtor(document.getElementById('foo')),
    i = 0;
setInterval(function() {
    obj.value += ++i;
}, 3000);

Ich möchte betonen, dass dies nur für moderne Browser funktioniert.

Arbeitsgeige: http://jsfiddle.net/Derija93/RkTMD/1/

24
Kiruse

Ich denke, dass meine Antwort eher technischer sein wird, aber nicht anders, da die anderen mit unterschiedlichen Techniken dasselbe präsentieren.
Die Lösung für dieses Problem ist zunächst die Verwendung eines als "Beobachter" bezeichneten Entwurfsmusters, mit dem Sie Ihre Daten von Ihrer Präsentation abkoppeln können, wodurch die Änderung in einer Sache an die Zuhörer übertragen wird, aber In diesem Fall wird es in beide Richtungen gemacht.

Für den DOM nach JS-Weg

Um die Daten aus dem DOM an das js-Objekt zu binden, können Sie Markierungen in Form von data-Attributen (oder Klassen, falls Sie Kompatibilität benötigen) hinzufügen, wie folgt:

<input type="text" data-object="a" data-property="b" id="b" class="bind" value=""/>
<input type="text" data-object="a" data-property="c" id="c" class="bind" value=""/>
<input type="text" data-object="d" data-property="e" id="e" class="bind" value=""/>

Auf diese Weise kann über js mit querySelectorAll (oder dem alten Freund getElementsByClassName aus Kompatibilitätsgründen) darauf zugegriffen werden.

Jetzt können Sie das Ereignis so binden, dass es die Änderungen überwacht, und zwar auf folgende Weise: einen Listener pro Objekt oder einen großen Listener mit dem Container/Dokument. Durch das Binden an das Dokument/den Container wird das Ereignis bei jeder Änderung oder dem untergeordneten Element ausgelöst. Es hat einen geringeren Speicherbedarf, aber es werden Ereignisaufrufe ausgelöst.
Der Code sieht ungefähr so ​​aus:

//Bind to each element
var elements = document.querySelectorAll('input[data-property]');

function toJS(){
    //Assuming `a` is in scope of the document
    var obj = document[this.data.object];
    obj[this.data.property] = this.value;
}

elements.forEach(function(el){
    el.addEventListener('change', toJS, false);
}

//Bind to document
function toJS2(){
    if (this.data && this.data.object) {
        //Again, assuming `a` is in document's scope
        var obj = document[this.data.object];
        obj[this.data.property] = this.value;
    }
}

document.addEventListener('change', toJS2, false);

Für den JS tun Sie DOM-Weg

Sie benötigen zwei Dinge: Ein Metaobjekt, das die Referenzen von DOM-Elementen enthält, die an jedes js-Objekt/-Attribut gebunden sind, und eine Möglichkeit, Änderungen an Objekten zu überwachen. Es ist im Grunde der gleiche Weg: Sie müssen die Möglichkeit haben, Änderungen am Objekt zu überwachen und es an den DOM-Knoten zu binden. Da Ihr Objekt Metadaten "nicht haben kann", benötigen Sie ein anderes Objekt, das Metadaten enthält dass der Name der Eigenschaft den Eigenschaften des Metadatenobjekts zugeordnet ist ..__ Der Code sieht etwa wie folgt aus:

var a = {
        b: 'foo',
        c: 'bar'
    },
    d = {
        e: 'baz'
    },
    metadata = {
        b: 'b',
        c: 'c',
        e: 'e'
    };
function toDOM(changes){
    //changes is an array of objects changed and what happened
    //for now i'd recommend a polyfill as this syntax is still a proposal
    changes.forEach(function(change){
        var element = document.getElementById(metadata[change.name]);
        element.value = change.object[change.name];
    });
}
//Side note: you can also use currying to fix the second argument of the function (the toDOM method)
Object.observe(a, toDOM);
Object.observe(d, toDOM);

Ich hoffe, dass mir geholfen wurde.

7
madcampos

Gestern habe ich angefangen, meine eigene Methode zum Binden von Daten zu schreiben.

Es ist sehr lustig damit zu spielen.

Ich finde es schön und sehr nützlich. Zumindest bei meinen Tests mit Firefox und Chrom muss Edge auch funktionieren. Nicht sicher über andere, aber wenn sie Proxy unterstützen, denke ich, dass es funktionieren wird.

https://jsfiddle.net/2ozoovne/1/

<H1>Bind Context 1</H1>
<input id='a' data-bind='data.test' placeholder='Button Text' />
<input id='b' data-bind='data.test' placeholder='Button Text' />
<input type=button id='c' data-bind='data.test' />
<H1>Bind Context 2</H1>
<input id='d' data-bind='data.otherTest' placeholder='input bind' />
<input id='e' data-bind='data.otherTest' placeholder='input bind' />
<input id='f' data-bind='data.test' placeholder='button 2 text - same var name, other context' />
<input type=button id='g' data-bind='data.test' value='click here!' />
<H1>No bind data</H1>
<input id='h' placeholder='not bound' />
<input id='i' placeholder='not bound'/>
<input type=button id='j' />

Hier ist der Code:

(function(){
    if ( ! ( 'SmartBind' in window ) ) { // never run more than once
        // This hack sets a "proxy" property for HTMLInputElement.value set property
        var nativeHTMLInputElementValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
        var newDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
        newDescriptor.set=function( value ){
            if ( 'settingDomBind' in this )
                return;
            var hasDataBind=this.hasAttribute('data-bind');
            if ( hasDataBind ) {
                this.settingDomBind=true;
                var dataBind=this.getAttribute('data-bind');
                if ( ! this.hasAttribute('data-bind-context-id') ) {
                    console.error("Impossible to recover data-bind-context-id attribute", this, dataBind );
                } else {
                    var bindContextId=this.getAttribute('data-bind-context-id');
                    if ( bindContextId in SmartBind.contexts ) {
                        var bindContext=SmartBind.contexts[bindContextId];
                        var dataTarget=SmartBind.getDataTarget(bindContext, dataBind);
                        SmartBind.setDataValue( dataTarget, value);
                    } else {
                        console.error( "Invalid data-bind-context-id attribute", this, dataBind, bindContextId );
                    }
                }
                delete this.settingDomBind;
            }
            nativeHTMLInputElementValue.set.bind(this)( value );
        }
        Object.defineProperty(HTMLInputElement.prototype, 'value', newDescriptor);

    var uid= function(){
           return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
               var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
               return v.toString(16);
          });
   }

        // SmartBind Functions
        window.SmartBind={};
        SmartBind.BindContext=function(){
            var _data={};
            var ctx = {
                "id" : uid()    /* Data Bind Context Id */
                , "_data": _data        /* Real data object */
                , "mapDom": {}          /* DOM Mapped objects */
                , "mapDataTarget": {}       /* Data Mapped objects */
            }
            SmartBind.contexts[ctx.id]=ctx;
            ctx.data=new Proxy( _data, SmartBind.getProxyHandler(ctx, "data"))  /* Proxy object to _data */
            return ctx;
        }

        SmartBind.getDataTarget=function(bindContext, bindPath){
            var bindedObject=
                { bindContext: bindContext
                , bindPath: bindPath 
                };
            var dataObj=bindContext;
            var dataObjLevels=bindPath.split('.');
            for( var i=0; i<dataObjLevels.length; i++ ) {
                if ( i == dataObjLevels.length-1 ) { // last level, set value
                    bindedObject={ target: dataObj
                    , item: dataObjLevels[i]
                    }
                } else {    // digg in
                    if ( ! ( dataObjLevels[i] in dataObj ) ) {
                        console.warn("Impossible to get data target object to map bind.", bindPath, bindContext);
                        break;
                    }
                    dataObj=dataObj[dataObjLevels[i]];
                }
            }
            return bindedObject ;
        }

        SmartBind.contexts={};
        SmartBind.add=function(bindContext, domObj){
            if ( typeof domObj == "undefined" ){
                console.error("No DOM Object argument given ", bindContext);
                return;
            }
            if ( ! domObj.hasAttribute('data-bind') ) {
                console.warn("Object has no data-bind attribute", domObj);
                return;
            }
            domObj.setAttribute("data-bind-context-id", bindContext.id);
            var bindPath=domObj.getAttribute('data-bind');
            if ( bindPath in bindContext.mapDom ) {
                bindContext.mapDom[bindPath][bindContext.mapDom[bindPath].length]=domObj;
            } else {
                bindContext.mapDom[bindPath]=[domObj];
            }
            var bindTarget=SmartBind.getDataTarget(bindContext, bindPath);
            bindContext.mapDataTarget[bindPath]=bindTarget;
            domObj.addEventListener('input', function(){ SmartBind.setDataValue(bindTarget,this.value); } );
            domObj.addEventListener('change', function(){ SmartBind.setDataValue(bindTarget, this.value); } );
        }

        SmartBind.setDataValue=function(bindTarget,value){
            if ( ! ( 'target' in bindTarget ) ) {
                var lBindTarget=SmartBind.getDataTarget(bindTarget.bindContext, bindTarget.bindPath);
                if ( 'target' in lBindTarget ) {
                    bindTarget.target=lBindTarget.target;
                    bindTarget.item=lBindTarget.item;
                } else {
                    console.warn("Still can't recover the object to bind", bindTarget.bindPath );
                }
            }
            if ( ( 'target' in bindTarget ) ) {
                bindTarget.target[bindTarget.item]=value;
            }
        }
        SmartBind.getDataValue=function(bindTarget){
            if ( ! ( 'target' in bindTarget ) ) {
                var lBindTarget=SmartBind.getDataTarget(bindTarget.bindContext, bindTarget.bindPath);
                if ( 'target' in lBindTarget ) {
                    bindTarget.target=lBindTarget.target;
                    bindTarget.item=lBindTarget.item;
                } else {
                    console.warn("Still can't recover the object to bind", bindTarget.bindPath );
                }
            }
            if ( ( 'target' in bindTarget ) ) {
                return bindTarget.target[bindTarget.item];
            }
        }
        SmartBind.getProxyHandler=function(bindContext, bindPath){
            return  {
                get: function(target, name){
                    if ( name == '__isProxy' )
                        return true;
                    // just get the value
                    // console.debug("proxy get", bindPath, name, target[name]);
                    return target[name];
                }
                ,
                set: function(target, name, value){
                    target[name]=value;
                    bindContext.mapDataTarget[bindPath+"."+name]=value;
                    SmartBind.processBindToDom(bindContext, bindPath+"."+name);
                    // console.debug("proxy set", bindPath, name, target[name], value );
                    // and set all related objects with this target.name
                    if ( value instanceof Object) {
                        if ( !( name in target) || ! ( target[name].__isProxy ) ){
                            target[name]=new Proxy(value, SmartBind.getProxyHandler(bindContext, bindPath+'.'+name));
                        }
                        // run all tree to set proxies when necessary
                        var objKeys=Object.keys(value);
                        // console.debug("...objkeys",objKeys);
                        for ( var i=0; i<objKeys.length; i++ ) {
                            bindContext.mapDataTarget[bindPath+"."+name+"."+objKeys[i]]=target[name][objKeys[i]];
                            if ( typeof value[objKeys[i]] == 'undefined' || value[objKeys[i]] == null || ! ( value[objKeys[i]] instanceof Object ) || value[objKeys[i]].__isProxy )
                                continue;
                            target[name][objKeys[i]]=new Proxy( value[objKeys[i]], SmartBind.getProxyHandler(bindContext, bindPath+'.'+name+"."+objKeys[i]));
                        }
                        // TODO it can be faster than run all items
                        var bindKeys=Object.keys(bindContext.mapDom);
                        for ( var i=0; i<bindKeys.length; i++ ) {
                            // console.log("test...", bindKeys[i], " for ", bindPath+"."+name);
                            if ( bindKeys[i].startsWith(bindPath+"."+name) ) {
                                // console.log("its ok, lets update dom...", bindKeys[i]);
                                SmartBind.processBindToDom( bindContext, bindKeys[i] );
                            }
                        }
                    }
                    return true;
                }
            };
        }
        SmartBind.processBindToDom=function(bindContext, bindPath) {
            var domList=bindContext.mapDom[bindPath];
            if ( typeof domList != 'undefined' ) {
                try {
                    for ( var i=0; i < domList.length ; i++){
                        var dataTarget=SmartBind.getDataTarget(bindContext, bindPath);
                        if ( 'target' in dataTarget )
                            domList[i].value=dataTarget.target[dataTarget.item];
                        else
                            console.warn("Could not get data target", bindContext, bindPath);
                    }
                } catch (e){
                    console.warn("bind fail", bindPath, bindContext, e);
                }
            }
        }
    }
})();

Dann zum Einstellen einfach:

var bindContext=SmartBind.BindContext();
SmartBind.add(bindContext, document.getElementById('a'));
SmartBind.add(bindContext, document.getElementById('b'));
SmartBind.add(bindContext, document.getElementById('c'));

var bindContext2=SmartBind.BindContext();
SmartBind.add(bindContext2, document.getElementById('d'));
SmartBind.add(bindContext2, document.getElementById('e'));
SmartBind.add(bindContext2, document.getElementById('f'));
SmartBind.add(bindContext2, document.getElementById('g'));

setTimeout( function() {
    document.getElementById('b').value='Via Script works too!'
}, 2000);

document.getElementById('g').addEventListener('click',function(){
bindContext2.data.test='Set by js value'
})

Im Moment habe ich gerade den HTMLInputElement-Wert bind hinzugefügt.

Lass es mich wissen, wenn du weißt, wie ich es verbessern kann.

7
ton

In diesem Link gibt es eine sehr einfache Barebone-Implementierung der 2-Wege-Datenbindung "Einfache bidirektionale 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 jQuery was gut mit jQuery spielt und von dem ich der bescheidene (oder vielleicht auch nicht so bescheidene) Autor bin.

Beispielcode unten reproduzieren (von Blogpost-Link ):

Beispielcode für DataBinder

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 das JavaScript-Objekt ist eine minimale Implementierung von Das Benutzermodell für dieses Experiment könnte das Folgende sein:

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;
}

Wann immer wir die Eigenschaften eines Modells an ein Teil der Benutzeroberfläche binden möchten, werden wir Sie müssen lediglich ein entsprechendes Datenattribut für das entsprechende .__ festlegen. HTML-Element:

// javascript
var user = new User( 123 );
user.set( "name", "Wolfgang" );

<!-- html -->
<input type="number" data-bind-123="name" />
6
Nikos M.

Binden Sie eine beliebige HTML-Eingabe 

<input id="element-to-bind" type="text">

definiere zwei Funktionen:

function bindValue(objectToBind) {
var elemToBind = document.getElementById(objectToBind.id)    
elemToBind.addEventListener("change", function() {
    objectToBind.value = this.value;
})
}

function proxify(id) { 
var handler = {
    set: function(target, key, value, receiver) {
        target[key] = value;
        document.getElementById(target.id).value = value;
        return Reflect.set(target, key, value);
    },
}
return new Proxy({id: id}, handler);
}

benutze die Funktionen:

var myObject = proxify('element-to-bind')
bindValue(myObject);
2
o-t-w
<!DOCTYPE html>
<html>
<head>
    <title>Test</title>
</head>
<body>

<input type="text" id="demo" name="">
<p id="view"></p>
<script type="text/javascript">
    var id = document.getElementById('demo');
    var view = document.getElementById('view');
    id.addEventListener('input', function(evt){
        view.innerHTML = this.value;
    });

</script>
</body>
</html>
1

Eine einfache Möglichkeit, eine Variable an eine Eingabe zu binden (bidirektionale Bindung), besteht im direkten Zugriff auf das Eingabeelement im Getter und Setter:

var variable = function(element){                    
                   return {
                       get : function () { return element.value;},
                       set : function (value) { element.value = value;} 
                   }
               };

In HTML:

<input id="an-input" />
<input id="another-input" />

Und zu verwenden:

var myVar = new variable(document.getElementById("an-input"));
myVar.set(10);

// and another example:
var myVar2 = new variable(document.getElementById("another-input"));
myVar.set(myVar2.get());


.__ Eine schickere Art, das oben ohne Getter/Setter zu tun:

var variable = function(element){

                return function () {
                    if(arguments.length > 0)                        
                        element.value = arguments[0];                                           

                    else return element.value;                                                  
                }

        }

Benutzen:

var v1 = new variable(document.getElementById("an-input"));
v1(10); // sets value to 20.
console.log(v1()); // reads value.
1
A-Sharabiani

Ich habe einige grundlegende Javascript-Beispiele durchlaufen, in denen die Ereignis-Handler onkeypress und onchange verwendet wurden, um eine verbindliche Ansicht unserer Js und Js zu erstellen

Hier Beispiel Plunker http://plnkr.co/edit/7hSOIFRTvqLAvdZT4Bcc?p=preview

<!DOCTYPE html>
<html>
<body>

    <p>Two way binding data.</p>

    <p>Binding data from  view to JS</p>

    <input type="text" onkeypress="myFunction()" id="myinput">
    <p id="myid"></p>
    <p>Binding data from  js to view</p>
    <input type="text" id="myid2" onkeypress="myFunction1()" oninput="myFunction1()">
    <p id="myid3" onkeypress="myFunction1()" id="myinput" oninput="myFunction1()"></p>

    <script>

        document.getElementById('myid2').value="myvalue from script";
        document.getElementById('myid3').innerHTML="myvalue from script";
        function myFunction() {
            document.getElementById('myid').innerHTML=document.getElementById('myinput').value;
        }
        document.getElementById("myinput").onchange=function(){

            myFunction();

        }
        document.getElementById("myinput").oninput=function(){

            myFunction();

        }

        function myFunction1() {

            document.getElementById('myid3').innerHTML=document.getElementById('myid2').value;
        }
    </script>

</body>
</html>
1
macha devendher

Das Ändern eines Elementwerts kann ein DOM-Ereignis auslösen. Listener, die auf Ereignisse reagieren, können zur Implementierung der Datenbindung in JavaScript verwendet werden.

Zum Beispiel:

function bindValues(id1, id2) {
  const e1 = document.getElementById(id1);
  const e2 = document.getElementById(id2);
  e1.addEventListener('input', function(event) {
    e2.value = event.target.value;
  });
  e2.addEventListener('input', function(event) {
    e1.value = event.target.value;
  });
}

Hier ist Code und eine Demo, die zeigt, wie DOM-Elemente miteinander oder mit einem JavaScript-Objekt verbunden werden können.

1
amusingmaker

Hier ist eine Idee mit Object.defineProperty, die den Zugriff auf eine Eigenschaft direkt ändert. 

Code:

function bind(base, el, varname) {
    Object.defineProperty(base, varname, {
        get: () => {
            return el.value;
        },
        set: (value) => {
            el.value = value;
        }
    })
}

Verwendungszweck:

var p = new some_class();
bind(p,document.getElementById("someID"),'variable');

p.variable="yes"

geige: hier

0
Thornkey

Es ist sehr einfach in beide Richtungen Datenbindung in Vanilla Javascript ....

<input type="text" id="inp" onkeyup="document.getElementById('name').innerHTML=document.getElementById('inp').value;">

<div id="name">

</div>

0
Subodh Gawade