javascript: save a variable's value between calls? - javascript

I have some code that looks like this:
createEntity = function (imageSource, baseX, baseY) {
tmpIndex = images.length;
images[tmpIndex] = new Image();
entities[tmpIndex] = new Entity;
images[tmpIndex].onload = function () {
entities[tmpIndex].ready = true;
// How do I get the line above to use the value of tmpIndex
// that existed when it was created?
// That is to say, I want the images[1].onload function to
// change entities[1].ready, not entities[4].ready
// (assuming I created 5 objects sequentially
// before any images finished loading)
}
images[tmpIndex].src = imageSource;
entities[tmpIndex].x = baseX;
entities[tmpIndex].y = baseY;
}
createEntity("images/background.png", 0, 0);
createEntity("images/pawn1.png",0,0);
createEntity("images/pawn2.png",30,30);
createEntity("images/pawn3.png",60,60);
createEntity("images/pawn4.png",90,90);
The problem is that when I load all 5 images sequentially, as the above code shows, my onload function triggers with the current value of tmpIndex, not the one that existed when the function was created. Is there a simple way to make it so that the entities[somenumber].ready is toggled appropriately?

You need to declare tmpIndex as a local variable. To do this change
tmpIndex = images.length;
to
var tmpIndex = images.length;

Why does tmpIndex have to be visible outside of createEntity function? If it doesn't, just declare it inside your function, like this: var tmpIndex = images.length;
Your onload callback will be able to read its value even after the createEntity function has finished executing because will keep a reference to the scope where it was created.
Every execution scope is different, so every time you call createEntity you create different scope and a different onload callback function which stores a reference to that execution scope, and therefore is able to consume variables defined there.

Exactly. Here's a working JSfiddle example catching onerror instead on onload : http://jsfiddle.net/bdn2775k/1/
Here's the revelant part :
//Don't forget to declare a local variable !
var tmpIndex = images.length;

Related

scope of variable inside an event listener callback function

I used to think that the scope of a variable declared with var covers the whole scope between the {} but i tried the following example and i would like an explanation please on why when i use 'i' inside the callback function for the onclick event listener the value is not the index of the for loop but the value of i once the for loop is finished.Thank you.
<html>
<head>
</head>
<body>
</body>
<script>
var components = ["1obj", "2obj", "3obj", "4obj", "5obj"];
createDivs(components);
function createDivs(components) {
for (var i = 0; i < components.length; i++) {
var image = document.createElement('div');
image.innerHTML = components[i];
image.index = i;
image.onclick = function () {
console.log(this.index); //gives the real index
console.log(i); //gives the length of components+1
};
document.body.appendChild(image);
}
}
</script>
</html>
Your issue is closures, or lexical scoping . You're seeing i as the value of the last index because of lexical scoping. By the time the onclick happens, i will always be the last index. You can inject i in a closure, as such:
(function(_i) {
image.onclick = function () {
console.log(this.index); //gives the real index
console.log(_i); //gives the length of components+1
};
})(i);
see here for more information
All of the functions are referencing the same scope. It doesn't capture the current value and store a separate copy of the current value like you may think.
The solution is to use a function to set each variable so they each have a separate lexical environment.

Accessing a JavaScript / jQuery Instance only once

