This is my first question ever thus I apologize in advance might I use the wrong netiquette.
I'm exploring different solutions to implement a two way binding using Javascript only, I ran across the 'common mistake' when using a Closure inside a for loop hence having the counter variable always set on the last item, I found the explanation (and the solution) on this very site but then I came across a different issue I'd appreciate some help for.
Imagine we have two sets of data, where one contains proper data, i.e.:
var data = {
data1 : 0
};
The other a collection of objects describing 3 elements :
var elements = {
1 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
2 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
3 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
}
}
See the complete codesnippet below
var data = {
data1 : 0
};
var elements = {
1 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
2 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
},
3 : {
target : 'main',
value : data,
element : 'div',
events : {
click : 'add'
}
}
}
// This is our main object, we define the properties only ...
var _elem = function (props,id){
this.id = id;
this.target = document.getElementById(props.target);
this.element = document.createElement(props.element);
this.events = props.events;
this.value = props.value;
}
// Then we add a method to render it on the page ...
_elem.prototype.render = function(){
// I added the Object Id for debugging purposes
this.element.innerHTML = this.value.data1 + ' ['+this.id+']';
this.target.appendChild(this.element);
}
// ... and another to change the underlying data and re - render it
_elem.prototype.add = function(){
// Since the data is a reference to the same data object
// We expect to change the value for all the elements
this.value.data1++;
this.render();
}
// First we looop trough the array with the element definition and
// Cast each item into a new element
for(var el in elements){
elements[el] = new _elem(elements[el],el);
}
// Then we apply the event listener (if any event description is present)
for(var el in elements){
if(!elements[el].hasOwnProperty( 'events' )){
continue;
}
// We use the anonymous function here to avoid the "common mistake"
(function() {
var obj = elements[el];
var events = obj.events;
for(var ev in events){
obj.element.addEventListener(ev,function(){ obj[events[ev]]() });
}
})();
}
// And finally we render all the elements on the page
for(var el in elements){
elements[el].render(elements[el]);
}
div {
padding: 10px;
border: solid 1px black;
margin: 5px;
display: inline-block;
}
<html>
<head>
</head>
<body>
<div id="main"></div>
</body>
</html>
Now, if we click button [1] it will update itself and the following, resulting in this sequence:
0 [2] 0 [3] 1 [1]
We refresh the page and this time click button [2], the sequence will be:
0 [1] 0 [3] 1 [2]
Button [3] Instead will update itself only
0 [1] 0 [2] 1 [3]
I did look for this topic before posting but all I could find were questions similar to this: addEventListener using for loop and passing values , where the issue was the counter variable holding always the last value
In this case instead it seems the issue to be the opposite, or the object holding the initial value and the ones following (if you keep clicking you will see what I mean)
What am I do wrong?