function createTextFields(obj) {
for (var i = 0; i < obj.length; i++) {
dataDump = {};
for (var key in obj[i]) {
dataDump[key] = textField.value;
var callback = function (vbKey) {
return function (e) {
dataDump[vbKey] = e.source.value;
};
}(key);
}
}
globalData.push(dataDump);
}
I create textFields on click of button, each time a new one is created from the object. When i change the First or Second or Third TextFields and click on update... the value get's updated on the fourth or the last TextFields TextField's Object...
The callback is called on change of the TextFields
It's hard to tell without context, but assuming that different instances of callback are called on change of set text fields, then the problem with closure context of callback.
Callback function holds reference on global dataDump object which is re-assigned on each cycle of the iteration. So, at the end of the for loop all callbacks would reference only one (latest) dataDump object.
Try to add "var" to assignment line.
var dataDump = {};
You are using window.dataDump in this bit of code, so all your callback functions and such are using this same global variable.
Try var dataDump = {}; instead. You'll probably also want to move your globalData.push(dataDump); inside the loop.
Related
I have two javascript functions, one which attaches an eventhandler to an entire class of elements and the other which is called when the event handler is activated:
function attachDefinition(obj) {
var classArr = document.getElementsByClassName("flashcards");
for (let i = 0, len = classArr.length; i < len; i++) {
classArr[i].addEventListener('click', cardClicked);
}
}
function cardClicked(obj) {
console.log(this.id);
console.log(obj);
var img = document.createElement('img');
img.src = 'https://www.wordnik.com/img/wordnik_badge_a2.png';
document.getElementById(this.id).innerHTML = '';
document.getElementById(this.id).appendChild(img);
}
The above function runs without error on click. this.id logged to the console displays the id of the div element being clicked and obj logs the global object.
This is fine however I need to pass in an object created in a different function in the program. The above code only needs the obj argument added to addEventListener call but when I do that everything falls apart. This code:
function attachDefinition(obj) {
var classArr = document.getElementsByClassName("flashcards");
for (let i = 0, len = classArr.length; i < len; i++) {
classArr[i].addEventListener('click', cardClicked(obj)); //only thing I've changed!
}
}
function cardClicked(obj) {
console.log(this.id);
console.log(obj);
var img = document.createElement('img');
img.src = 'https://www.wordnik.com/img/wordnik_badge_a2.png';
document.getElementById(this.id).innerHTML = '';
document.getElementById(this.id).appendChild(img);
}
Now successfully console logs the passed in object but the line logging this.id is now undefined and I get "Unable to set property 'innerHTML' of undefined or null reference" on the innerHTML line.
I'm struggling to understand why passing in an argument would change this and how I can go about fixing it.
If we assume that your
classArr[i].addEventListener('click', cardClicked(obj);
is really
classArr[i].addEventListener('click', cardClicked(obj));
// Note ---------------------------------------------^
that's calling cardClicked and passing its return value into addEventListener, exactly the way foo(bar()) calls bar and passes its return value into foo.
But addEventListener expects a function in the second argument, and cardClicked doesn't return a function.
Instead, since you're relying on this referring to the clicked element inside cardClicked, you either need:
classArr[i].addEventListener('click', function() {
cardClicked.call(this, obj);
});
or
classArr[i].addEventListener('click', cardClicked.bind(classArr[i], obj));
The first works by responding to a click by calling cardClicked such that the this it sees is the same as the this the anonymous function receives.
The second works by using Function#bind to create a new function that, when called, will call cardClicked with this set to the avlue of classArr[i] and its first argument set to obj.
Change your classArr[i].addEventListener('click', cardClicked(obj); to this instead:
classArr[i].addEventListener('click', function() {
cardClicked(obj);
});
First off, you're missing a ) in the original. Additionally, you need to create an anonymous function when passing parameters in setInterval, otherwise the function in question will execute immediately upon reading.
when you assign a function to an event as action on trigger then the function's this would point to element which event fire on. your first implementation uses this advantage and then you can use this in your function to refer to related element.
But the problem in second implementation is that the function is called instead of assignment.
If you want to send additional data to you callback you can use function call or apply:
element.addEventListener('click', function(){
var obj = { addtional: 'data' };
myCallback.call(this, obj);
})
function myCallback (obj){
console.log(this.id, obj);
}
function.prototype.call
The call() method calls a function with a given this value and arguments provided individually.
function.prototype.apply
The apply() method calls a function with a given this value and arguments provided as an array (or an array-like object).
This may be a common error, but I can't find another way of doing this. I'm creating a timeline showing multiple projects using timeline.js with the following code
function createTimeline(){
for(var length = data.section.length,i = 0; i < length; i++){
var divWrapper = document.createElement("div");
if(i != data.section.length - 1)
divWrapper.setAttribute('class','timelineWrapper');
$('body').append(divWrapper);
layer[i] = new links.Timeline(divWrapper);
links.events.addListener(layer[i], 'rangechange',
function(){
var that = this;
var range = layer[i].getVisibleChartRange();
for (var j = 0; j < layer.length; j++){
if(j != i){
layer[j].setVisibleChartRange(range.start, range.end);
}
}
}
);
layer[i].draw(data.section[i].project, options);
}
}
It gives the error Cannot call method 'getVisibleChartRange' of undefined .
What is the problem here? Why is layer[i] undefined? It is not being detected during the event rangechange itself.
You must bind i within a closure to save its value for your addListener call, as i is undefined when the function in your addListener later gets called. Try replacing the third argument of your addListener call with the below:
(function(i) {
return function() {
var that = this;
var range = layer[i].getVisibleChartRange();
// Rest of code
};
}(i)); // Anonymous function binds i in a closure
The issue is caused because the unnamed function used as event handler uses its parent scope's i variable.
At the end of your loop, i==data.section.length in this scope.
This is also the i value for all your events handlers. Because of this, layer[i] is undefined, and this is causing the error message.
The easiest way to address this issue is to create a functionBuilder function taking i as parameter and returning a new function (your handler).
In this returned handler's direct parent's scope, i value will be the parameter you passed to the functionBuilder function.
I'll post some code example later today, as soon as I have access to a pc (no way I can type that from a tablet :o) )
EDIT: I've been too slow... mc10 posted more or less what I wanted to post :o)
In case you don't understand why this works, or what closures, scopes or bind means, here is an old but complete explanation:
http://blog.niftysnippets.org/2008/02/closures-are-not-complicated.html
In the function "Encaisser", the value of "i" is OK in the for, but if i call 'i' in a function in my function, "i" return "Undefined.
function Encaisser()
{
for(var i=1; i <= Nombre_ligne_en_caisse; i++)
{
db.transaction(function(t,i){ t.executeSql('SELECT En_cour FROM Sequence WHERE Nom="Ticket_ID"', [], function(tx,rs,i){
var row = rs.rows.item(0);
var Tick_ID = row['En_Cour'];
var Noma = window['Produit_en_caisse_' + i] ;
alert(i); //Undefined
alert(Noma); //Undefined
}, [])});
alert(i); //If i put the alert here, its OK
}
}
Do you know why?
Thank You,
The problem is that your inner function defines a parameter named i here:
db.transaction(function(t,i){ ...
If you intend for i to be the value from the outer function, I recommend you simply remove this parameter. It doesn't appear that db.transaction is actually providing a value for this parameter anyway. You'll probably also want to close the value of i at each iteration in a separate variable, and use that inside your function, like this:
var index = i;
db.transaction(function(t){ ...
var Noma = window['Produit_en_caisse_' + index ];
alert(index);
You redefine i inside both your db.transaction callback and your t.executeSql callback. Inside your t.executeSql callback, i must be undefined.
If you want to access the value of i from the for loop, you'll need to rename those parameters in your callbacks.
This code is supposed to pop up an alert with the number of the image when you click it:
for(var i=0; i<10; i++) {
$("#img" + i).click(
function () { alert(i); }
);
}
You can see it not working at http://jsfiddle.net/upFaJ/. I know that this is because all of the click-handler closures are referring to the same object i, so every single handler pops up "10" when it's triggered.
However, when I do this, it works fine:
for(var i=0; i<10; i++) {
(function (i2) {
$("#img" + i2).click(
function () { alert(i2); }
);
})(i);
}
You can see it working at http://jsfiddle.net/v4sSD/.
Why does it work? There's still only one i object in memory, right? Objects are always passed by reference, not copied, so the self-executing function call should make no difference. The output of the two code snippets should be identical. So why is the i object being copied 10 times? Why does it work?
I think it's interesting that this version doesn't work:
for(var i=0; i<10; i++) {
(function () {
$("#img" + i).click(
function () { alert(i); }
);
})();
}
It seems that the passing of the object as a function parameter makes all the difference.
EDIT: OK, so the previous example can be explained by primitives (i) being passed by value to the function call. But what about this example, which uses real objects?
for(var i=0; i<5; i++) {
var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
toggler.click(function () { toggler.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
$("#container").append(toggler);
}
Not working: http://jsfiddle.net/Zpwku/
for(var i=0; i<5; i++) {
var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
(function (t) {
t.click(function () { t.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
$("#container").append(t);
})(toggler);
}
Working: http://jsfiddle.net/YLSn6/
Most of the answers are correct in that passing an object as a function parameter breaks a closure and thus allow us to assign things to functions from within a loop. But I'd like to point out why this is the case, and it's not just a special case for closures.
You see, the way javascript passes parameters to functions is a bit different form other languages. Firstly, it seems to have two ways of doing it depending on weather it's a primitive value or an object. For primitive values it seems to pass by value and for objects it seems to pass by reference.
How javascript passes function arguments
Actually, the real explanation of what javascript does explains both situations, as well as why it breaks closures, using just a single mechanism.
What javascript does is actually it passes parameters by copy of reference. That is to say, it creates another reference to the parameter and passes that new reference into the function.
Pass by value?
Assume that all variables in javascript are references. In other languages, when we say a variable is a reference, we expect it to behave like this:
var i = 1;
function increment (n) { n = n+1 };
increment(i); // we would expect i to be 2 if i is a reference
But in javascript, it's not the case:
console.log(i); // i is still 1
That's a classic pass by value isn't it?
Pass by reference?
But wait, for objects it's a different story:
var o = {a:1,b:2}
function foo (x) {
x.c = 3;
}
foo(o);
If parameters were passed by value we'd expect the o object to be unchanged but:
console.log(o); // outputs {a:1,b:2,c:3}
That's classic pass by reference there. So we have two behaviors depending on weather we're passing a primitive type or an object.
Wait, what?
But wait a second, check this out:
var o = {a:1,b:2,c:3}
function bar (x) {
x = {a:2,b:4,c:6}
}
bar(o);
Now see what happens:
console.log(o); // outputs {a:1,b:2,c:3}
What! That's not passing by reference! The values are unchanged!
Which is why I call it pass by copy of reference. If we think about it this way, everything makes sense. We don't need to think of primitives as having special behavior when passed into a function because objects behave the same way. If we try to modify the object the variable points to then it works like pass by reference but if we try to modify the reference itself then it works like pass by value.
This also explains why closures are broken by passing a variable as a function parameter. Because the function call will create another reference that is not bound by the closure like the original variable.
Epilogue: I lied
One more thing before we end this. I said before that this unifies the behavior of primitive types and objects. Actually no, primitive types are still different:
var i = 1;
function bat (n) { n.hello = 'world' };
bat(i);
console.log(i.hello); // undefined, i is unchanged
I give up. There's no making sense of this. It's just the way it is.
It's because you are calling a function, passing it a value.
for (var i = 0; i < 10; i++) {
alert(i);
}
You expect this to alert different values, right? Because you are passing the current value of i to alert.
function attachClick(val) {
$("#img" + val).click(
function () { alert(val); }
);
}
With this function, you'd expect it to alert whatever val was passed into it, right? That also works when calling it in a loop:
for (var i = 0; i < 10; i++) {
attachClick(i);
}
This:
for (var i = 0; i < 10; i++) {
(function (val) {
$("#img" + val).click(
function () { alert(val); }
);
})(i);
}
is just an inline declaration of the above. You are declaring an anonymous function with the same characteristics as attachClick above and you call it immediately. The act of passing a value through a function parameter breaks any references to the i variable.
upvoted deceze's answer, but thought I'd try a simpler explanation. The reason the closure works is that variables in javascript are function scoped. The closure creates a new scope, and by passing the value of i in as a parameter, you are defining a local variable i in the new scope. without the closure, all of the click handlers you define are in the same scope, using the same i. the reason that your last code snippet doesn't work is because there is no local i, so all click handlers are looking to the nearest parent context with i defined.
I think the other thing that might be confusing you is this comment
Objects are always passed by reference, not copied, so the self-executing function call should make no difference.
this is true for objects, but not primitive values (numbers, for example). This is why a new local i can be defined. To demonstrate, if you did something weird like wrapping the value of i in an array, the closure would not work, because arrays are passed by reference.
// doesn't work
for(var i=[0]; i[0]<10; i[0]++) {
(function (i2) {
$("#img" + i2[0]).click(
function () { alert(i2[0]); }
);
})(i);
}
In the first example, there is only one value of i and it's the one used in the for loop. This, all event handlers will show the value of i when the for loop ends, not the desired value.
In the second example, the value of i at the time the event handler is installed is copied to the i2 function argument and there is a separate copy of that for each invocation of the function and thus for each event handler.
So, this:
(function (i2) {
$("#img" + i2).click(
function () { alert(i2); }
);
})(i);
Creates a new variable i2 that has it's own value for each separate invocation of the function. Because of closures in javascript, each separate copy of i2 is preserved for each separate event handler - thus solving your problem.
In the third example, no new copy of i is made (they all refer to the same i from the for loop) so it works the same as the first example.
Code 1 and Code 3 didn't work because i is a variable and values are changed in each loop. At the end of loop 10 will be assigned to i.
For more clear, take a look at this example,
for(var i=0; i<10; i++) {
}
alert(i)
http://jsfiddle.net/muthkum/t4Ur5/
You can see I put a alert after the loop and it will show show alert box with value 10.
This is what happening to Code 1 and Code 3.
Run the next example:
for(var i=0; i<10; i++) {
$("#img" + i).click(
function () { alert(i); }
);
}
i++;
You'll see that now, 11 is being alerted.
Therefore, you need to avoid the reference to i, by sending it as a function parameter, by it's value. You have already found the solution.
One thing that the other answers didn't mention is why this example that I gave in the question doesn't work:
for(var i=0; i<5; i++) {
var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
toggler.click(function () { toggler.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
$("#container").append(toggler);
}
Coming back to the question months later with a better understanding of JavaScript, the reason it doesn't work can be understood as follows:
The var toggler declaration is hoisted to the top of the function call. All references to toggler are to the same actual identifier.
The closure referenced in the anonymous function is the same (not a shallow copy) of the one containing toggler, which is being updated for each iteration of the loop.
#2 is quite surprising. This alerts "5" for example:
var o;
setTimeout(function () { o = {value: 5}; }, 100);
setTimeout(function () { alert(o.value) }, 1000);
I would like to assign the jQuery click-function for all elements in an array. But additionally, I need to access the array from within the click-function. The source will hopefully make it clearer:
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click(function(event, mybox) {
event.preventDefault();
doStuffWithParameter(mybox);
});
}
// mybox is a JavaScript object (not jQuery-element!), myarray is an array, jqelem is a jQueryelement ala $("div.myclass");
The problem seems to be with function(event, mybox), apparently that doesn't work, i.e. mybox is unknown within the function. I think I 'kind of' understand why it cannot work this way, but how can this be achieved?
PS: I'm basically just doing it to save me from typing it manually for all array-elements.
Just remove the (useless) second callback function parameter named mybox.
If mybox is in scope in the outer block, it'll be in scope in the inner callback function too!
Should you need to know the appropriate value of i in the callback then you can do event registration-time binding:
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click({i: i}, function(event) {
// use "event.data.i" to access i's value
var my_i = event.data.i;
});
}
The map {i : i} corresponds with the eventData parameter in the jQuery .click() documentation.
When your click handler gets called, the first argument is the event data. jQuery doesn't pass in a second argument.
Update: Using closure to get to mybox object (notice I removed the 2nd argument)
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click(function(event) {
event.preventDefault();
// here's the trick to get the correct i
(function(item) {
return function() {
doStuffWithParameter(mybox.myarray[item]);
};
})(i);
// you can access any object from parent scope here
// (except i, to get to right i, you need another trick),
// effectively creating a closure
// e.g. doOtherStuff(myarray)
});
}
Read more on closures here: http://jibbering.com/faq/notes/closures/
and here: How do JavaScript closures work?
You can take help of jquery data attributes
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.data("arrayIndex", i).click(function(event) {
event.preventDefault();
doStuffWithParameter(mybox.myarray[$(this).data("arrayIndex")]);
});
}