I have a jQuery function which is called on several events (button click, change etc.)
This function is called in the documentReadyFunction and is feeded with start values..
everytime I call this function, parameters will be passed to the function.
My problem is: I don't want to create a new Object each time I call the function, because if I set a variable which decides if a part of the function is beeing executed or not, will be always overwritten..
What do I have to do, to access the first created instance instead of creating always a new one with every function call..
Down below is a simplyfied version of my function.. Maybe you understand my problem better then.
$.fn.doSomething = function(param1) {
var localParam = param1;
var amIcalledMoreThanOnce = parseInt(0, 10);
if (param1 == 1) {
amIcalledMoreThanOnce = amIcalledMoreThanOnce + 1;
if (amIcalledMoreThanOnce == 1) {
$('#run').val(amIcalledMoreThanOnce);
// fill form fields with URL parameters
// This shall be executed only once after getting the URL vals
} else {
// set the localParam to 0 to exit this loop and reach the outter else..
localParam = 0;
$.fn.doSomething(localParam);
}
} else {
$('#run').val(amIcalledMoreThanOnce);
// use the User Input Data not the URL Params
}
};
$.fn.doSomething(1);
$.fn.doSomething(1);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<input type="number" id="run">
you can use this pattern:
var nameOfYourFunction = (function() {
var initializedOnlyOnce = {};
return function() {
//use initializedOnlyOnce here.
}
})();
here what you see is; you create and run a function immediately when the code is run. The outer function immediately returns the inner function and it's assigned to nameOfYourFunction. Then you can use the nameOfYourFunction(); just as any other function. However any varible declared in the outer function will be available to the nameOfYourFunction() and initializedOnlyOnce will never be initialized again.

Issue with scope and passing parameters to dynamically created event handler

