I have this function
function createSlidingGallery(){
gallery_position = parseInt(jQuery(".imageWrapper.default").attr("rel"));
gallery_position_min = 0;
gallery_position_max = parseInt(jQuery("#galleryEl .sig_thumb a").size() - 1);
var galleryWrapper = document.createElement("div");
galleryWrapper.className = "sGalleryWrapper";
for (var i = 0; i < gallery_position_max; i++) {
var slide = document.createElement("div");
slide.className = "slide slide"+(i);
slide.setAttribute('rel', i);
galleryWrapper.appendChild(slide);
};
jQuery(".imageWrapper.top").append(galleryWrapper);
//HERE COMES THE PROBLEM PART
for (var i = 0; i < gallery_position_max; i++) {
var position = i;
//THE CALLBACK ACTUALLY USES THE SAME CONTEXT FOR ALL PARTICULAR CALLS SO THAT THE POSITION VALUE HOLDS THE MAXIMUM VALUE IN ALL INSTANCES
loadImage(position, false, function(index){
console.log(index);
jQuery(".slide"+position).css({
'background': 'url('+backgroundImages[index].src+')'
});
});
};
hideLoadingDC();
}
What it should do is asynchronously load images into dynamicaly created elements. It actually creates all the elements, and it loads images as well. But there is function called loadImage which is intended to preload images and then save the information that this images has been already loaded and are propably cached. I am calling it with callback function which handles the loaded image and sets it as a background to appropriate element.Hence I need to hold the information about the element (like pointer, or index/position).
Now I am trying to propagate its index to the function, but because the callback function is called after some time the position variable has already other value (the for loop actually goes through and in all calls of the callback it is set to the maximum value)
I know I can alter the loadImage function and add the position as another attribute, but I would prefere any other solution. I do not want to alter the loadImage function.
You can use a helper function to create a new scope for the position variable:
function makeGalleryCallback(position) {
return function(index){
console.log(index);
jQuery(".slide"+position).css({
'background': 'url('+backgroundImages[index].src+')'
});
};
}
function createSlidingGallery(){
...
for (var i = 0; i < gallery_position_max; i++) {
loadImage(i, false, makeGalleryCallback(i));
}
...
}
The problem is that all the callback functions are referencing the same i value, and not actually tracking the value at the time of iteration. So, you need to create a new scope (a closure) that creates a new reference for each different value of i.
A new scope can be created in JS with a function. Your code needs to be wrapped with an anonymous function and execute that function for each different value of i.
for (var i = 0; i < gallery_position_max; i++) {
var position = i;
loadImage(position, false, (function(index){
return function(){
console.log(index);
jQuery(".slide"+position).css({
'background': 'url('+backgroundImages[index].src+')'
});
};})(i));
};
Related
var imageArray = [];
for(var i =0; i<3; i++){
imageArray[i] = new Image();
imageArray[i].onload = function(i) {
$("#cell_"+i).append(imageArray[i]);
imageArray[i].style.visibility = "hidden";
}
imageArray[i].onerror = function() {
alert("not loaded");
}
imageArray[i].src = '/home//dummy_'+i+'.jpg';
}
I wan to load some images from a list of dummy list of images.
but during onload i lost the context of i,
it always points to i =3 (last value of loop).
ho to preserve this i so that when it comes to onload, it will give me exact i.
You need to wrap it in another function:
imageArray[i].onload = function(ic) { // ic is a copy of i in the scope of this function
return function() {
$("#cell_"+ic).append(imageArray[ic]); // ic is borrowed from the outer scope
imageArray[ic].style.visibility = "hidden";
}
}(i); // call the function with i as parameter
Alternatively, you can use bind to ... bind a parameter to your function:
imageArray[i].onload = function(i,event) {
// added the event parameter to show that the arguments normally passed to the function are not overwritten, merely pushed to make room for the bound ones
$("#cell_"+i).append(imageArray[i]);
imageArray[i].style.visibility = "hidden";
}
}.bind(null, i); // the function will always be called with he value of i passed to the first argument
NOTE: Behind the scenes, this is the same solution.
Reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
I have a timeout which calls a function until 100% progress is complete. It then executes the function I have assigned to it. Only the value that was given to it is undefined or at least part of it.
I'm not sure at which stage the code is losing the value being passed, thus making it return undefined but I have made a JS Fiddle with it in action for you to see it:
JS Fiddle
My end result is to receive the value correct then remove the given element like so:
function rmv_div(div_id) {
//div_id is not properly defined so cannot find the div.
document.getElementById('result').innerHTML = div_id;
var div = document.getElementById(div_id);
div.parentNode.removeChild(div);
}
The problem is that the variable i used inside func is created outside the scope of that function, and is increased at each iteration. Then, when you call func at the end, i equals array.length, so array[i] is undefined.
You can solve it creating another variable at each iteration that you won't increase:
Solution 1:
Demo: http://jsfiddle.net/qJ42h/4/ http://jsfiddle.net/qJ42h/11/
for (var i = 0; i < array.length; i++) {
var bar = document.getElementById('bar' + array[i]),
text = document.getElementById('text' + array[i]),
remove = 'wrap' + array[i],
j = i;
do_something(bar, text, function () {
rmv_div('id' + array[j]);
}, 1);
}
Solution 2
Demo: http://jsfiddle.net/qJ42h/8/ http://jsfiddle.net/qJ42h/12/
for (var i = 0; i < array.length; i++) {
var bar = document.getElementById('bar' + array[i]),
text = document.getElementById('text' + array[i]),
remove = 'wrap' + array[i];
do_something(bar, text, (function(i) {
return function(){ rmv_div('id' + array[i]); }
})(i), 1);
}
The problem here is that you didn't isolate the loop variable i inside the closure. However, this can be solved much more elegantly by using objects.
First off, I'm introducing the object that will encapsulate what you want; it gets initialized with a bar element and a function to call when it's done counting to 100. So, I'll call it BarCounter:
function BarCounter(element, fn)
{
this.element = element;
this.fn = fn;
this.text = element.getElementsByTagName('div')[0];
this.counter = 0;
}
This is just the constructor; it doesn't do anything useful; it resolves the text element, which is simply the first <div> tag it can find underneath the given element and stores that reference for later use.
Now we need a function that will do the work; let's call it run():
BarCounter.prototype.run = function()
{
var that = this;
if (this.counter < 100) {
this.text.innerHTML = this.counter++;
setTimeout(function() {
that.run();
}, 70);
} else {
this.fn(this.element);
}
}
The function will check whether the counter has reached 100 yet; until then it will update the text element with the current value, increase the counter and then call itself again after 70 msec. You can see how the reference to this is kept beforehand to retain the context in which the run() function is called later.
When all is done, it calls the completion function, passing in the element on which the BarCounter object operates.
The completion function is much easier if you pass the element to remove:
function removeDiv(element)
{
element.parentNode.removeChild(element);
}
The final step is to adjust the rest of your code:
var array = [1];
for (var i = 0; i < array.length; ++i) {
var bar = new BarCounter(
document.getElementById('bar' + array[i]),
removeDiv
);
bar.run();
}
It's very simple now; it creates a new BarCounter object and invokes its run() method. Done :)
Btw, you have the option to remove the element from within the object as well; this, of course, depends on your own needs.
Demo
What is the difference between these functions? Why does the first one work and the second does not work as expected?
http://jsfiddle.net/GKDev/x6pyg/ (this works)
http://jsfiddle.net/GKDev/bv4em/ (and this is not)
I'm trying to loop over input elements and add onfocus events on them:
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
};
}
In your non-working example, when the anonymous function is called, item has the last value it held when the for loop finished executing. That's because this variable belongs to the parent function that contains the for loop.
In your working example, you create a new function, pass in the current value of item like so:
function (help) {
return function () {
showHelp(help); // <-- This will be the value enclosed in this anonymous function
};
}(item.help); // <-- Calls an anonymous function passing in the current value
This creates a new closure around that value as it existed during that iteration. When the anonymous function is called, it uses that local value.
It is treated as:
var item;
for (var i = 0; i < helpText.length; i++) {
item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
};
}
The loop has finished before any focus callback fires and at that point item is the last item assigned.
It is pretty easy in fact.
In the first case you are passing item.help inside of the closure, which acts as a local copy. It's like a prisoner of its scope. What happens outside, nobody cares.
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function (help) {
return function () {
showHelp(help);
};
}(item.help);
}
In the second one, there is no closure preserving the value of item, which means that when item is evaluated, it evaluates to its actual value, i.e.: the last element of the array, since the last loop of the for set its value to the last value of the array.
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
I have the following code that adds an onmouseover event to a bullet onload
for (var i = 0; i <= 3; i++) {
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
}
This is the function that it is calling. It is supposed to add a css class to the menu item as the mouse goes over it.
function addBarOnHover(node) {
document.getElementById('menu').getElementsByTagName('li')[node].className = "current_page_item"; }
When the function is called, I keep getting the error:
"document.getElementById("menu").getElementsByTagName("li")[node] is
undefined"
The thing that is stumping me is I added an alert(node) statement to the addBarOnHover function to see what the value of the parameter was. The alert said the value of the parameter being passed was 4. I'm not sure how this could happen with the loop I have set up.
Any help would be much appreciated.
This is a common problem when you close over an iteration variable. Wrap the for body in an extra method to capture the value of the iteration variable:
for (var i = 0; i <= 3; i++) {
(function(i){ //here
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
})(i); //here
}
an anonymous function is created each time the loop is entered, and it is passed the current value of the iteration variable. i inside the anonymous function refers to the argument of this function, rather than the i in the outer scope.
You could also rename the inner variable for clarity:
for(var i=0; i<=3; i++){
(function(ii){
//use ii as i
})(i)
}
Without capturing the iteration variable, the value of i when it is finally used in the anonymous handler has been already changed to 4. There's only one i in the outer scope, shared between all instances of the handler. If you capture the value by an anonymous function, then the argument to that function is used instead.
i is being passed as a reference (not by value), so once the onmouseover callback is called, the value of i has already become 4.
You'll have to create your callback function using another function:
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('li');
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = (function(i) {
return function() {
addBarOnHover(i);
};
})(i);
}
You could make it a little more readable by making a helper function:
var createCallback = function(i) {
return function() {
addBarOnHover(i);
};
};
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = createCallback(i);
}
When I run this sample code in Google Chrome, the intended behavior--loading an image within a placeholder image tag on the current page--does not occur. I checked the value of currPic when showPic() is called, and it is "undefined." I know if I change the parameter to showPic from 'anchors[i]' to 'this', then it will work, but was trying to understand why this is so.
function showPic(currPic) {
var srcLoc = currPic.getAttribute("href");
var placeHolder = document.getElementById("placeholder");
placeHolder.setAttribute("src", srcLoc);
var imgLabel = document.getElementById("imglabel");
var currLinkTitle = currPic.getAttribute("title");
imgLabel.firstChild.nodeValue = currLinkTitle;
}
function prepareGallery() {
if(!(document.getElementsByTagName && document.getElementById)) return false;
var imgGallery = document.getElementById("imagegallery");
if(imgGallery) {
var anchors = imgGallery.getElementsByTagName("a");
var i;
for(i = 0; i < anchors.length; i++) {
anchors[i].onclick = function() {
showPic(anchors[i]);
return false;
}
}
}
}
Inside the anonymous function, anchors[i] provides a runtime reference. At the time the click occurs, anchors[i] no longer exists. While it existed at the time the assignment was made, it falls out of scope at the time of the click (since it's just an array reference). However, using this provides a solid reference to the immediate object that is always available at the time of the click.
More succinctly, anchors[i] is a reference to a position in an array (which leaves scope once the for loop exits). this is a reference to the dom element itself.
Because this would also work: showPic(document.getElementById(anchors[i].id)); - do you "get" it now (pun very much intended)?
Didn't see the obvious statement regarding how closures work, so here's my take on it.
var i;
for(i = 0; i < anchors.length; i++) {
anchors[i].onclick = function() {
showPic(anchors[i]);
return false;
}
}
Notice how you reference the i variable inside the loop? By the end of your loop, the value of i equals anchors.length.
So, when any of your onclick function is executed, that reference to i now points one position past the last index of anchors; this is why you see currPic is undefined.
One solution to this problem has been given in other answers: use this to reference the current anchor and don't pass anchors[i] to the onclick function.
As you may encounter similar situations, I'll show you another solution by closing over the value of i like so:
var i;
for(i = 0; i < anchors.length; i++) {
anchors[i].onclick = (function(i) {
// inside this function, i is closed over and won't change anymore
return function() {
showPic(anchors[i]);
return false;
}
}(i));
}