How to Implement DOM Data Binding in JavaScript












225















Please treat this question as strictly educational. I'm still interested in hearing new answers and ideas to implement this



tl;dr



How would I implement bi-directional data-binding with JavaScript?



Data Binding to the DOM



By data binding to the DOM I mean for example, having a JavaScript object a with a property b. Then having an <input> DOM element (for example), when the DOM element changes, a changes and vice versa (that is, I mean bidirectional data binding).



Here is a diagram from AngularJS on what this looks like:



two way data binding



So basically I have JavaScript similar to:



var a = {b:3};


Then an input (or other form) element like:



<input type='text' value=''>


I'd like the input's value to be a.b's value (for example), and when the input text changes, I'd like a.b to change too. When a.b changes in JavaScript, the input changes.



The Question



What are some basic techniques to accomplish this in plain JavaScript?



In specific, I'd like a good answer to refer to:




  • How would binding work for objects?

  • How listening to change in the form might work?

  • Is it possible in a simple way to only have the HTML modified on the template level? I'd like to not keep track of the binding in the HTML document itself but only in JavaScript (with DOM events, and JavaScript keeping reference to the DOM elements used).


What have I tried?



I'm a big fan of Mustache so I tried using it for templating. However, I ran into issues when trying to perform the data binding itself since Mustache processes HTML as a string so after I get its result I have no reference to where the objects in my viewmodel are. The only workaround I could think for this was modifying the HTML string (or created DOM tree) itself with attributes. I don't mind using a different templating engine.



Basically, I got a strong feeling that I was complicating the issue at hand and there is a simple solution.



Note: Please do not provide answers that use external libraries, especially ones that are thousands of lines of code. I've used (and like!) AngularJS and KnockoutJS. I really don't want answers in the form 'use framework x'. Optimally, I'd like a future reader who doesn't know how to use many frameworks to grasp how to implement bi-directional data-binding herself. I do not expect a complete answer, but one that gets the idea across.










share|improve this question




















  • 1





    I based CrazyGlue on Benjamin Gruenbaum's design. It also supports SELECT, checkbox and radio tags. jQuery is a dependency.

    – JohnSz
    Mar 21 '14 at 15:24






  • 10





    This question is totally awesome. If it ever gets closed for being off-topic or some other silly nonsense, I'm going to be seriously ticked off.

    – OCDev
    Dec 31 '14 at 0:17











  • @JohnSz thanks for mentioning your CrazyGlue project. I've been searching for a simple 2 way data binder for a long time. It looks like you aren't using Object.observe so your browser support should be great. And you aren't using mustache templating so its perfect.

    – Gavin
    Jan 2 '15 at 17:13











  • @Benjamin What did you end up doing?

    – johnny
    Mar 29 '16 at 20:05











  • @johnny in my opinion the correct approach is to create the DOM in JS (like React) and not vice versa. I think that eventually that's what we'll do.

    – Benjamin Gruenbaum
    Mar 29 '16 at 20:45
















225















Please treat this question as strictly educational. I'm still interested in hearing new answers and ideas to implement this



tl;dr



How would I implement bi-directional data-binding with JavaScript?



Data Binding to the DOM



By data binding to the DOM I mean for example, having a JavaScript object a with a property b. Then having an <input> DOM element (for example), when the DOM element changes, a changes and vice versa (that is, I mean bidirectional data binding).



Here is a diagram from AngularJS on what this looks like:



two way data binding



So basically I have JavaScript similar to:



var a = {b:3};


Then an input (or other form) element like:



<input type='text' value=''>


I'd like the input's value to be a.b's value (for example), and when the input text changes, I'd like a.b to change too. When a.b changes in JavaScript, the input changes.



The Question



What are some basic techniques to accomplish this in plain JavaScript?



In specific, I'd like a good answer to refer to:




  • How would binding work for objects?

  • How listening to change in the form might work?

  • Is it possible in a simple way to only have the HTML modified on the template level? I'd like to not keep track of the binding in the HTML document itself but only in JavaScript (with DOM events, and JavaScript keeping reference to the DOM elements used).


What have I tried?



I'm a big fan of Mustache so I tried using it for templating. However, I ran into issues when trying to perform the data binding itself since Mustache processes HTML as a string so after I get its result I have no reference to where the objects in my viewmodel are. The only workaround I could think for this was modifying the HTML string (or created DOM tree) itself with attributes. I don't mind using a different templating engine.



Basically, I got a strong feeling that I was complicating the issue at hand and there is a simple solution.



Note: Please do not provide answers that use external libraries, especially ones that are thousands of lines of code. I've used (and like!) AngularJS and KnockoutJS. I really don't want answers in the form 'use framework x'. Optimally, I'd like a future reader who doesn't know how to use many frameworks to grasp how to implement bi-directional data-binding herself. I do not expect a complete answer, but one that gets the idea across.










share|improve this question




















  • 1





    I based CrazyGlue on Benjamin Gruenbaum's design. It also supports SELECT, checkbox and radio tags. jQuery is a dependency.

    – JohnSz
    Mar 21 '14 at 15:24






  • 10





    This question is totally awesome. If it ever gets closed for being off-topic or some other silly nonsense, I'm going to be seriously ticked off.

    – OCDev
    Dec 31 '14 at 0:17











  • @JohnSz thanks for mentioning your CrazyGlue project. I've been searching for a simple 2 way data binder for a long time. It looks like you aren't using Object.observe so your browser support should be great. And you aren't using mustache templating so its perfect.

    – Gavin
    Jan 2 '15 at 17:13











  • @Benjamin What did you end up doing?

    – johnny
    Mar 29 '16 at 20:05











  • @johnny in my opinion the correct approach is to create the DOM in JS (like React) and not vice versa. I think that eventually that's what we'll do.

    – Benjamin Gruenbaum
    Mar 29 '16 at 20:45














225












225








225


156






Please treat this question as strictly educational. I'm still interested in hearing new answers and ideas to implement this



tl;dr



How would I implement bi-directional data-binding with JavaScript?



Data Binding to the DOM



By data binding to the DOM I mean for example, having a JavaScript object a with a property b. Then having an <input> DOM element (for example), when the DOM element changes, a changes and vice versa (that is, I mean bidirectional data binding).



Here is a diagram from AngularJS on what this looks like:



two way data binding



So basically I have JavaScript similar to:



var a = {b:3};


Then an input (or other form) element like:



<input type='text' value=''>


I'd like the input's value to be a.b's value (for example), and when the input text changes, I'd like a.b to change too. When a.b changes in JavaScript, the input changes.



The Question



What are some basic techniques to accomplish this in plain JavaScript?



In specific, I'd like a good answer to refer to:




  • How would binding work for objects?

  • How listening to change in the form might work?

  • Is it possible in a simple way to only have the HTML modified on the template level? I'd like to not keep track of the binding in the HTML document itself but only in JavaScript (with DOM events, and JavaScript keeping reference to the DOM elements used).


What have I tried?



I'm a big fan of Mustache so I tried using it for templating. However, I ran into issues when trying to perform the data binding itself since Mustache processes HTML as a string so after I get its result I have no reference to where the objects in my viewmodel are. The only workaround I could think for this was modifying the HTML string (or created DOM tree) itself with attributes. I don't mind using a different templating engine.



Basically, I got a strong feeling that I was complicating the issue at hand and there is a simple solution.



Note: Please do not provide answers that use external libraries, especially ones that are thousands of lines of code. I've used (and like!) AngularJS and KnockoutJS. I really don't want answers in the form 'use framework x'. Optimally, I'd like a future reader who doesn't know how to use many frameworks to grasp how to implement bi-directional data-binding herself. I do not expect a complete answer, but one that gets the idea across.










share|improve this question
















Please treat this question as strictly educational. I'm still interested in hearing new answers and ideas to implement this



tl;dr



How would I implement bi-directional data-binding with JavaScript?



Data Binding to the DOM



By data binding to the DOM I mean for example, having a JavaScript object a with a property b. Then having an <input> DOM element (for example), when the DOM element changes, a changes and vice versa (that is, I mean bidirectional data binding).



Here is a diagram from AngularJS on what this looks like:



two way data binding



So basically I have JavaScript similar to:



var a = {b:3};


Then an input (or other form) element like:



<input type='text' value=''>


I'd like the input's value to be a.b's value (for example), and when the input text changes, I'd like a.b to change too. When a.b changes in JavaScript, the input changes.



The Question



What are some basic techniques to accomplish this in plain JavaScript?



In specific, I'd like a good answer to refer to:




  • How would binding work for objects?

  • How listening to change in the form might work?

  • Is it possible in a simple way to only have the HTML modified on the template level? I'd like to not keep track of the binding in the HTML document itself but only in JavaScript (with DOM events, and JavaScript keeping reference to the DOM elements used).


What have I tried?



I'm a big fan of Mustache so I tried using it for templating. However, I ran into issues when trying to perform the data binding itself since Mustache processes HTML as a string so after I get its result I have no reference to where the objects in my viewmodel are. The only workaround I could think for this was modifying the HTML string (or created DOM tree) itself with attributes. I don't mind using a different templating engine.



Basically, I got a strong feeling that I was complicating the issue at hand and there is a simple solution.



Note: Please do not provide answers that use external libraries, especially ones that are thousands of lines of code. I've used (and like!) AngularJS and KnockoutJS. I really don't want answers in the form 'use framework x'. Optimally, I'd like a future reader who doesn't know how to use many frameworks to grasp how to implement bi-directional data-binding herself. I do not expect a complete answer, but one that gets the idea across.







javascript html dom data-binding






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Apr 28 '14 at 19:09







Benjamin Gruenbaum

















asked May 10 '13 at 13:34









Benjamin GruenbaumBenjamin Gruenbaum

190k63403440




190k63403440








  • 1





    I based CrazyGlue on Benjamin Gruenbaum's design. It also supports SELECT, checkbox and radio tags. jQuery is a dependency.

    – JohnSz
    Mar 21 '14 at 15:24






  • 10





    This question is totally awesome. If it ever gets closed for being off-topic or some other silly nonsense, I'm going to be seriously ticked off.

    – OCDev
    Dec 31 '14 at 0:17











  • @JohnSz thanks for mentioning your CrazyGlue project. I've been searching for a simple 2 way data binder for a long time. It looks like you aren't using Object.observe so your browser support should be great. And you aren't using mustache templating so its perfect.

    – Gavin
    Jan 2 '15 at 17:13











  • @Benjamin What did you end up doing?

    – johnny
    Mar 29 '16 at 20:05











  • @johnny in my opinion the correct approach is to create the DOM in JS (like React) and not vice versa. I think that eventually that's what we'll do.

    – Benjamin Gruenbaum
    Mar 29 '16 at 20:45














  • 1





    I based CrazyGlue on Benjamin Gruenbaum's design. It also supports SELECT, checkbox and radio tags. jQuery is a dependency.

    – JohnSz
    Mar 21 '14 at 15:24






  • 10





    This question is totally awesome. If it ever gets closed for being off-topic or some other silly nonsense, I'm going to be seriously ticked off.

    – OCDev
    Dec 31 '14 at 0:17











  • @JohnSz thanks for mentioning your CrazyGlue project. I've been searching for a simple 2 way data binder for a long time. It looks like you aren't using Object.observe so your browser support should be great. And you aren't using mustache templating so its perfect.

    – Gavin
    Jan 2 '15 at 17:13











  • @Benjamin What did you end up doing?

    – johnny
    Mar 29 '16 at 20:05











  • @johnny in my opinion the correct approach is to create the DOM in JS (like React) and not vice versa. I think that eventually that's what we'll do.

    – Benjamin Gruenbaum
    Mar 29 '16 at 20:45








1




1





I based CrazyGlue on Benjamin Gruenbaum's design. It also supports SELECT, checkbox and radio tags. jQuery is a dependency.

– JohnSz
Mar 21 '14 at 15:24





I based CrazyGlue on Benjamin Gruenbaum's design. It also supports SELECT, checkbox and radio tags. jQuery is a dependency.

– JohnSz
Mar 21 '14 at 15:24




10




10





This question is totally awesome. If it ever gets closed for being off-topic or some other silly nonsense, I'm going to be seriously ticked off.

– OCDev
Dec 31 '14 at 0:17





This question is totally awesome. If it ever gets closed for being off-topic or some other silly nonsense, I'm going to be seriously ticked off.