In the code below you will see that I am trying to define an event handler for image.onclick which takes extra parameters, I was declaring those parameters inside of the while loop hoping JavaScript defined scope this way but it does not. Basically all of the event handlers here are getting the very last value that I give to variables id and section_id. Any idea on how to handle a situation where i want to be generating these handlers dynamically?
function handlePicturesResult(){
if (req.readyState == 4) { // Complete
if (req.status == 200) { // OK response
var el = document.getElementById('pictureHolder');
while( el.hasChildNodes() ){
el.removeChild(el.lastChild);
}
var tempObject = JSON.parse(req.responseText);
var pictures = tempObject.images;
var i=0;
while(i<pictures.length){
var picIndex = i;
var image= new Image(120,80);
image.src = 'images/' + pictures[i].url;
image.width = 120;
image.height = 80;
var id = pictures[picIndex].id;
var section_id = pictures[picIndex].sectionId;
image.onclick = function(event){
deleteImage(event,id,section_id);
}
var txtNode = document.createTextNode(pictures[picIndex.valueOf()].id);
el.appendChild(image);
el.appendChild(txtNode);
i++;
}
} else {
alert("Problem: " + req.statusText);
}
}
}
Yet another problem solved by closures!
image.onclick = function(id, section_id){
return function(event) {
deleteImage(event,id,section_id);
}
}(id, section_id);
The outer function is run at once (notice the parentheses with arguments at the end), and it returns the inner function, which keeps in its scope a copy of the variables as they are at that iteration of the loop.
JavaScript passes non-object variable by value, so by passing id and section_id to the outer function, you create a copy of them inside that function. When you create and return the inner function, that inner function keeps in scope all variables that are in scope at the time of its creation (this is at the heart of what a closure is), including the local-variable copies of id and section_id. The inner function, with its unique scope, becomes the event handler for that element.
You need to use a closure.
https://developer.mozilla.org/en/JavaScript/Guide/Closures
image.onclick = function(id, s_id){
return function(event){
deleteImage(event, id, s_id);
}
}(id, section_id)
Scope in javascript can be defined within the curly braces of a function, which why having an outer function execute with arguments passed to it will preserve the value of these variables at the specific time in the loop you need them.
Without them, the values of id and section_id will always reference the value they have at last iteration.
This is a classic JavaScript problem that stems from lack of understanding about function scope. On this line:
deleteImage(event,id,section_id);
there is no reason that the parameters passed to deleteImage should retain the values they had at the time when the callback was created. The variables id and section_id are bound to the scope of handlePicturesResult; they are variables that exist within that space, and there is only ever a single copy of each. Thus, when the callback runs, it will use those specific variables and the values they currently refer to (from the loop's last iteration).
The solution is to get the variables into a scope that "saves off" their values every iteration of the loop. Functions provide the only such scopes in JS. I won't repeat the excellent solutions already provided. An alternative is to use jQuery's each for iterations, and you won't have this problem again:
$.each(pictures, function(i, picture) {
var image= new Image(120,80);
var id = pictures.id;
var section_id = picture.sectionId;
var txtNode = document.createTextNode(id);
image.width = 120;
image.height = 80;
image.src = 'images/' + picture.url;
image.onclick = function(event){
deleteImage(event, id, section_id);
}
el.appendChild(image);
el.appendChild(txtNode);
});
Assumes pictures is an array.

Calling an object function from onload event makes it lose the context

I wanted to call a function when all required images are loaded. The number of images is known in advance, so I tried attaching a function call to the onload event of each image and count the number of times it was called.
<html>
<head>
<script>
var tractor;
function Tractor()
{
this.init_graphics();
}
Tractor.prototype.init_graphics = function()
{
this.gr_max = 3;
this.load_count = 0;
this.loading_complete(); // #1 test call, works OK
this.img1 = new Image();
this.img1.onload = this.loading_complete; // #2 gets called, but gr_max = undefined, load_count = NaN
this.img1.src = "http://dl.dropbox.com/u/217824/tmp/rearwheel.gif"; //just a test image
}
Tractor.prototype.loading_complete = function()
{
this.load_count += 1;
alert("this.loading_complete, load_count = " + this.load_count + ", gr_max = " + this.gr_max);
if(this.load_count >= this.gr_max) {this.proceed();}
};
function start()
{
tractor = new Tractor();
}
</script>
</head>
<body onload="start();">
</body>
</html>
When it's just called from another function of the object (see #1), it works just as I expected. When, however, it's called from onload event (see #2), the variables become "undefined" or "NaN" or something. What's happening? What am I doing wrong? How do I make it work?
I don't remember ever creating my own objects in Javascript before, so I certainly deeply apologize for this "what's wrong with my code" kind of question. I used this article as a reference, section 1.2, mainly.
Just in case, I put the same code on http://jsfiddle.net/ffJLn/
bind the context to the callback:
this.img1.onload = this.loading_complete.bind(this);
See: http://jsfiddle.net/ffJLn/1/ (same as yours but with this addition)
Here's an explanation of how bind works in detail: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
The basic idea is that it makes this in the bound function equal to whatever you pass as the parameter to bind.
Another option is to create a closure:
var self = this;
this.img1.onload = function() { self.loading_complete() };
Closures are functions that keep references to their context (in fact, all functions in javascript work this way). So here you are creating an anonymous function that keeps a reference to self. So this is another way to maintain context and for loading_complete to have the right this.
See: http://jsfiddle.net/ffJLn/2/ (same as yours but with the second possibility)
When #2 gets called, your this has changed. this now refers to the new Image() rather than the Tractor object.
Try changing...
this.img1.onload = this.loading_complete;
to
var that = this;
this.img1.onload = function() { that.loading_complete(); };
You can now use es6 arrow functions which provide lexical binding:
this.img1.onload = () => { this.loading_complete(); };

Javascript Scope of "for" loops

Alrighty... so there's a couple things going on here. First, I'm trying to create a global object called myScrolls. Second, I'm trying to set the value of that global object inside a jQuery load function. Third, I'm trying to access the myScrolls object outside of the load object.
What am I missing? Do the 'for' loops have limited scope?
Thanks
myScrolls=new Object();
$(window).load(function () {
var projectCount = 5;
for (var i=0;i<=projectCount;i++)
{
var singleProject = 'project_' + i;
myScrolls[singleProject] = new iScroll(singleProject, horizontalPreferences);
}
});
console.log(myScrolls);
You're trying to read the object before the load callback executes.
That $(window).load() function is waiting until after the window loads, and because the console log is not also in that callback, it actually gets executed before the function does. Thus by the time the console statement runs, its actually not populated.
myScrolls=new Object();
$(window).load(function () {
var projectCount = 5;
for (var i=0;i<=projectCount;i++)
{
var singleProject = 'project_' + i;
myScrolls[singleProject] = new iScroll(singleProject, horizontalPreferences);
}
console.log(myScrolls); //this was out of scope when outside of $(window).load()
});

Categories

Resources