– OCDev
Dec 31 '14 at 0:17













@JohnSz thanks for mentioning your CrazyGlue project. I've been searching for a simple 2 way data binder for a long time. It looks like you aren't using Object.observe so your browser support should be great. And you aren't using mustache templating so its perfect.

– Gavin
Jan 2 '15 at 17:13





@JohnSz thanks for mentioning your CrazyGlue project. I've been searching for a simple 2 way data binder for a long time. It looks like you aren't using Object.observe so your browser support should be great. And you aren't using mustache templating so its perfect.

– Gavin
Jan 2 '15 at 17:13













@Benjamin What did you end up doing?

– johnny
Mar 29 '16 at 20:05





@Benjamin What did you end up doing?

– johnny
Mar 29 '16 at 20:05













@johnny in my opinion the correct approach is to create the DOM in JS (like React) and not vice versa. I think that eventually that's what we'll do.

– Benjamin Gruenbaum
Mar 29 '16 at 20:45





@johnny in my opinion the correct approach is to create the DOM in JS (like React) and not vice versa. I think that eventually that's what we'll do.

– Benjamin Gruenbaum
Mar 29 '16 at 20:45












13 Answers
13






active

oldest

votes


















100
















  • How would binding work for objects?

  • How listening to change in the form might work?




An abstraction that updates both objects



I suppose there are other techniques, but ultimately I'd have an object that holds reference to a related DOM element, and provides an interface that coordinates updates to its own data and its related element.



The .addEventListener() provides a very nice interface for this. You can give it an object that implements the eventListener interface, and it'll invoke its handlers with that object as the this value.



This gives you automatic access to both the element and its related data.



Defining your object



Prototypal inheritance is a nice way to implement this, though not required of course. First you'd create a constructor that receives your element and some initial data.



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


So here the constructor stores the element and data on properties of the new object. It also binds a change event to the given element. The interesting thing is that it passes the new object instead of a function as the second argument. But this alone won't work.



Implementing the eventListener interface



To make this work, your object needs to implement the eventListener interface. All that's needed to accomplish this is to give the object a handleEvent() method.



That's where the inheritance comes in.



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


There are many different ways in which this could be structured, but for your example of coordinating updates, I decided to make the change() method only accept a value, and have the handleEvent pass that value instead of the event object. This way the change() can be invoked without an event as well.



So now, when the change event happens, it'll update both the element and the .data property. And the same will happen when you call .change() in your JavaScript program.



Using the code



Now you'd just create the new object, and let it perform updates. Updates in JS code will appear on the input, and change events on the input will be visible to the JS code.



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/






share|improve this answer





















  • 5





    +1 Very clean approach, very simply put and simple enough for people to learn from, a lot cleaner than what I had. A common use case is using templates in code to represent objects' views. I was wondering how this might work here? In engines like Mustache I do something Mustache.render(template,object), assuming I want to keep an object synced with the template (not specific to Mustache) , how would I go on about that?

    – Benjamin Gruenbaum
    May 10 '13 at 14:21






  • 3





    @BenjaminGruenbaum: I haven't used client-side templates, but I would imagine that Mustache has some syntax for identifying insertion points, and that that syntax includes a label. So I would think that the "static" parts of the template would be rendered into chunks of HTML stored in an Array, and the dynamic parts would go between those chunks. Then the labels on the insertion points would be used as object properties. Then if some input is to update one of those points, there would be a mapping from the input to that point. I'll see if I can come up with a quick example.

    – user1106925
    May 10 '13 at 14:28






  • 1





    @BenjaminGruenbaum: Hmmm... I haven't thought about how to cleanly coordinate two different elements. This is a little more involved than I thought at first. I'm curious though, so I may need to work on this a little later. :)

    – user1106925
    May 10 '13 at 14:53






  • 2





    You'll see that there's a primary Template constructor that does the parsing, holds the different MyCtor objects, and provides an interface to update each on by its identifier. Let me know if you have questions. :) EDIT: ...use this link instead... I had forgotten that I had an exponential increase in the input value every 10 seconds to demonstrate JS updates. This limits it.

    – user1106925
    May 10 '13 at 15:32








  • 2





    ...fully commented version plus minor improvements.

    – user1106925
    May 10 '13 at 15:52





















34














So, I decided to throw my own solution in the pot. Here is a working fiddle. Note this only runs on very modern browsers.



What it uses



This implementation is very modern - it requires a (very) modern browser and users two new technologies:





  • MutationObservers to detect changes in the dom (event listeners are used as well)


  • Object.observe to detect changes in the object and notifying the dom. Danger, since this answer has been written O.o has been discussed and decided against by the ECMAScript TC, consider a polyfill.


How it works




  • On the element, put a domAttribute:objAttribute mapping - for example bind='textContent:name'

  • Read that in the dataBind function. Observe changes to both the element and the object.

  • When a change occurs - update the relevant element.


The solution



Here is the dataBind function, note it's just 20 lines of code and could be shorter:



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


Here is some usage:



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


Here is a working fiddle. Note that this solution is pretty generic. Object.observe and mutation observer shimming is available.






share|improve this answer





















  • 1





    I just happened to write this (es5) for fun, if anyone finds it useful - knock yourself out jsfiddle.net/P9rMm

    – Benjamin Gruenbaum
    Apr 19 '14 at 15:57






  • 1





    Keep in mind that when obj.name has a setter it cannot be observed externally, but must broadcast that it has changed from within the setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - kinda throws a wrench in the works for O.o() if you want more complex, interdependent behavior using setters. Furthermore, when obj.name is not configurable, redefining it's setter (with various tricks to add notification) also is not allowed - so generics with O.o() are totally scrapped in that specific case.

    – Nolo
    Oct 20 '14 at 17:25






  • 7





    Object.observe is removed from all browsers: caniuse.com/#feat=object-observe

    – JvdBerg
    Jun 23 '16 at 13:46






  • 1





    A Proxy can be used instead of Object.observe, or github.com/anywhichway/proxy-observe or gist.github.com/ebidel/1b553d571f924da2da06 or the older polyfills, also on github @JvdBerg

    – jimmont
    Mar 18 '17 at 4:22



















24














I'd like to add to my preposter. I suggest a slightly different approach that will allow you to simply assign a new value to your object without using a method. It must be noted though that this is not supported by especially older browsers and IE9 still requires use of a different interface.



Most notably is that my approach does not make use of events.



Getters and Setters



My proposal makes use of the relatively young feature of getters and setters, particularly setters only. Generally speaking, mutators allow us to "customize" the behavior of how certain properties are assigned a value and retrieved.



One implementation I'll be using here is the Object.defineProperty method. It works in FireFox, GoogleChrome and - I think - IE9. Haven't tested other browsers, but since this is theory only...



Anyways, it accepts three parameters. The first parameter being the object that you wish to define a new property for, the second a string resembling the the name of the new property and the last a "descriptor object" providing information on the behavior of the new property.



Two particularly interesting descriptors are get and set. An example would look something like the following. Note that using these two prohibits the use of the other 4 descriptors.



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


Now making use of this becomes slightly different:



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


I want to emphasize that this only works for modern browsers.



Working fiddle: http://jsfiddle.net/Derija93/RkTMD/1/






share|improve this answer





















  • 1





    If only we had Harmony Proxy objects :) Setters do seem like a nice idea, but wouldn't that require us to modify the actual objects? Also, on a side note - Object.create could be used here (again, assuming modern browser that allowed for the second parameter). Also, the setter/getter could be used to 'project' a different value to the object and the DOM element :) . I'm wondering if you have any insights on templating too, that seems like a real challenge here, especially to structure nicely :)

    – Benjamin Gruenbaum
    May 10 '13 at 15:00











  • Just like my preposter, I too do not work a lot with client-side templating engines, sorry. :( But what do you mean by modify the actual objects? And I'd like to understand your thoughts of how you got to understand that the setter/getter could be used to .... The getters/setters here are used for nothing but redirecting all input to and retrievals from the object to the DOM element, basically like a Proxy, like you said. ;) I understood the challenge to be to keep two distinct properties synchronized. My method eliminates one of both.

    – Kiruse
    May 10 '13 at 15:10











  • A Proxy would eliminate the need to use getters/setters, you could bind elements without knowing what properties they have. What I meant, is that the getters can change more than bindTo.value they can contain logic (and maybe even a template). The question is how to maintain this sort of bidirectional binding with a template in mind? Lets say I'm mapping my object to a form, I'd like to maintain both the element and the form synced and I'm wondering how I'd go on about that sort of thing. You can check out how that works on knockout learn.knockoutjs.com/#/?tutorial=intro for example

    – Benjamin Gruenbaum
    May 10 '13 at 15:24











  • @BenjaminGruenbaum Gotcha. I'll give it a look.

    – Kiruse
    May 10 '13 at 15:33











  • @BenjaminGruenbaum I see what you're trying to understand. Setting all this up with templates in mind turns out to be a little more difficult. I'll be working on this script for a while (and continuously rebase it). But for now, I'm taking a break. I actually don't quite have the time for this.

    – Kiruse
    May 10 '13 at 16:41



















7














I think my answer will be more technical, but not different as the others present the same thing using different techniques.

So, first things first, the solution to this problem is the use of a design pattern known as "observer", it let's you decouple your data from your presentation, making the change in one thing be broadcasted to their listeners, but in this case it's made two-way.



For the DOM to JS way



To bind the data from the DOM to the js object you may add markup in the form of data attributes (or classes if you need compatibility), like this:



<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=""/>


This way it can be accessed via js using querySelectorAll (or the old friend getElementsByClassName for compatibility).



Now you can bind the event listening to the changes in to ways: one listener per object or one big listener to the container/document. Binding to the document/container will trigger the event for every change made in it or it's child, it willhave a smaller memory footprint but will spawn event calls.

The code will look something like this:



//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);


For the JS do DOM way



You will need two things: one meta-object that will hold the references of witch DOM element is binded to each js object/attribute and a way to listen to changes in objects. It is basically the same way: you have to have a way to listen to changes in the object and then bind it to the DOM node, as your object "can't have" metadata you will need another object that holds metadata in a way that the property name maps to the metadata object's properties.
The code will be something like this:



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


I hope that i was of help.






share|improve this answer
























  • isn't there comparability issue with using the .observer?

    – Mohsen Shakiba
    Jul 6 '15 at 6:15











  • for now it needs a shim or polyfill to Object.observe as support is presente only in chrome for now. caniuse.com/#feat=object-observe

    – madcampos
    Jul 30 '15 at 5:24








  • 8





    Object.observe is dead. Just thought I'd note that here.

    – Benjamin Gruenbaum
    Oct 29 '15 at 8:57











  • @BenjaminGruenbaum What's the correct thing to use now, since this is dead?

    – johnny
    Mar 29 '16 at 19:58






  • 1





    @johnny if i'm not wrong it would be proxy traps as they allow to a more granular control of what can i do with an object, but i have to investigate that.

    – madcampos
    Mar 31 '16 at 14:53



















7














Yesterday, I started to write my own way to bind data.



It's very funny to play with it.



I think it's beautiful and very useful. At least on my tests using firefox and chrome, Edge must works too. Not sure about others, but if they support Proxy, I think it will work.



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' />


Here is the 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);
}
}
}
}
})();


Then, to set, just:



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'
})


For now, I've just added the HTMLInputElement value bind.



Let me know if you know how to improve it.






share|improve this answer

































    6














    There is a very simple barebones implementation of 2-way data-binding in this link "Easy Two-Way Data Binding in JavaScript"



    The previous link along with ideas from knockoutjs, backbone.js and agility.js, led to this light-weight and fast MVVM framework, ModelView.js based on jQuery which plays nicely with jQuery and of which i am the humble (or maybe not so humble) author.



    Reproducing sample code below (from blog post link):



    Sample code for 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;
    }



    For what concerns the JavaScript object, a minimal implementation of a
    User model for the sake of this experiment could be the following:




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



    Now, whenever we want to bind a model’s property to a piece of UI we
    just have to set an appropriate data attribute on the corresponding
    HTML element:




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

    <!-- html -->
    <input type="number" data-bind-123="name" />





    share|improve this answer


























    • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.

      – Sam Hanley
      Apr 13 '15 at 13:07











    • @sphanley, noted, i'll probably update when i have more time, as it a rather long code for an answer post

      – Nikos M.
      Apr 13 '15 at 19:24











    • @sphanley, reproduced sample code on answer from referenced link (although i thinbk this creates duplicarte content most of the time, anyway)

      – Nikos M.
      Apr 13 '15 at 19:32






    • 1





      It definitely does create duplicate content, but that's the point - blog links can often break with time, and by duplicating the relevant content here it ensures that it will be available and useful to future readers. The answer looks great now!

      – Sam Hanley
      Apr 14 '15 at 12:33





















    3














    Bind any html input



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


    define two functions:



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


    use the functions:



    var myObject = proxify('element-to-bind')
    bindValue(myObject);





    share|improve this answer

































      1














      Changing an element's value can trigger a DOM event. Listeners that respond to events can be used to implement data binding in JavaScript.



      For example:



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


      Here is code and a demo that shows how DOM elements can be bound with each other or with a JavaScript object.






      share|improve this answer

































        1














        I have gone through some basic javascript example using onkeypress and onchange event handlers for making binding view to our js and js to view



        Here example 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>





        share|improve this answer































          1














          <!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>





          share|improve this answer































            1














            A simple way of binding a variable to an input (two-way binding) is to just directly access the input element in the getter and 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" />


            And to use:



            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());





            A fancier way of doing the above without getter/setter:

            var variable = function(element){

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

            else return element.value;
            }

            }


            To use:



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





            share|improve this answer

































              0














              It is very simple two way data binding in vanilla javascript....











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

              <div id="name">

              </div>









              share|improve this answer



















              • 1





                surely this would only work with the onkeyup event? i.e. if you did an ajax request, and then changed the innerHTML via JavaScript then this wouldn't work

                – Zach Smith
                Jan 22 '18 at 15:50



















              0














              Here's an idea using Object.defineProperty which directly modifies the way a property is accessed.



              Code:



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


              Usage:



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

              p.variable="yes"


              fiddle: Here






              share|improve this answer























                Your Answer






                StackExchange.ifUsing("editor", function () {
                StackExchange.using("externalEditor", function () {
                StackExchange.using("snippets", function () {
                StackExchange.snippets.init();
                });
                });
                }, "code-snippets");

                StackExchange.ready(function() {
                var channelOptions = {
                tags: "".split(" "),
                id: "1"
                };
                initTagRenderer("".split(" "), "".split(" "), channelOptions);

                StackExchange.using("externalEditor", function() {
                // Have to fire editor after snippets, if snippets enabled
                if (StackExchange.settings.snippets.snippetsEnabled) {
                StackExchange.using("snippets", function() {
                createEditor();
                });
                }
                else {
                createEditor();
                }
                });

                function createEditor() {
                StackExchange.prepareEditor({
                heartbeatType: 'answer',
                autoActivateHeartbeat: false,
                convertImagesToLinks: true,
                noModals: true,
                showLowRepImageUploadWarning: true,
                reputationToPostImages: 10,
                bindNavPrevention: true,
                postfix: "",
                imageUploader: {
                brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
                contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
                allowUrls: true
                },
                onDemand: true,
                discardSelector: ".discard-answer"
                ,immediatelyShowMarkdownHelp:true
                });


                }
                });














                draft saved

                draft discarded


















                StackExchange.ready(
                function () {
                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f16483560%2fhow-to-implement-dom-data-binding-in-javascript%23new-answer', 'question_page');
                }
                );

                Post as a guest















                Required, but never shown

























                13 Answers
                13






                active

                oldest

                votes








                13 Answers
                13






                active

                oldest

                votes









                active

                oldest

                votes






                active

                oldest

                votes









                100
















                • How would binding work for objects?

                • How listening to change in the form might work?




                An abstraction that updates both objects



                I suppose there are other techniques, but ultimately I'd have an object that holds reference to a related DOM element, and provides an interface that coordinates updates to its own data and its related element.



                The .addEventListener() provides a very nice interface for this. You can give it an object that implements the eventListener interface, and it'll invoke its handlers with that object as the this value.



                This gives you automatic access to both the element and its related data.



                Defining your object



                Prototypal inheritance is a nice way to implement this, though not required of course. First you'd create a constructor that receives your element and some initial data.



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


                So here the constructor stores the element and data on properties of the new object. It also binds a change event to the given element. The interesting thing is that it passes the new object instead of a function as the second argument. But this alone won't work.



                Implementing the eventListener interface



                To make this work, your object needs to implement the eventListener interface. All that's needed to accomplish this is to give the object a handleEvent() method.



                That's where the inheritance comes in.



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


                There are many different ways in which this could be structured, but for your example of coordinating updates, I decided to make the change() method only accept a value, and have the handleEvent pass that value instead of the event object. This way the change() can be invoked without an event as well.



                So now, when the change event happens, it'll update both the element and the .data property. And the same will happen when you call .change() in your JavaScript program.



                Using the code



                Now you'd just create the new object, and let it perform updates. Updates in JS code will appear on the input, and change events on the input will be visible to the JS code.



                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/






                share|improve this answer





















                • 5





                  +1 Very clean approach, very simply put and simple enough for people to learn from, a lot cleaner than what I had. A common use case is using templates in code to represent objects' views. I was wondering how this might work here? In engines like Mustache I do something Mustache.render(template,object), assuming I want to keep an object synced with the template (not specific to Mustache) , how would I go on about that?

                  – Benjamin Gruenbaum
                  May 10 '13 at 14:21






                • 3





                  @BenjaminGruenbaum: I haven't used client-side templates, but I would imagine that Mustache has some syntax for identifying insertion points, and that that syntax includes a label. So I would think that the "static" parts of the template would be rendered into chunks of HTML stored in an Array, and the dynamic parts would go between those chunks. Then the labels on the insertion points would be used as object properties. Then if some input is to update one of those points, there would be a mapping from the input to that point. I'll see if I can come up with a quick example.

                  – user1106925
                  May 10 '13 at 14:28






                • 1





                  @BenjaminGruenbaum: Hmmm... I haven't thought about how to cleanly coordinate two different elements. This is a little more involved than I thought at first. I'm curious though, so I may need to work on this a little later. :)

                  – user1106925
                  May 10 '13 at 14:53






                • 2





                  You'll see that there's a primary Template constructor that does the parsing, holds the different MyCtor objects, and provides an interface to update each on by its identifier. Let me know if you have questions. :) EDIT: ...use this link instead... I had forgotten that I had an exponential increase in the input value every 10 seconds to demonstrate JS updates. This limits it.

                  – user1106925
                  May 10 '13 at 15:32








                • 2





                  ...fully commented version plus minor improvements.

                  – user1106925
                  May 10 '13 at 15:52


















                100
















                • How would binding work for objects?

                • How listening to change in the form might work?




                An abstraction that updates both objects



                I suppose there are other techniques, but ultimately I'd have an object that holds reference to a related DOM element, and provides an interface that coordinates updates to its own data and its related element.



                The .addEventListener() provides a very nice interface for this. You can give it an object that implements the eventListener interface, and it'll invoke its handlers with that object as the this value.



                This gives you automatic access to both the element and its related data.



                Defining your object



                Prototypal inheritance is a nice way to implement this, though not required of course. First you'd create a constructor that receives your element and some initial data.



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


                So here the constructor stores the element and data on properties of the new object. It also binds a change event to the given element. The interesting thing is that it passes the new object instead of a function as the second argument. But this alone won't work.



                Implementing the eventListener interface



                To make this work, your object needs to implement the eventListener interface. All that's needed to accomplish this is to give the object a handleEvent() method.



                That's where the inheritance comes in.



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


                There are many different ways in which this could be structured, but for your example of coordinating updates, I decided to make the change() method only accept a value, and have the handleEvent pass that value instead of the event object. This way the change() can be invoked without an event as well.



                So now, when the change event happens, it'll update both the element and the .data property. And the same will happen when you call .change() in your JavaScript program.



                Using the code



                Now you'd just create the new object, and let it perform updates. Updates in JS code will appear on the input, and change events on the input will be visible to the JS code.



                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/






                share|improve this answer





















                • 5





                  +1 Very clean approach, very simply put and simple enough for people to learn from, a lot cleaner than what I had. A common use case is using templates in code to represent objects' views. I was wondering how this might work here? In engines like Mustache I do something Mustache.render(template,object), assuming I want to keep an object synced with the template (not specific to Mustache) , how would I go on about that?

                  – Benjamin Gruenbaum
                  May 10 '13 at 14:21






                • 3





                  @BenjaminGruenbaum: I haven't used client-side templates, but I would imagine that Mustache has some syntax for identifying insertion points, and that that syntax includes a label. So I would think that the "static" parts of the template would be rendered into chunks of HTML stored in an Array, and the dynamic parts would go between those chunks. Then the labels on the insertion points would be used as object properties. Then if some input is to update one of those points, there would be a mapping from the input to that point. I'll see if I can come up with a quick example.

                  – user1106925
                  May 10 '13 at 14:28






                • 1





                  @BenjaminGruenbaum: Hmmm... I haven't thought about how to cleanly coordinate two different elements. This is a little more involved than I thought at first. I'm curious though, so I may need to work on this a little later. :)

                  – user1106925
                  May 10 '13 at 14:53






                • 2





                  You'll see that there's a primary Template constructor that does the parsing, holds the different MyCtor objects, and provides an interface to update each on by its identifier. Let me know if you have questions. :) EDIT: ...use this link instead... I had forgotten that I had an exponential increase in the input value every 10 seconds to demonstrate JS updates. This limits it.

                  – user1106925
                  May 10 '13 at 15:32








                • 2





                  ...fully commented version plus minor improvements.

                  – user1106925
                  May 10 '13 at 15:52
















                100












                100








                100









                • How would binding work for objects?

                • How listening to change in the form might work?




                An abstraction that updates both objects



                I suppose there are other techniques, but ultimately I'd have an object that holds reference to a related DOM element, and provides an interface that coordinates updates to its own data and its related element.



                The .addEventListener() provides a very nice interface for this. You can give it an object that implements the eventListener interface, and it'll invoke its handlers with that object as the this value.



                This gives you automatic access to both the element and its related data.



                Defining your object



                Prototypal inheritance is a nice way to implement this, though not required of course. First you'd create a constructor that receives your element and some initial data.



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


                So here the constructor stores the element and data on properties of the new object. It also binds a change event to the given element. The interesting thing is that it passes the new object instead of a function as the second argument. But this alone won't work.



                Implementing the eventListener interface



                To make this work, your object needs to implement the eventListener interface. All that's needed to accomplish this is to give the object a handleEvent() method.



                That's where the inheritance comes in.



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


                There are many different ways in which this could be structured, but for your example of coordinating updates, I decided to make the change() method only accept a value, and have the handleEvent pass that value instead of the event object. This way the change() can be invoked without an event as well.



                So now, when the change event happens, it'll update both the element and the .data property. And the same will happen when you call .change() in your JavaScript program.



                Using the code



                Now you'd just create the new object, and let it perform updates. Updates in JS code will appear on the input, and change events on the input will be visible to the JS code.



                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/






                share|improve this answer

















                • How would binding work for objects?

                • How listening to change in the form might work?




                An abstraction that updates both objects



                I suppose there are other techniques, but ultimately I'd have an object that holds reference to a related DOM element, and provides an interface that coordinates updates to its own data and its related element.



                The .addEventListener() provides a very nice interface for this. You can give it an object that implements the eventListener interface, and it'll invoke its handlers with that object as the this value.



                This gives you automatic access to both the element and its related data.



                Defining your object



                Prototypal inheritance is a nice way to implement this, though not required of course. First you'd create a constructor that receives your element and some initial data.



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


                So here the constructor stores the element and data on properties of the new object. It also binds a change event to the given element. The interesting thing is that it passes the new object instead of a function as the second argument. But this alone won't work.



                Implementing the eventListener interface



                To make this work, your object needs to implement the eventListener interface. All that's needed to accomplish this is to give the object a handleEvent() method.



                That's where the inheritance comes in.



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


                There are many different ways in which this could be structured, but for your example of coordinating updates, I decided to make the change() method only accept a value, and have the handleEvent pass that value instead of the event object. This way the change() can be invoked without an event as well.



                So now, when the change event happens, it'll update both the element and the .data property. And the same will happen when you call .change() in your JavaScript program.



                Using the code



                Now you'd just create the new object, and let it perform updates. Updates in JS code will appear on the input, and change events on the input will be visible to the JS code.



                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/







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Dec 6 '16 at 6:59


























                community wiki





                3 revs, 2 users 98%
                user1106925









                • 5





                  +1 Very clean approach, very simply put and simple enough for people to learn from, a lot cleaner than what I had. A common use case is using templates in code to represent objects' views. I was wondering how this might work here? In engines like Mustache I do something Mustache.render(template,object), assuming I want to keep an object synced with the template (not specific to Mustache) , how would I go on about that?

                  – Benjamin Gruenbaum
                  May 10 '13 at 14:21






                • 3





                  @BenjaminGruenbaum: I haven't used client-side templates, but I would imagine that Mustache has some syntax for identifying insertion points, and that that syntax includes a label. So I would think that the "static" parts of the template would be rendered into chunks of HTML stored in an Array, and the dynamic parts would go between those chunks. Then the labels on the insertion points would be used as object properties. Then if some input is to update one of those points, there would be a mapping from the input to that point. I'll see if I can come up with a quick example.

                  – user1106925
                  May 10 '13 at 14:28






                • 1





                  @BenjaminGruenbaum: Hmmm... I haven't thought about how to cleanly coordinate two different elements. This is a little more involved than I thought at first. I'm curious though, so I may need to work on this a little later. :)

                  – user1106925
                  May 10 '13 at 14:53






                • 2





                  You'll see that there's a primary Template constructor that does the parsing, holds the different MyCtor objects, and provides an interface to update each on by its identifier. Let me know if you have questions. :) EDIT: ...use this link instead... I had forgotten that I had an exponential increase in the input value every 10 seconds to demonstrate JS updates. This limits it.

                  – user1106925
                  May 10 '13 at 15:32








                • 2





                  ...fully commented version plus minor improvements.

                  – user1106925
                  May 10 '13 at 15:52
















                • 5





                  +1 Very clean approach, very simply put and simple enough for people to learn from, a lot cleaner than what I had. A common use case is using templates in code to represent objects' views. I was wondering how this might work here? In engines like Mustache I do something Mustache.render(template,object), assuming I want to keep an object synced with the template (not specific to Mustache) , how would I go on about that?

                  – Benjamin Gruenbaum
                  May 10 '13 at 14:21






                • 3





                  @BenjaminGruenbaum: I haven't used client-side templates, but I would imagine that Mustache has some syntax for identifying insertion points, and that that syntax includes a label. So I would think that the "static" parts of the template would be rendered into chunks of HTML stored in an Array, and the dynamic parts would go between those chunks. Then the labels on the insertion points would be used as object properties. Then if some input is to update one of those points, there would be a mapping from the input to that point. I'll see if I can come up with a quick example.

                  – user1106925
                  May 10 '13 at 14:28






                • 1





                  @BenjaminGruenbaum: Hmmm... I haven't thought about how to cleanly coordinate two different elements. This is a little more involved than I thought at first. I'm curious though, so I may need to work on this a little later. :)

                  – user1106925
                  May 10 '13 at 14:53






                • 2





                  You'll see that there's a primary Template constructor that does the parsing, holds the different MyCtor objects, and provides an interface to update each on by its identifier. Let me know if you have questions. :) EDIT: ...use this link instead... I had forgotten that I had an exponential increase in the input value every 10 seconds to demonstrate JS updates. This limits it.

                  – user1106925
                  May 10 '13 at 15:32








                • 2





                  ...fully commented version plus minor improvements.

                  – user1106925
                  May 10 '13 at 15:52










                5




                5





                +1 Very clean approach, very simply put and simple enough for people to learn from, a lot cleaner than what I had. A common use case is using templates in code to represent objects' views. I was wondering how this might work here? In engines like Mustache I do something Mustache.render(template,object), assuming I want to keep an object synced with the template (not specific to Mustache) , how would I go on about that?

                – Benjamin Gruenbaum
                May 10 '13 at 14:21





                +1 Very clean approach, very simply put and simple enough for people to learn from, a lot cleaner than what I had. A common use case is using templates in code to represent objects' views. I was wondering how this might work here? In engines like Mustache I do something Mustache.render(template,object), assuming I want to keep an object synced with the template (not specific to Mustache) , how would I go on about that?

                – Benjamin Gruenbaum
                May 10 '13 at 14:21




                3




                3





                @BenjaminGruenbaum: I haven't used client-side templates, but I would imagine that Mustache has some syntax for identifying insertion points, and that that syntax includes a label. So I would think that the "static" parts of the template would be rendered into chunks of HTML stored in an Array, and the dynamic parts would go between those chunks. Then the labels on the insertion points would be used as object properties. Then if some input is to update one of those points, there would be a mapping from the input to that point. I'll see if I can come up with a quick example.

                – user1106925
                May 10 '13 at 14:28





                @BenjaminGruenbaum: I haven't used client-side templates, but I would imagine that Mustache has some syntax for identifying insertion points, and that that syntax includes a label. So I would think that the "static" parts of the template would be rendered into chunks of HTML stored in an Array, and the dynamic parts would go between those chunks. Then the labels on the insertion points would be used as object properties. Then if some input is to update one of those points, there would be a mapping from the input to that point. I'll see if I can come up with a quick example.

                – user1106925
                May 10 '13 at 14:28




                1




                1





                @BenjaminGruenbaum: Hmmm... I haven't thought about how to cleanly coordinate two different elements. This is a little more involved than I thought at first. I'm curious though, so I may need to work on this a little later. :)

                – user1106925
                May 10 '13 at 14:53





                @BenjaminGruenbaum: Hmmm... I haven't thought about how to cleanly coordinate two different elements. This is a little more involved than I thought at first. I'm curious though, so I may need to work on this a little later. :)

                – user1106925
                May 10 '13 at 14:53




                2




                2





                You'll see that there's a primary Template constructor that does the parsing, holds the different MyCtor objects, and provides an interface to update each on by its identifier. Let me know if you have questions. :) EDIT: ...use this link instead... I had forgotten that I had an exponential increase in the input value every 10 seconds to demonstrate JS updates. This limits it.

                – user1106925
                May 10 '13 at 15:32







                You'll see that there's a primary Template constructor that does the parsing, holds the different MyCtor objects, and provides an interface to update each on by its identifier. Let me know if you have questions. :) EDIT: ...use this link instead... I had forgotten that I had an exponential increase in the input value every 10 seconds to demonstrate JS updates. This limits it.

                – user1106925
                May 10 '13 at 15:32






                2




                2





                ...fully commented version plus minor improvements.

                – user1106925
                May 10 '13 at 15:52







                ...fully commented version plus minor improvements.

                – user1106925
                May 10 '13 at 15:52















                34














                So, I decided to throw my own solution in the pot. Here is a working fiddle. Note this only runs on very modern browsers.



                What it uses



                This implementation is very modern - it requires a (very) modern browser and users two new technologies:





                • MutationObservers to detect changes in the dom (event listeners are used as well)


                • Object.observe to detect changes in the object and notifying the dom. Danger, since this answer has been written O.o has been discussed and decided against by the ECMAScript TC, consider a polyfill.


                How it works




                • On the element, put a domAttribute:objAttribute mapping - for example bind='textContent:name'

                • Read that in the dataBind function. Observe changes to both the element and the object.

                • When a change occurs - update the relevant element.


                The solution



                Here is the dataBind function, note it's just 20 lines of code and could be shorter:



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


                Here is some usage:



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


                Here is a working fiddle. Note that this solution is pretty generic. Object.observe and mutation observer shimming is available.






                share|improve this answer





















                • 1





                  I just happened to write this (es5) for fun, if anyone finds it useful - knock yourself out jsfiddle.net/P9rMm

                  – Benjamin Gruenbaum
                  Apr 19 '14 at 15:57






                • 1





                  Keep in mind that when obj.name has a setter it cannot be observed externally, but must broadcast that it has changed from within the setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - kinda throws a wrench in the works for O.o() if you want more complex, interdependent behavior using setters. Furthermore, when obj.name is not configurable, redefining it's setter (with various tricks to add notification) also is not allowed - so generics with O.o() are totally scrapped in that specific case.

                  – Nolo
                  Oct 20 '14 at 17:25






                • 7





                  Object.observe is removed from all browsers: caniuse.com/#feat=object-observe

                  – JvdBerg
                  Jun 23 '16 at 13:46






                • 1





                  A Proxy can be used instead of Object.observe, or github.com/anywhichway/proxy-observe or gist.github.com/ebidel/1b553d571f924da2da06 or the older polyfills, also on github @JvdBerg

                  – jimmont
                  Mar 18 '17 at 4:22
















                34














                So, I decided to throw my own solution in the pot. Here is a working fiddle. Note this only runs on very modern browsers.



                What it uses



                This implementation is very modern - it requires a (very) modern browser and users two new technologies:





                • MutationObservers to detect changes in the dom (event listeners are used as well)


                • Object.observe to detect changes in the object and notifying the dom. Danger, since this answer has been written O.o has been discussed and decided against by the ECMAScript TC, consider a polyfill.


                How it works




                • On the element, put a domAttribute:objAttribute mapping - for example bind='textContent:name'

                • Read that in the dataBind function. Observe changes to both the element and the object.

                • When a change occurs - update the relevant element.


                The solution



                Here is the dataBind function, note it's just 20 lines of code and could be shorter:



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


                Here is some usage:



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


                Here is a working fiddle. Note that this solution is pretty generic. Object.observe and mutation observer shimming is available.






                share|improve this answer





















                • 1





                  I just happened to write this (es5) for fun, if anyone finds it useful - knock yourself out jsfiddle.net/P9rMm

                  – Benjamin Gruenbaum
                  Apr 19 '14 at 15:57






                • 1





                  Keep in mind that when obj.name has a setter it cannot be observed externally, but must broadcast that it has changed from within the setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - kinda throws a wrench in the works for O.o() if you want more complex, interdependent behavior using setters. Furthermore, when obj.name is not configurable, redefining it's setter (with various tricks to add notification) also is not allowed - so generics with O.o() are totally scrapped in that specific case.

                  – Nolo
                  Oct 20 '14 at 17:25






                • 7





                  Object.observe is removed from all browsers: caniuse.com/#feat=object-observe

                  – JvdBerg
                  Jun 23 '16 at 13:46






                • 1





                  A Proxy can be used instead of Object.observe, or github.com/anywhichway/proxy-observe or gist.github.com/ebidel/1b553d571f924da2da06 or the older polyfills, also on github @JvdBerg

                  – jimmont
                  Mar 18 '17 at 4:22














                34












                34








                34







                So, I decided to throw my own solution in the pot. Here is a working fiddle. Note this only runs on very modern browsers.



                What it uses



                This implementation is very modern - it requires a (very) modern browser and users two new technologies:





                • MutationObservers to detect changes in the dom (event listeners are used as well)


                • Object.observe to detect changes in the object and notifying the dom. Danger, since this answer has been written O.o has been discussed and decided against by the ECMAScript TC, consider a polyfill.


                How it works




                • On the element, put a domAttribute:objAttribute mapping - for example bind='textContent:name'

                • Read that in the dataBind function. Observe changes to both the element and the object.

                • When a change occurs - update the relevant element.


                The solution



                Here is the dataBind function, note it's just 20 lines of code and could be shorter:



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


                Here is some usage:



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


                Here is a working fiddle. Note that this solution is pretty generic. Object.observe and mutation observer shimming is available.






                share|improve this answer















                So, I decided to throw my own solution in the pot. Here is a working fiddle. Note this only runs on very modern browsers.



                What it uses



                This implementation is very modern - it requires a (very) modern browser and users two new technologies:





                • MutationObservers to detect changes in the dom (event listeners are used as well)


                • Object.observe to detect changes in the object and notifying the dom. Danger, since this answer has been written O.o has been discussed and decided against by the ECMAScript TC, consider a polyfill.


                How it works




                • On the element, put a domAttribute:objAttribute mapping - for example bind='textContent:name'

                • Read that in the dataBind function. Observe changes to both the element and the object.

                • When a change occurs - update the relevant element.


                The solution



                Here is the dataBind function, note it's just 20 lines of code and could be shorter:



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


                Here is some usage:



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


                Here is a working fiddle. Note that this solution is pretty generic. Object.observe and mutation observer shimming is available.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Nov 19 '15 at 18:41

























                answered Nov 23 '13 at 22:58









                Benjamin GruenbaumBenjamin Gruenbaum

                190k63403440




                190k63403440








                • 1





                  I just happened to write this (es5) for fun, if anyone finds it useful - knock yourself out jsfiddle.net/P9rMm

                  – Benjamin Gruenbaum
                  Apr 19 '14 at 15:57






                • 1





                  Keep in mind that when obj.name has a setter it cannot be observed externally, but must broadcast that it has changed from within the setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - kinda throws a wrench in the works for O.o() if you want more complex, interdependent behavior using setters. Furthermore, when obj.name is not configurable, redefining it's setter (with various tricks to add notification) also is not allowed - so generics with O.o() are totally scrapped in that specific case.

                  – Nolo
                  Oct 20 '14 at 17:25






                • 7





                  Object.observe is removed from all browsers: caniuse.com/#feat=object-observe

                  – JvdBerg
                  Jun 23 '16 at 13:46






                • 1





                  A Proxy can be used instead of Object.observe, or github.com/anywhichway/proxy-observe or gist.github.com/ebidel/1b553d571f924da2da06 or the older polyfills, also on github @JvdBerg

                  – jimmont
                  Mar 18 '17 at 4:22














                • 1





                  I just happened to write this (es5) for fun, if anyone finds it useful - knock yourself out jsfiddle.net/P9rMm

                  – Benjamin Gruenbaum
                  Apr 19 '14 at 15:57






                • 1





                  Keep in mind that when obj.name has a setter it cannot be observed externally, but must broadcast that it has changed from within the setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - kinda throws a wrench in the works for O.o() if you want more complex, interdependent behavior using setters. Furthermore, when obj.name is not configurable, redefining it's setter (with various tricks to add notification) also is not allowed - so generics with O.o() are totally scrapped in that specific case.

                  – Nolo
                  Oct 20 '14 at 17:25






                • 7





                  Object.observe is removed from all browsers: caniuse.com/#feat=object-observe

                  – JvdBerg
                  Jun 23 '16 at 13:46






                • 1





                  A Proxy can be used instead of Object.observe, or github.com/anywhichway/proxy-observe or gist.github.com/ebidel/1b553d571f924da2da06 or the older polyfills, also on github @JvdBerg

                  – jimmont
                  Mar 18 '17 at 4:22








                1




                1





                I just happened to write this (es5) for fun, if anyone finds it useful - knock yourself out jsfiddle.net/P9rMm

                – Benjamin Gruenbaum
                Apr 19 '14 at 15:57





                I just happened to write this (es5) for fun, if anyone finds it useful - knock yourself out jsfiddle.net/P9rMm

                – Benjamin Gruenbaum
                Apr 19 '14 at 15:57




                1




                1





                Keep in mind that when obj.name has a setter it cannot be observed externally, but must broadcast that it has changed from within the setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - kinda throws a wrench in the works for O.o() if you want more complex, interdependent behavior using setters. Furthermore, when obj.name is not configurable, redefining it's setter (with various tricks to add notification) also is not allowed - so generics with O.o() are totally scrapped in that specific case.

                – Nolo
                Oct 20 '14 at 17:25





                Keep in mind that when obj.name has a setter it cannot be observed externally, but must broadcast that it has changed from within the setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - kinda throws a wrench in the works for O.o() if you want more complex, interdependent behavior using setters. Furthermore, when obj.name is not configurable, redefining it's setter (with various tricks to add notification) also is not allowed - so generics with O.o() are totally scrapped in that specific case.

                – Nolo
                Oct 20 '14 at 17:25




                7




                7





                Object.observe is removed from all browsers: caniuse.com/#feat=object-observe

                – JvdBerg
                Jun 23 '16 at 13:46





                Object.observe is removed from all browsers: caniuse.com/#feat=object-observe

                – JvdBerg
                Jun 23 '16 at 13:46




                1




                1





                A Proxy can be used instead of Object.observe, or github.com/anywhichway/proxy-observe or gist.github.com/ebidel/1b553d571f924da2da06 or the older polyfills, also on github @JvdBerg

                – jimmont
                Mar 18 '17 at 4:22





                A Proxy can be used instead of Object.observe, or github.com/anywhichway/proxy-observe or gist.github.com/ebidel/1b553d571f924da2da06 or the older polyfills, also on github @JvdBerg

                – jimmont
                Mar 18 '17 at 4:22











                24














                I'd like to add to my preposter. I suggest a slightly different approach that will allow you to simply assign a new value to your object without using a method. It must be noted though that this is not supported by especially older browsers and IE9 still requires use of a different interface.



                Most notably is that my approach does not make use of events.



                Getters and Setters



                My proposal makes use of the relatively young feature of getters and setters, particularly setters only. Generally speaking, mutators allow us to "customize" the behavior of how certain properties are assigned a value and retrieved.



                One implementation I'll be using here is the Object.defineProperty method. It works in FireFox, GoogleChrome and - I think - IE9. Haven't tested other browsers, but since this is theory only...



                Anyways, it accepts three parameters. The first parameter being the object that you wish to define a new property for, the second a string resembling the the name of the new property and the last a "descriptor object" providing information on the behavior of the new property.



                Two particularly interesting descriptors are get and set. An example would look something like the following. Note that using these two prohibits the use of the other 4 descriptors.



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


                Now making use of this becomes slightly different:



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


                I want to emphasize that this only works for modern browsers.



                Working fiddle: http://jsfiddle.net/Derija93/RkTMD/1/






                share|improve this answer





















                • 1





                  If only we had Harmony Proxy objects :) Setters do seem like a nice idea, but wouldn't that require us to modify the actual objects? Also, on a side note - Object.create could be used here (again, assuming modern browser that allowed for the second parameter). Also, the setter/getter could be used to 'project' a different value to the object and the DOM element :) . I'm wondering if you have any insights on templating too, that seems like a real challenge here, especially to structure nicely :)

                  – Benjamin Gruenbaum
                  May 10 '13 at 15:00











                • Just like my preposter, I too do not work a lot with client-side templating engines, sorry. :( But what do you mean by modify the actual objects? And I'd like to understand your thoughts of how you got to understand that the setter/getter could be used to .... The getters/setters here are used for nothing but redirecting all input to and retrievals from the object to the DOM element, basically like a Proxy, like you said. ;) I understood the challenge to be to keep two distinct properties synchronized. My method eliminates one of both.

                  – Kiruse
                  May 10 '13 at 15:10











                • A Proxy would eliminate the need to use getters/setters, you could bind elements without knowing what properties they have. What I meant, is that the getters can change more than bindTo.value they can contain logic (and maybe even a template). The question is how to maintain this sort of bidirectional binding with a template in mind? Lets say I'm mapping my object to a form, I'd like to maintain both the element and the form synced and I'm wondering how I'd go on about that sort of thing. You can check out how that works on knockout learn.knockoutjs.com/#/?tutorial=intro for example

                  – Benjamin Gruenbaum
                  May 10 '13 at 15:24











                • @BenjaminGruenbaum Gotcha. I'll give it a look.

                  – Kiruse
                  May 10 '13 at 15:33











                • @BenjaminGruenbaum I see what you're trying to understand. Setting all this up with templates in mind turns out to be a little more difficult. I'll be working on this script for a while (and continuously rebase it). But for now, I'm taking a break. I actually don't quite have the time for this.

                  – Kiruse
                  May 10 '13 at 16:41
















                24














                I'd like to add to my preposter. I suggest a slightly different approach that will allow you to simply assign a new value to your object without using a method. It must be noted though that this is not supported by especially older browsers and IE9 still requires use of a different interface.



                Most notably is that my approach does not make use of events.



                Getters and Setters



                My proposal makes use of the relatively young feature of getters and setters, particularly setters only. Generally speaking, mutators allow us to "customize" the behavior of how certain properties are assigned a value and retrieved.



                One implementation I'll be using here is the Object.defineProperty method. It works in FireFox, GoogleChrome and - I think - IE9. Haven't tested other browsers, but since this is theory only...



                Anyways, it accepts three parameters. The first parameter being the object that you wish to define a new property for, the second a string resembling the the name of the new property and the last a "descriptor object" providing information on the behavior of the new property.



                Two particularly interesting descriptors are get and set. An example would look something like the following. Note that using these two prohibits the use of the other 4 descriptors.



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


                Now making use of this becomes slightly different:



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


                I want to emphasize that this only works for modern browsers.



                Working fiddle: http://jsfiddle.net/Derija93/RkTMD/1/






                share|improve this answer





















                • 1





                  If only we had Harmony Proxy objects :) Setters do seem like a nice idea, but wouldn't that require us to modify the actual objects? Also, on a side note - Object.create could be used here (again, assuming modern browser that allowed for the second parameter). Also, the setter/getter could be used to 'project' a different value to the object and the DOM element :) . I'm wondering if you have any insights on templating too, that seems like a real challenge here, especially to structure nicely :)

                  – Benjamin Gruenbaum
                  May 10 '13 at 15:00











                • Just like my preposter, I too do not work a lot with client-side templating engines, sorry. :( But what do you mean by modify the actual objects? And I'd like to understand your thoughts of how you got to understand that the setter/getter could be used to .... The getters/setters here are used for nothing but redirecting all input to and retrievals from the object to the DOM element, basically like a Proxy, like you said. ;) I understood the challenge to be to keep two distinct properties synchronized. My method eliminates one of both.

                  – Kiruse
                  May 10 '13 at 15:10











                • A Proxy would eliminate the need to use getters/setters, you could bind elements without knowing what properties they have. What I meant, is that the getters can change more than bindTo.value they can contain logic (and maybe even a template). The question is how to maintain this sort of bidirectional binding with a template in mind? Lets say I'm mapping my object to a form, I'd like to maintain both the element and the form synced and I'm wondering how I'd go on about that sort of thing. You can check out how that works on knockout learn.knockoutjs.com/#/?tutorial=intro for example

                  – Benjamin Gruenbaum
                  May 10 '13 at 15:24











                • @BenjaminGruenbaum Gotcha. I'll give it a look.

                  – Kiruse
                  May 10 '13 at 15:33











                • @BenjaminGruenbaum I see what you're trying to understand. Setting all this up with templates in mind turns out to be a little more difficult. I'll be working on this script for a while (and continuously rebase it). But for now, I'm taking a break. I actually don't quite have the time for this.

                  – Kiruse
                  May 10 '13 at 16:41














                24












                24








                24







                I'd like to add to my preposter. I suggest a slightly different approach that will allow you to simply assign a new value to your object without using a method. It must be noted though that this is not supported by especially older browsers and IE9 still requires use of a different interface.



                Most notably is that my approach does not make use of events.



                Getters and Setters



                My proposal makes use of the relatively young feature of getters and setters, particularly setters only. Generally speaking, mutators allow us to "customize" the behavior of how certain properties are assigned a value and retrieved.



                One implementation I'll be using here is the Object.defineProperty method. It works in FireFox, GoogleChrome and - I think - IE9. Haven't tested other browsers, but since this is theory only...



                Anyways, it accepts three parameters. The first parameter being the object that you wish to define a new property for, the second a string resembling the the name of the new property and the last a "descriptor object" providing information on the behavior of the new property.



                Two particularly interesting descriptors are get and set. An example would look something like the following. Note that using these two prohibits the use of the other 4 descriptors.



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


                Now making use of this becomes slightly different:



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


                I want to emphasize that this only works for modern browsers.



                Working fiddle: http://jsfiddle.net/Derija93/RkTMD/1/






                share|improve this answer















                I'd like to add to my preposter. I suggest a slightly different approach that will allow you to simply assign a new value to your object without using a method. It must be noted though that this is not supported by especially older browsers and IE9 still requires use of a different interface.



                Most notably is that my approach does not make use of events.



                Getters and Setters



                My proposal makes use of the relatively young feature of getters and setters, particularly setters only. Generally speaking, mutators allow us to "customize" the behavior of how certain properties are assigned a value and retrieved.



                One implementation I'll be using here is the Object.defineProperty method. It works in FireFox, GoogleChrome and - I think - IE9. Haven't tested other browsers, but since this is theory only...



                Anyways, it accepts three parameters. The first parameter being the object that you wish to define a new property for, the second a string resembling the the name of the new property and the last a "descriptor object" providing information on the behavior of the new property.



                Two particularly interesting descriptors are get and set. An example would look something like the following. Note that using these two prohibits the use of the other 4 descriptors.



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


                Now making use of this becomes slightly different:



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


                I want to emphasize that this only works for modern browsers.



                Working fiddle: http://jsfiddle.net/Derija93/RkTMD/1/







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Mar 1 '16 at 10:37

























                answered May 10 '13 at 14:50









                KiruseKiruse

                1,400918




                1,400918








                • 1





                  If only we had Harmony Proxy objects :) Setters do seem like a nice idea, but wouldn't that require us to modify the actual objects? Also, on a side note - Object.create could be used here (again, assuming modern browser that allowed for the second parameter). Also, the setter/getter could be used to 'project' a different value to the object and the DOM element :) . I'm wondering if you have any insights on templating too, that seems like a real challenge here, especially to structure nicely :)

                  – Benjamin Gruenbaum
                  May 10 '13 at 15:00











                • Just like my preposter, I too do not work a lot with client-side templating engines, sorry. :( But what do you mean by modify the actual objects? And I'd like to understand your thoughts of how you got to understand that the setter/getter could be used to .... The getters/setters here are used for nothing but redirecting all input to and retrievals from the object to the DOM element, basically like a Proxy, like you said. ;) I understood the challenge to be to keep two distinct properties synchronized. My method eliminates one of both.

                  – Kiruse
                  May 10 '13 at 15:10











                • A Proxy would eliminate the need to use getters/setters, you could bind elements without knowing what properties they have. What I meant, is that the getters can change more than bindTo.value they can contain logic (and maybe even a template). The question is how to maintain this sort of bidirectional binding with a template in mind? Lets say I'm mapping my object to a form, I'd like to maintain both the element and the form synced and I'm wondering how I'd go on about that sort of thing. You can check out how that works on knockout learn.knockoutjs.com/#/?tutorial=intro for example

                  – Benjamin Gruenbaum
                  May 10 '13 at 15:24











                • @BenjaminGruenbaum Gotcha. I'll give it a look.

                  – Kiruse
                  May 10 '13 at 15:33











                • @BenjaminGruenbaum I see what you're trying to understand. Setting all this up with templates in mind turns out to be a little more difficult. I'll be working on this script for a while (and continuously rebase it). But for now, I'm taking a break. I actually don't quite have the time for this.

                  – Kiruse
                  May 10 '13 at 16:41














                • 1





                  If only we had Harmony Proxy objects :) Setters do seem like a nice idea, but wouldn't that require us to modify the actual objects? Also, on a side note - Object.create could be used here (again, assuming modern browser that allowed for the second parameter). Also, the setter/getter could be used to 'project' a different value to the object and the DOM element :) . I'm wondering if you have any insights on templating too, that seems like a real challenge here, especially to structure nicely :)

                  – Benjamin Gruenbaum
                  May 10 '13 at 15:00











                • Just like my preposter, I too do not work a lot with client-side templating engines, sorry. :( But what do you mean by modify the actual objects? And I'd like to understand your thoughts of how you got to understand that the setter/getter could be used to .... The getters/setters here are used for nothing but redirecting all input to and retrievals from the object to the DOM element, basically like a Proxy, like you said. ;) I understood the challenge to be to keep two distinct properties synchronized. My method eliminates one of both.

                  – Kiruse
                  May 10 '13 at 15:10











                • A Proxy would eliminate the need to use getters/setters, you could bind elements without knowing what properties they have. What I meant, is that the getters can change more than bindTo.value they can contain logic (and maybe even a template). The question is how to maintain this sort of bidirectional binding with a template in mind? Lets say I'm mapping my object to a form, I'd like to maintain both the element and the form synced and I'm wondering how I'd go on about that sort of thing. You can check out how that works on knockout learn.knockoutjs.com/#/?tutorial=intro for example

                  – Benjamin Gruenbaum
                  May 10 '13 at 15:24











                • @BenjaminGruenbaum Gotcha. I'll give it a look.

                  – Kiruse
                  May 10 '13 at 15:33











                • @BenjaminGruenbaum I see what you're trying to understand. Setting all this up with templates in mind turns out to be a little more difficult. I'll be working on this script for a while (and continuously rebase it). But for now, I'm taking a break. I actually don't quite have the time for this.

                  – Kiruse
                  May 10 '13 at 16:41








                1




                1





                If only we had Harmony Proxy objects :) Setters do seem like a nice idea, but wouldn't that require us to modify the actual objects? Also, on a side note - Object.create could be used here (again, assuming modern browser that allowed for the second parameter). Also, the setter/getter could be used to 'project' a different value to the object and the DOM element :) . I'm wondering if you have any insights on templating too, that seems like a real challenge here, especially to structure nicely :)

                – Benjamin Gruenbaum
                May 10 '13 at 15:00





                If only we had Harmony Proxy objects :) Setters do seem like a nice idea, but wouldn't that require us to modify the actual objects? Also, on a side note - Object.create could be used here (again, assuming modern browser that allowed for the second parameter). Also, the setter/getter could be used to 'project' a different value to the object and the DOM element :) . I'm wondering if you have any insights on templating too, that seems like a real challenge here, especially to structure nicely :)

                – Benjamin Gruenbaum
                May 10 '13 at 15:00













                Just like my preposter, I too do not work a lot with client-side templating engines, sorry. :( But what do you mean by modify the actual objects? And I'd like to understand your thoughts of how you got to understand that the setter/getter could be used to .... The getters/setters here are used for nothing but redirecting all input to and retrievals from the object to the DOM element, basically like a Proxy, like you said. ;) I understood the challenge to be to keep two distinct properties synchronized. My method eliminates one of both.

                – Kiruse
                May 10 '13 at 15:10





                Just like my preposter, I too do not work a lot with client-side templating engines, sorry. :( But what do you mean by modify the actual objects? And I'd like to understand your thoughts of how you got to understand that the setter/getter could be used to .... The getters/setters here are used for nothing but redirecting all input to and retrievals from the object to the DOM element, basically like a Proxy, like you said. ;) I understood the challenge to be to keep two distinct properties synchronized. My method eliminates one of both.

                – Kiruse
                May 10 '13 at 15:10













                A Proxy would eliminate the need to use getters/setters, you could bind elements without knowing what properties they have. What I meant, is that the getters can change more than bindTo.value they can contain logic (and maybe even a template). The question is how to maintain this sort of bidirectional binding with a template in mind? Lets say I'm mapping my object to a form, I'd like to maintain both the element and the form synced and I'm wondering how I'd go on about that sort of thing. You can check out how that works on knockout learn.knockoutjs.com/#/?tutorial=intro for example

                – Benjamin Gruenbaum
                May 10 '13 at 15:24





                A Proxy would eliminate the need to use getters/setters, you could bind elements without knowing what properties they have. What I meant, is that the getters can change more than bindTo.value they can contain logic (and maybe even a template). The question is how to maintain this sort of bidirectional binding with a template in mind? Lets say I'm mapping my object to a form, I'd like to maintain both the element and the form synced and I'm wondering how I'd go on about that sort of thing. You can check out how that works on knockout learn.knockoutjs.com/#/?tutorial=intro for example

                – Benjamin Gruenbaum
                May 10 '13 at 15:24













                @BenjaminGruenbaum Gotcha. I'll give it a look.

                – Kiruse
                May 10 '13 at 15:33





                @BenjaminGruenbaum Gotcha. I'll give it a look.

                – Kiruse
                May 10 '13 at 15:33













                @BenjaminGruenbaum I see what you're trying to understand. Setting all this up with templates in mind turns out to be a little more difficult. I'll be working on this script for a while (and continuously rebase it). But for now, I'm taking a break. I actually don't quite have the time for this.

                – Kiruse
                May 10 '13 at 16:41





                @BenjaminGruenbaum I see what you're trying to understand. Setting all this up with templates in mind turns out to be a little more difficult. I'll be working on this script for a while (and continuously rebase it). But for now, I'm taking a break. I actually don't quite have the time for this.

                – Kiruse
                May 10 '13 at 16:41











                7














                I think my answer will be more technical, but not different as the others present the same thing using different techniques.

                So, first things first, the solution to this problem is the use of a design pattern known as "observer", it let's you decouple your data from your presentation, making the change in one thing be broadcasted to their listeners, but in this case it's made two-way.



                For the DOM to JS way



                To bind the data from the DOM to the js object you may add markup in the form of data attributes (or classes if you need compatibility), like this:



                <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=""/>


                This way it can be accessed via js using querySelectorAll (or the old friend getElementsByClassName for compatibility).



                Now you can bind the event listening to the changes in to ways: one listener per object or one big listener to the container/document. Binding to the document/container will trigger the event for every change made in it or it's child, it willhave a smaller memory footprint but will spawn event calls.

                The code will look something like this:



                //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);


                For the JS do DOM way



                You will need two things: one meta-object that will hold the references of witch DOM element is binded to each js object/attribute and a way to listen to changes in objects. It is basically the same way: you have to have a way to listen to changes in the object and then bind it to the DOM node, as your object "can't have" metadata you will need another object that holds metadata in a way that the property name maps to the metadata object's properties.
                The code will be something like this:



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


                I hope that i was of help.






                share|improve this answer
























                • isn't there comparability issue with using the .observer?

                  – Mohsen Shakiba
                  Jul 6 '15 at 6:15











                • for now it needs a shim or polyfill to Object.observe as support is presente only in chrome for now. caniuse.com/#feat=object-observe

                  – madcampos
                  Jul 30 '15 at 5:24








                • 8





                  Object.observe is dead. Just thought I'd note that here.

                  – Benjamin Gruenbaum
                  Oct 29 '15 at 8:57











                • @BenjaminGruenbaum What's the correct thing to use now, since this is dead?

                  – johnny
                  Mar 29 '16 at 19:58






                • 1





                  @johnny if i'm not wrong it would be proxy traps as they allow to a more granular control of what can i do with an object, but i have to investigate that.

                  – madcampos
                  Mar 31 '16 at 14:53
















                7














                I think my answer will be more technical, but not different as the others present the same thing using different techniques.

                So, first things first, the solution to this problem is the use of a design pattern known as "observer", it let's you decouple your data from your presentation, making the change in one thing be broadcasted to their listeners, but in this case it's made two-way.



                For the DOM to JS way



                To bind the data from the DOM to the js object you may add markup in the form of data attributes (or classes if you need compatibility), like this:



                <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=""/>


                This way it can be accessed via js using querySelectorAll (or the old friend getElementsByClassName for compatibility).



                Now you can bind the event listening to the changes in to ways: one listener per object or one big listener to the container/document. Binding to the document/container will trigger the event for every change made in it or it's child, it willhave a smaller memory footprint but will spawn event calls.

                The code will look something like this:



                //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);


                For the JS do DOM way



                You will need two things: one meta-object that will hold the references of witch DOM element is binded to each js object/attribute and a way to listen to changes in objects. It is basically the same way: you have to have a way to listen to changes in the object and then bind it to the DOM node, as your object "can't have" metadata you will need another object that holds metadata in a way that the property name maps to the metadata object's properties.
                The code will be something like this:



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


                I hope that i was of help.






                share|improve this answer
























                • isn't there comparability issue with using the .observer?

                  – Mohsen Shakiba
                  Jul 6 '15 at 6:15











                • for now it needs a shim or polyfill to Object.observe as support is presente only in chrome for now. caniuse.com/#feat=object-observe

                  – madcampos
                  Jul 30 '15 at 5:24








                • 8





                  Object.observe is dead. Just thought I'd note that here.

                  – Benjamin Gruenbaum
                  Oct 29 '15 at 8:57











                • @BenjaminGruenbaum What's the correct thing to use now, since this is dead?

                  – johnny
                  Mar 29 '16 at 19:58






                • 1





                  @johnny if i'm not wrong it would be proxy traps as they allow to a more granular control of what can i do with an object, but i have to investigate that.

                  – madcampos
                  Mar 31 '16 at 14:53














                7












                7








                7







                I think my answer will be more technical, but not different as the others present the same thing using different techniques.

                So, first things first, the solution to this problem is the use of a design pattern known as "observer", it let's you decouple your data from your presentation, making the change in one thing be broadcasted to their listeners, but in this case it's made two-way.



                For the DOM to JS way



                To bind the data from the DOM to the js object you may add markup in the form of data attributes (or classes if you need compatibility), like this:



                <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=""/>


                This way it can be accessed via js using querySelectorAll (or the old friend getElementsByClassName for compatibility).



                Now you can bind the event listening to the changes in to ways: one listener per object or one big listener to the container/document. Binding to the document/container will trigger the event for every change made in it or it's child, it willhave a smaller memory footprint but will spawn event calls.

                The code will look something like this:



                //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);


                For the JS do DOM way



                You will need two things: one meta-object that will hold the references of witch DOM element is binded to each js object/attribute and a way to listen to changes in objects. It is basically the same way: you have to have a way to listen to changes in the object and then bind it to the DOM node, as your object "can't have" metadata you will need another object that holds metadata in a way that the property name maps to the metadata object's properties.
                The code will be something like this:



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


                I hope that i was of help.






                share|improve this answer













                I think my answer will be more technical, but not different as the others present the same thing using different techniques.

                So, first things first, the solution to this problem is the use of a design pattern known as "observer", it let's you decouple your data from your presentation, making the change in one thing be broadcasted to their listeners, but in this case it's made two-way.



                For the DOM to JS way



                To bind the data from the DOM to the js object you may add markup in the form of data attributes (or classes if you need compatibility), like this:



                <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=""/>


                This way it can be accessed via js using querySelectorAll (or the old friend getElementsByClassName for compatibility).



                Now you can bind the event listening to the changes in to ways: one listener per object or one big listener to the container/document. Binding to the document/container will trigger the event for every change made in it or it's child, it willhave a smaller memory footprint but will spawn event calls.

                The code will look something like this:



                //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);


                For the JS do DOM way



                You will need two things: one meta-object that will hold the references of witch DOM element is binded to each js object/attribute and a way to listen to changes in objects. It is basically the same way: you have to have a way to listen to changes in the object and then bind it to the DOM node, as your object "can't have" metadata you will need another object that holds metadata in a way that the property name maps to the metadata object's properties.
                The code will be something like this:



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


                I hope that i was of help.







                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Dec 2 '13 at 4:47









                madcamposmadcampos

                33836




                33836













                • isn't there comparability issue with using the .observer?

                  – Mohsen Shakiba
                  Jul 6 '15 at 6:15











                • for now it needs a shim or polyfill to Object.observe as support is presente only in chrome for now. caniuse.com/#feat=object-observe

                  – madcampos
                  Jul 30 '15 at 5:24








                • 8





                  Object.observe is dead. Just thought I'd note that here.

                  – Benjamin Gruenbaum
                  Oct 29 '15 at 8:57











                • @BenjaminGruenbaum What's the correct thing to use now, since this is dead?

                  – johnny
                  Mar 29 '16 at 19:58






                • 1





                  @johnny if i'm not wrong it would be proxy traps as they allow to a more granular control of what can i do with an object, but i have to investigate that.

                  – madcampos
                  Mar 31 '16 at 14:53



















                • isn't there comparability issue with using the .observer?

                  – Mohsen Shakiba
                  Jul 6 '15 at 6:15











                • for now it needs a shim or polyfill to Object.observe as support is presente only in chrome for now. caniuse.com/#feat=object-observe

                  – madcampos
                  Jul 30 '15 at 5:24








                • 8





                  Object.observe is dead. Just thought I'd note that here.

                  – Benjamin Gruenbaum
                  Oct 29 '15 at 8:57











                • @BenjaminGruenbaum What's the correct thing to use now, since this is dead?

                  – johnny
                  Mar 29 '16 at 19:58






                • 1





                  @johnny if i'm not wrong it would be proxy traps as they allow to a more granular control of what can i do with an object, but i have to investigate that.

                  – madcampos
                  Mar 31 '16 at 14:53

















                isn't there comparability issue with using the .observer?

                – Mohsen Shakiba
                Jul 6 '15 at 6:15





                isn't there comparability issue with using the .observer?

                – Mohsen Shakiba
                Jul 6 '15 at 6:15













                for now it needs a shim or polyfill to Object.observe as support is presente only in chrome for now. caniuse.com/#feat=object-observe

                – madcampos
                Jul 30 '15 at 5:24







                for now it needs a shim or polyfill to Object.observe as support is presente only in chrome for now. caniuse.com/#feat=object-observe

                – madcampos
                Jul 30 '15 at 5:24






                8




                8





                Object.observe is dead. Just thought I'd note that here.

                – Benjamin Gruenbaum
                Oct 29 '15 at 8:57





                Object.observe is dead. Just thought I'd note that here.

                – Benjamin Gruenbaum
                Oct 29 '15 at 8:57













                @BenjaminGruenbaum What's the correct thing to use now, since this is dead?

                – johnny
                Mar 29 '16 at 19:58





                @BenjaminGruenbaum What's the correct thing to use now, since this is dead?

                – johnny
                Mar 29 '16 at 19:58




                1




                1





                @johnny if i'm not wrong it would be proxy traps as they allow to a more granular control of what can i do with an object, but i have to investigate that.

                – madcampos
                Mar 31 '16 at 14:53





                @johnny if i'm not wrong it would be proxy traps as they allow to a more granular control of what can i do with an object, but i have to investigate that.

                – madcampos
                Mar 31 '16 at 14:53











                7














                Yesterday, I started to write my own way to bind data.



                It's very funny to play with it.



                I think it's beautiful and very useful. At least on my tests using firefox and chrome, Edge must works too. Not sure about others, but if they support Proxy, I think it will work.



                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' />


                Here is the 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);
                }
                }
                }
                }
                })();


                Then, to set, just:



                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'
                })


                For now, I've just added the HTMLInputElement value bind.



                Let me know if you know how to improve it.






                share|improve this answer






























                  7














                  Yesterday, I started to write my own way to bind data.



                  It's very funny to play with it.



                  I think it's beautiful and very useful. At least on my tests using firefox and chrome, Edge must works too. Not sure about others, but if they support Proxy, I think it will work.



                  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' />


                  Here is the 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);
                  }
                  }
                  }
                  }
                  })();


                  Then, to set, just:



                  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'
                  })


                  For now, I've just added the HTMLInputElement value bind.



                  Let me know if you know how to improve it.






                  share|improve this answer




























                    7












                    7








                    7







                    Yesterday, I started to write my own way to bind data.



                    It's very funny to play with it.



                    I think it's beautiful and very useful. At least on my tests using firefox and chrome, Edge must works too. Not sure about others, but if they support Proxy, I think it will work.



                    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' />


                    Here is the 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);
                    }
                    }
                    }
                    }
                    })();


                    Then, to set, just:



                    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'
                    })


                    For now, I've just added the HTMLInputElement value bind.



                    Let me know if you know how to improve it.






                    share|improve this answer















                    Yesterday, I started to write my own way to bind data.



                    It's very funny to play with it.



                    I think it's beautiful and very useful. At least on my tests using firefox and chrome, Edge must works too. Not sure about others, but if they support Proxy, I think it will work.



                    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' />


                    Here is the 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);
                    }
                    }
                    }
                    }
                    })();


                    Then, to set, just:



                    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'
                    })


                    For now, I've just added the HTMLInputElement value bind.



                    Let me know if you know how to improve it.







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Nov 25 '18 at 8:25









                    Lonnie Best

                    2,79153145




                    2,79153145










                    answered Jun 16 '16 at 19:30









                    tonton

                    1,1381721




                    1,1381721























                        6














                        There is a very simple barebones implementation of 2-way data-binding in this link "Easy Two-Way Data Binding in JavaScript"



                        The previous link along with ideas from knockoutjs, backbone.js and agility.js, led to this light-weight and fast MVVM framework, ModelView.js based on jQuery which plays nicely with jQuery and of which i am the humble (or maybe not so humble) author.



                        Reproducing sample code below (from blog post link):



                        Sample code for 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;
                        }



                        For what concerns the JavaScript object, a minimal implementation of a
                        User model for the sake of this experiment could be the following:




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



                        Now, whenever we want to bind a model’s property to a piece of UI we
                        just have to set an appropriate data attribute on the corresponding
                        HTML element:




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

                        <!-- html -->
                        <input type="number" data-bind-123="name" />





                        share|improve this answer


























                        • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.

                          – Sam Hanley
                          Apr 13 '15 at 13:07











                        • @sphanley, noted, i'll probably update when i have more time, as it a rather long code for an answer post

                          – Nikos M.
                          Apr 13 '15 at 19:24











                        • @sphanley, reproduced sample code on answer from referenced link (although i thinbk this creates duplicarte content most of the time, anyway)

                          – Nikos M.
                          Apr 13 '15 at 19:32






                        • 1





                          It definitely does create duplicate content, but that's the point - blog links can often break with time, and by duplicating the relevant content here it ensures that it will be available and useful to future readers. The answer looks great now!

                          – Sam Hanley
                          Apr 14 '15 at 12:33


















                        6














                        There is a very simple barebones implementation of 2-way data-binding in this link "Easy Two-Way Data Binding in JavaScript"



                        The previous link along with ideas from knockoutjs, backbone.js and agility.js, led to this light-weight and fast MVVM framework, ModelView.js based on jQuery which plays nicely with jQuery and of which i am the humble (or maybe not so humble) author.



                        Reproducing sample code below (from blog post link):



                        Sample code for 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;
                        }



                        For what concerns the JavaScript object, a minimal implementation of a
                        User model for the sake of this experiment could be the following:




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



                        Now, whenever we want to bind a model’s property to a piece of UI we
                        just have to set an appropriate data attribute on the corresponding
                        HTML element:




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

                        <!-- html -->
                        <input type="number" data-bind-123="name" />





                        share|improve this answer


























                        • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.

                          – Sam Hanley
                          Apr 13 '15 at 13:07











                        • @sphanley, noted, i'll probably update when i have more time, as it a rather long code for an answer post

                          – Nikos M.
                          Apr 13 '15 at 19:24











                        • @sphanley, reproduced sample code on answer from referenced link (although i thinbk this creates duplicarte content most of the time, anyway)

                          – Nikos M.
                          Apr 13 '15 at 19:32






                        • 1





                          It definitely does create duplicate content, but that's the point - blog links can often break with time, and by duplicating the relevant content here it ensures that it will be available and useful to future readers. The answer looks great now!

                          – Sam Hanley
                          Apr 14 '15 at 12:33
















                        6












                        6








                        6







                        There is a very simple barebones implementation of 2-way data-binding in this link "Easy Two-Way Data Binding in JavaScript"



                        The previous link along with ideas from knockoutjs, backbone.js and agility.js, led to this light-weight and fast MVVM framework, ModelView.js based on jQuery which plays nicely with jQuery and of which i am the humble (or maybe not so humble) author.



                        Reproducing sample code below (from blog post link):



                        Sample code for 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;
                        }



                        For what concerns the JavaScript object, a minimal implementation of a
                        User model for the sake of this experiment could be the following:




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



                        Now, whenever we want to bind a model’s property to a piece of UI we
                        just have to set an appropriate data attribute on the corresponding
                        HTML element:




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

                        <!-- html -->
                        <input type="number" data-bind-123="name" />





                        share|improve this answer















                        There is a very simple barebones implementation of 2-way data-binding in this link "Easy Two-Way Data Binding in JavaScript"



                        The previous link along with ideas from knockoutjs, backbone.js and agility.js, led to this light-weight and fast MVVM framework, ModelView.js based on jQuery which plays nicely with jQuery and of which i am the humble (or maybe not so humble) author.



                        Reproducing sample code below (from blog post link):



                        Sample code for 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;
                        }



                        For what concerns the JavaScript object, a minimal implementation of a
                        User model for the sake of this experiment could be the following:




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



                        Now, whenever we want to bind a model’s property to a piece of UI we
                        just have to set an appropriate data attribute on the corresponding
                        HTML element:




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

                        <!-- html -->
                        <input type="number" data-bind-123="name" />






                        share|improve this answer














                        share|improve this answer



                        share|improve this answer








                        edited Apr 13 '15 at 19:36

























                        answered May 12 '14 at 20:59









                        Nikos M.Nikos M.

                        4,54521825




                        4,54521825













                        • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.

                          – Sam Hanley
                          Apr 13 '15 at 13:07











                        • @sphanley, noted, i'll probably update when i have more time, as it a rather long code for an answer post

                          – Nikos M.
                          Apr 13 '15 at 19:24











                        • @sphanley, reproduced sample code on answer from referenced link (although i thinbk this creates duplicarte content most of the time, anyway)

                          – Nikos M.
                          Apr 13 '15 at 19:32






                        • 1





                          It definitely does create duplicate content, but that's the point - blog links can often break with time, and by duplicating the relevant content here it ensures that it will be available and useful to future readers. The answer looks great now!

                          – Sam Hanley
                          Apr 14 '15 at 12:33





















                        • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.

                          – Sam Hanley
                          Apr 13 '15 at 13:07











                        • @sphanley, noted, i'll probably update when i have more time, as it a rather long code for an answer post

                          – Nikos M.
                          Apr 13 '15 at 19:24











                        • @sphanley, reproduced sample code on answer from referenced link (although i thinbk this creates duplicarte content most of the time, anyway)

                          – Nikos M.
                          Apr 13 '15 at 19:32






                        • 1





                          It definitely does create duplicate content, but that's the point - blog links can often break with time, and by duplicating the relevant content here it ensures that it will be available and useful to future readers. The answer looks great now!

                          – Sam Hanley
                          Apr 14 '15 at 12:33



















                        While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.

                        – Sam Hanley
                        Apr 13 '15 at 13:07





                        While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.

                        – Sam Hanley
                        Apr 13 '15 at 13:07













                        @sphanley, noted, i'll probably update when i have more time, as it a rather long code for an answer post

                        – Nikos M.
                        Apr 13 '15 at 19:24





                        @sphanley, noted, i'll probably update when i have more time, as it a rather long code for an answer post

                        – Nikos M.
                        Apr 13 '15 at 19:24













                        @sphanley, reproduced sample code on answer from referenced link (although i thinbk this creates duplicarte content most of the time, anyway)

                        – Nikos M.
                        Apr 13 '15 at 19:32





                        @sphanley, reproduced sample code on answer from referenced link (although i thinbk this creates duplicarte content most of the time, anyway)

                        – Nikos M.
                        Apr 13 '15 at 19:32




                        1




                        1





                        It definitely does create duplicate content, but that's the point - blog links can often break with time, and by duplicating the relevant content here it ensures that it will be available and useful to future readers. The answer looks great now!

                        – Sam Hanley
                        Apr 14 '15 at 12:33







                        It definitely does create duplicate content, but that's the point - blog links can often break with time, and by duplicating the relevant content here it ensures that it will be available and useful to future readers. The answer looks great now!

                        – Sam Hanley
                        Apr 14 '15 at 12:33













                        3














                        Bind any html input



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


                        define two functions:



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


                        use the functions:



                        var myObject = proxify('element-to-bind')
                        bindValue(myObject);





                        share|improve this answer






























                          3














                          Bind any html input



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


                          define two functions:



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


                          use the functions:



                          var myObject = proxify('element-to-bind')
                          bindValue(myObject);





                          share|improve this answer




























                            3












                            3








                            3







                            Bind any html input



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


                            define two functions:



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


                            use the functions:



                            var myObject = proxify('element-to-bind')
                            bindValue(myObject);





                            share|improve this answer















                            Bind any html input



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


                            define two functions:



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


                            use the functions:



                            var myObject = proxify('element-to-bind')
                            bindValue(myObject);






                            share|improve this answer














                            share|improve this answer



                            share|improve this answer








                            edited Jul 2 '17 at 23:31

























                            answered Jul 2 '17 at 11:01









                            o-t-wo-t-w

                            334212




                            334212























                                1














                                Changing an element's value can trigger a DOM event. Listeners that respond to events can be used to implement data binding in JavaScript.



                                For example:



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


                                Here is code and a demo that shows how DOM elements can be bound with each other or with a JavaScript object.






                                share|improve this answer






























                                  1














                                  Changing an element's value can trigger a DOM event. Listeners that respond to events can be used to implement data binding in JavaScript.



                                  For example:



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


                                  Here is code and a demo that shows how DOM elements can be bound with each other or with a JavaScript object.






                                  share|improve this answer




























                                    1












                                    1








                                    1







                                    Changing an element's value can trigger a DOM event. Listeners that respond to events can be used to implement data binding in JavaScript.



                                    For example:



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


                                    Here is code and a demo that shows how DOM elements can be bound with each other or with a JavaScript object.






                                    share|improve this answer















                                    Changing an element's value can trigger a DOM event. Listeners that respond to events can be used to implement data binding in JavaScript.



                                    For example:



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


                                    Here is code and a demo that shows how DOM elements can be bound with each other or with a JavaScript object.







                                    share|improve this answer














                                    share|improve this answer



                                    share|improve this answer








                                    edited Jan 2 '17 at 11:34

























                                    answered Dec 22 '16 at 20:21









                                    amusingmakeramusingmaker

                                    112




                                    112























                                        1














                                        I have gone through some basic javascript example using onkeypress and onchange event handlers for making binding view to our js and js to view



                                        Here example 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>





                                        share|improve this answer




























                                          1














                                          I have gone through some basic javascript example using onkeypress and onchange event handlers for making binding view to our js and js to view



                                          Here example 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>





                                          share|improve this answer


























                                            1












                                            1








                                            1







                                            I have gone through some basic javascript example using onkeypress and onchange event handlers for making binding view to our js and js to view



                                            Here example 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>





                                            share|improve this answer













                                            I have gone through some basic javascript example using onkeypress and onchange event handlers for making binding view to our js and js to view



                                            Here example 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>






                                            share|improve this answer












                                            share|improve this answer



                                            share|improve this answer










                                            answered Feb 1 '17 at 11:54









                                            macha devendhermacha devendher

                                            96318




                                            96318























                                                1














                                                <!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>





                                                share|improve this answer




























                                                  1














                                                  <!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>





                                                  share|improve this answer


























                                                    1












                                                    1








                                                    1







                                                    <!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>





                                                    share|improve this answer













                                                    <!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>






                                                    share|improve this answer












                                                    share|improve this answer



                                                    share|improve this answer










                                                    answered Feb 18 '17 at 12:19









                                                    Anthony NewlineinfoAnthony Newlineinfo

                                                    291




                                                    291























                                                        1














                                                        A simple way of binding a variable to an input (two-way binding) is to just directly access the input element in the getter and 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" />


                                                        And to use:



                                                        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());





                                                        A fancier way of doing the above without getter/setter:

                                                        var variable = function(element){

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

                                                        else return element.value;
                                                        }

                                                        }


                                                        To use:



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





                                                        share|improve this answer






























                                                          1














                                                          A simple way of binding a variable to an input (two-way binding) is to just directly access the input element in the getter and 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" />


                                                          And to use:



                                                          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());





                                                          A fancier way of doing the above without getter/setter:

                                                          var variable = function(element){

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

                                                          else return element.value;
                                                          }

                                                          }


                                                          To use:



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





                                                          share|improve this answer




























                                                            1












                                                            1








                                                            1







                                                            A simple way of binding a variable to an input (two-way binding) is to just directly access the input element in the getter and 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" />


                                                            And to use:



                                                            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());





                                                            A fancier way of doing the above without getter/setter:

                                                            var variable = function(element){

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

                                                            else return element.value;
                                                            }

                                                            }


                                                            To use:



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





                                                            share|improve this answer















                                                            A simple way of binding a variable to an input (two-way binding) is to just directly access the input element in the getter and 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" />


                                                            And to use:



                                                            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());





                                                            A fancier way of doing the above without getter/setter:

                                                            var variable = function(element){

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

                                                            else return element.value;
                                                            }

                                                            }


                                                            To use:



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






                                                            share|improve this answer














                                                            share|improve this answer



                                                            share|improve this answer








                                                            edited Feb 8 '18 at 18:33

























                                                            answered Feb 8 '18 at 17:02









                                                            A-SharabianiA-Sharabiani

                                                            6,20395384




                                                            6,20395384























                                                                0














                                                                It is very simple two way data binding in vanilla javascript....











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

                                                                <div id="name">

                                                                </div>









                                                                share|improve this answer



















                                                                • 1





                                                                  surely this would only work with the onkeyup event? i.e. if you did an ajax request, and then changed the innerHTML via JavaScript then this wouldn't work

                                                                  – Zach Smith
                                                                  Jan 22 '18 at 15:50
















                                                                0














                                                                It is very simple two way data binding in vanilla javascript....











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

                                                                <div id="name">

                                                                </div>









                                                                share|improve this answer



















                                                                • 1





                                                                  surely this would only work with the onkeyup event? i.e. if you did an ajax request, and then changed the innerHTML via JavaScript then this wouldn't work

                                                                  – Zach Smith
                                                                  Jan 22 '18 at 15:50














                                                                0












                                                                0








                                                                0







                                                                It is very simple two way data binding in vanilla javascript....











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

                                                                <div id="name">

                                                                </div>









                                                                share|improve this answer













                                                                It is very simple two way data binding in vanilla javascript....











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

                                                                <div id="name">

                                                                </div>










                                                                share|improve this answer












                                                                share|improve this answer



                                                                share|improve this answer










                                                                answered Jan 7 '18 at 17:26









                                                                Subodh GawadeSubodh Gawade

                                                                1




                                                                1








                                                                • 1





                                                                  surely this would only work with the onkeyup event? i.e. if you did an ajax request, and then changed the innerHTML via JavaScript then this wouldn't work

                                                                  – Zach Smith
                                                                  Jan 22 '18 at 15:50














                                                                • 1





                                                                  surely this would only work with the onkeyup event? i.e. if you did an ajax request, and then changed the innerHTML via JavaScript then this wouldn't work

                                                                  – Zach Smith
                                                                  Jan 22 '18 at 15:50








                                                                1




                                                                1





                                                                surely this would only work with the onkeyup event? i.e. if you did an ajax request, and then changed the innerHTML via JavaScript then this wouldn't work

                                                                – Zach Smith
                                                                Jan 22 '18 at 15:50





                                                                surely this would only work with the onkeyup event? i.e. if you did an ajax request, and then changed the innerHTML via JavaScript then this wouldn't work

                                                                – Zach Smith
                                                                Jan 22 '18 at 15:50











                                                                0














                                                                Here's an idea using Object.defineProperty which directly modifies the way a property is accessed.



                                                                Code:



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


                                                                Usage:



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

                                                                p.variable="yes"


                                                                fiddle: Here






                                                                share|improve this answer




























                                                                  0














                                                                  Here's an idea using Object.defineProperty which directly modifies the way a property is accessed.



                                                                  Code:



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


                                                                  Usage:



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

                                                                  p.variable="yes"


                                                                  fiddle: Here






                                                                  share|improve this answer


























                                                                    0












                                                                    0








                                                                    0







                                                                    Here's an idea using Object.defineProperty which directly modifies the way a property is accessed.



                                                                    Code:



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


                                                                    Usage:



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

                                                                    p.variable="yes"


                                                                    fiddle: Here






                                                                    share|improve this answer













                                                                    Here's an idea using Object.defineProperty which directly modifies the way a property is accessed.



                                                                    Code:



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


                                                                    Usage:



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

                                                                    p.variable="yes"


                                                                    fiddle: Here







                                                                    share|improve this answer












                                                                    share|improve this answer



                                                                    share|improve this answer










                                                                    answered Jan 22 at 19:35









                                                                    ThornkeyThornkey

                                                                    477312




                                                                    477312






























                                                                        draft saved

                                                                        draft discarded




















































                                                                        Thanks for contributing an answer to Stack Overflow!


                                                                        • Please be sure to answer the question. Provide details and share your research!

                                                                        But avoid



                                                                        • Asking for help, clarification, or responding to other answers.

                                                                        • Making statements based on opinion; back them up with references or personal experience.


                                                                        To learn more, see our tips on writing great answers.




                                                                        draft saved


                                                                        draft discarded














                                                                        StackExchange.ready(
                                                                        function () {
                                                                        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f16483560%2fhow-to-implement-dom-data-binding-in-javascript%23new-answer', 'question_page');
                                                                        }
                                                                        );

                                                                        Post as a guest















                                                                        Required, but never shown





















































                                                                        Required, but never shown














                                                                        Required, but never shown












                                                                        Required, but never shown







                                                                        Required, but never shown

































                                                                        Required, but never shown














                                                                        Required, but never shown












                                                                        Required, but never shown







                                                                        Required, but never shown







                                                                        Popular posts from this blog

                                                                        Create new schema in PostgreSQL using DBeaver

                                                                        Deepest pit of an array with Javascript: test on Codility

                                                                        Costa Masnaga