Issue with scope and passing parameters to dynamically created event handler - javascript

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.

Related

Three.js OBJ and MTL Loaders are not called in loop

I am using the THREE.js OBJ and MTL Loader in an Loop to display different Elements of an 3d animated cake. I need those different Elements because I want the user to be able to change the color of those specific elements (eg. the decor) of the cake.
But whenever I hit a THREE.load function the execution of the iteration of the loop is stopped an it starts with the next (i++). I am new to Javascript. So I am not sure if I am missing an general understanding of loops.
Only in the last gothrough the load function is called and correctly executed. If I use the exact same code without a loop, but rather provide the material-/objectPath hard coded and use several loader everything runs fine.
function draw(currentlySelectedCake){
layerArray = [];
// Load Cake
var i;
for (i = 0; i < currentCakeElements.length; i++){
if(currentCakeElements[i].endsWith(".mtl")){
var materialPath = "uploads/" +currentlySelectedCake + "/" + currentCakeElements[i];
var objectPath = "uploads/" +currentlySelectedCake + "/" + currentCakeElements[i+1];
var cakeLoader = new THREE.MTLLoader();
cakeLoader.load(materialPath, function (materials) {
materials.preload();
// Load the Cake
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.load(objectPath , function (object) {
layer1 = object.clone();
layer2 = object.clone();
layer3 = object.clone();
layer1.name = "Layer1Part" + i;
layer2.name = "Layer2Part" + i;
layer3.name = "Layer3Part" + i;
layer1.traverse((child) => {
if (child.isMesh) {
child.material = child.material.clone();
}
});
layer2.traverse((child) => {
if (child.isMesh) {
child.material = child.material.clone();
}
});
layer3.traverse((child) => {
if (child.isMesh) {
child.material = child.material.clone();
}
});
layer2.position.y = tortenhoehe;
layer3.position.y = tortenhoehe*2*0.75;
camera.lookAt(layer2.position);
layer1Group.add(layer1);
layer1Group.name = "Layer1";
layer2Group.add(layer2);
layer2Group.name = "Layer2";
layer3Group.add(layer3);
layer3Group.name = "Layer3";
});
layer1Elements.push(layer1Group);
layer2Elements.push(layer2Group);
layer3Elements.push(layer3Group);
});
}
});
}
}
I think this here was my solution. Still in the final process of testing. But looking good so far.
https://www.albertgao.xyz/2016/08/25/why-not-making-functions-within-a-loop-in-javascript/
the variable i and j gets declared first
When the first loop runs, an anonymous function has been created inside the loop
Inside the newly created anonymous function, it referred a variable i which is not in its scope
After the first loop, the value of variable i accumulates to 3 since the loop runs for 3 times.
In the second loop, each function created in the first loop will be invoked.
When it gets invoked, the interpreter will check the value of i, and it found there is no i inside.
Since this anonymous has become a closure, the interpreter will look at its scope chain.
Finally, the interpreter founds the variable i, in the global scope, still within its lexical scope, which is totally legitimate for this anonymous function to refer.
And the value of i is 3. We solved it in step 4.
So, a 3 will be output.
What happens afterwards for the second and third loop is totally the same from step 6~10.

javascript: save a variable's value between calls?

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;

Why couldn't I pass the value in javascript?

for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click', function() { install(id); } );
}
}
No idea why id can't reach 'install' in function(). I am trying to bind every button (id from 50 to 100) with a click event to trigger the install(id) function. But it seems the variable id cannot reach install function. While I hard code it:
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click', function() { install( 56 ); });
}
}
it works! Please tell me why.
What you made is one of the most common mistakes when using Javascript closures.
By the way the very fact that this mistake is so common is IMO a proof that it's indeed a "bug" in the language itself.
Javascript supports read-write closures so when you capture a variable in a closure it's not the current value of the variable that is captured, but the variable itself.
This means that for example in
var arr = [];
for (var i=0; i<10; i++)
arr.push(function(){alert(i);});
each of the 10 functions in the array will contain a closure, but all of them will be referencing the same i variable used in the loop, not the value that this variable was having at the time the closure was created. So if you call any of them the output will be the same (for example 10 if you call them right after the loop).
Luckily enough the workaround is simple:
var arr = [];
for (var i=0; i<10; i++)
arr.push((function(i) {
return (function(){alert(i);});
})(i));
using this "wrapping" you are calling an anonymous function and inside that function the variable i is a different one from the loop and is actually a different variable for each invocation. Inside that function i is just a parameter and the closure returned is bound to that parameter.
In your case the solution is therefore:
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click',
(function(id){
return (function() { install(id); });
})(id));
}
}
By not reaching the install(), I guess you mean you get all your install(id) behaves like install(100).
Reason why it doesn't work
This is caused by the javaSctipt closure. This line function() { install(id) } assign the id to the install() callback function. The id's value won't be resolved until install() is call when is far later after the loop is finished - the time when id has already reached 100.
The solution is create another closure the hold the current id value.
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
(function (id) {
$('#'+id).bind('click', function() { install(id); });
}) (id);
}
}
Here is a demonstration code:
var funcCollections = [];
for (id = 50; id < 100; id++)
{
if(true)
{
(function () {
var thatId = id;
funcCollections.push(function () {console.log(thatId,id)});
}) ();
}
}
// funcCollections[1]();
// 51 100
// undefined
// funcCollections[2]();
// 52 100
You can't pass a variable to the function you've bind. It loses the val. When you pass '56' it will be always 56, but when you pass a var, the JavaScript will not bind the value of the var in the loop.
When you loop over variables and you create anonymous functions(closure) that reference the loop variable they will reference the last value
also note that you don't limit scope the loop variable to the for loop(it's not declared with var) so that means that later modifications to that variable will be propagated to all closures.
take a look at this
It's down to variable scope.
The anonymous function you're binding to the click event of the $('#' + id) elements has no awareness of the id variable in the your sample code (assuming that your sample code is an excerpt from a function). Even if it did (e.g. you declared id outside of any function, giving it global scope), id would hold the value 100 when the click event was called, which isn't what you intend.
However, you could use $(this).attr('id') to get hold of the element's id value instead:
for (id = 50; id < 100; id++)
{
if($('#' + id).attr('class') == 'myField')
{
$('#' + id).bind('click', function()
{
install(parseInt($(this).attr('id')));
});
}
}
Check out the jQuery .bind() documentation, it shows how this can be used from within an event handler.

Functional object basics. How to go beyond simple containers?

On the upside I'm kinda bright, on the downside I'm wracked with ADD. If I have a simple example, that fits with what I already understand, I get it. I hope someone here can help me get it.
I've got a page that, on an interval, polls a server, processes the data, stores it in an object, and displays it in a div. It is using global variables, and outputing to a div defined in my html. I have to get it into an object so I can create multiple instances, pointed at different servers, and managing their data seperately.
My code is basically structured like this...
HTML...
<div id="server_output" class="data_div"></div>
JavaScript...
// globals
var server_url = "http://some.net/address?client=Some+Client";
var data = new Object();
var since_record_id;
var interval_id;
// window onload
window.onload(){
getRecent();
interval_id = setInterval(function(){
pollForNew();
}, 300000);
}
function getRecent(){
var url = server_url + '&recent=20';
// do stuff that relies on globals
// and literal reference to "server_output" div.
}
function pollForNew(){
var url = server_url + '&since_record_id=' + since_record_id;
// again dealing with globals and "server_output".
}
How would I go about formatting that into an object with the globals defined as attributes, and member functions(?) Preferably one that builds its own output div on creation, and returns a reference to it. So I could do something like...
dataOne = new MyDataDiv('http://address/?client');
dataOne.style.left = "30px";
dataTwo = new MyDataDiv('http://different/?client');
dataTwo.style.left = "500px";
My code is actually much more convoluted than this, but I think if I could understand this, I could apply it to what I've already got. If there is anything I've asked for that just isn't possible please tell me. I intend to figure this out, and will. Just typing out the question has helped my ADD addled mind get a better handle on what I'm actually trying to do.
As always... Any help is help.
Thanks
Skip
UPDATE:
I've already got this...
$("body").prepend("<div>text</div>");
this.test = document.body.firstChild;
this.test.style.backgroundColor = "blue";
That's a div created in code, and a reference that can be returned. Stick it in a function, it works.
UPDATE AGAIN:
I've got draggable popups created and manipulated as objects with one prototype function. Here's the fiddle. That's my first fiddle! The popups are key to my project, and from what I've learned the data functionality will come easy.
This is pretty close:
// globals
var pairs = {
{ div : 'div1', url : 'http://some.net/address?client=Some+Client' } ,
{ div : 'div2', url : 'http://some.net/otheraddress?client=Some+Client' } ,
};
var since_record_id; //?? not sure what this is
var intervals = [];
// window onload
window.onload(){ // I don't think this is gonna work
for(var i; i<pairs.length; i++) {
getRecent(pairs[i]);
intervals.push(setInterval(function(){
pollForNew(map[i]);
}, 300000));
}
}
function getRecent(map){
var url = map.url + '&recent=20';
// do stuff here to retrieve the resource
var content = loadResoucrce(url); // must define this
var elt = document.getElementById(map.div);
elt.innerHTML = content;
}
function pollForNew(map){
var url = map.url + '&since_record_id=' + since_record_id;
var content = loadResoucrce(url); // returns an html fragment
var elt = document.getElementById(map.div);
elt.innerHTML = content;
}
and the html obviously needs two divs:
<div id='div1' class='data_div'></div>
<div id='div2' class='data_div'></div>
Your 'window.onload` - I don't think that's gonna work, but maybe you have it set up correctly and didn't want to bother putting in all the code.
About my suggested code - it defines an array in the global scope, an array of objects. Each object is a map, a dictionary if you like. These are the params for each div. It supplies the div id, and the url stub. If you have other params that vary according to div, put them in the map.
Then, call getRecent() once for each map object. Inside the function you can unwrap the map object and get at its parameters.
You also want to set up that interval within the loop, using the same parameterization. I myself would prefer to use setTimeout(), but that's just me.
You need to supply the loadResource() function that accepts a URL (string) and returns the HTML available at that URL.
This solves the problem of modularity, but it is not "an object" or class-based approach to the problem. I'm not sure why you'd want one with such a simple task. Here's a crack an an object that does what you want:
(function() {
var getRecent = function(url, div){
url = url + '&recent=20';
// do stuff here to retrieve the resource
var content = loadResoucrce(url); // must define this
var elt = document.getElementById(div);
elt.innerHTML = content;
}
var pollForNew = function(url, div){
url = url + '&since_record_id=' + since_record_id;
var content = loadResoucrce(url); // returns an html fragment
var elt = document.getElementById(div);
elt.innerHTML = content;
}
UpdatingDataDiv = function(map) {
if (! (this instanceof arguments.callee) ) {
var error = new Error("you must use new to instantiate this class");
error.source = "UpdatingDataDiv";
throw error;
}
this.url = map.url;
this.div = map.div;
this.interval = map.interval || 30000; // default 30s
var self = this;
getRecent(this.url, this.div);
this.intervalId = setInterval(function(){
pollForNew(self.url, self.div);
}, this.interval);
};
UpdatingDataDiv.prototype.cancel = function() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
})();
var d1= new UpdatingDataDiv('div1','http://some.net/address?client=Some+Client');
var d2= new UpdatingDataDiv('div2','http://some.net/otheraddress?client=Some+Client');
...
d1.cancel();
But there's not a lot you can do with d1 and d2. You can invoke cancel() to stop the updating. I guess you could add more functions to extend its capability.
OK, figured out what I needed. It's pretty straight forward.
First off disregard window.onload, the object is defined as a function and when you instantiate a new object it runs the function. Do your setup in the function.
Second, for global variables that you wish to make local to your object, simply define them as this.variable_name; within the object. Those variables are visible throughout the object, and its member functions.
Third, define your member functions as object.prototype.function = function(){};
Fourth, for my case, the object function should return this; This allows regular program flow to examine the variables of the object using dot notation.
This is the answer I was looking for. It takes my non-functional example code, and repackages it as an object...
function ServerObject(url){
// global to the object
this.server_url = url;
this.data = new Object();
this.since_record_id;
this.interval_id;
// do the onload functions
this.getRecent();
this.interval_id = setInterval(function(){
this.pollForNew();
}, 300000);
// do other stuff to setup the object
return this;
}
// define the getRecent function
ServerObject.prototype.getRecent = function(){
// do getRecent(); stuff
// reference object variables as this.variable;
}
// same for pollForNew();
ServerObject.prototype.pollForNew = function(){
// do pollForNew(); stuff here.
// reference object variables as this.variable;
}
Then in your program flow you do something like...
var server = new ServerObject("http://some.net/address");
server.variable = newValue; // access object variables
I mentioned the ADD in the first post. I'm smart enough to know how complex objects can be, and when I look for examples and explanations they expose certain layers of those complexities that cause my mind to just swim. It is difficult to drill down to the simple rules that get you started on the ground floor. What's the scope of 'this'? Sure I'll figure that out someday, but the simple truth is, you gotta reference 'this'.
Thanks
I wish I had more to offer.
Skip

Why can't I add an event to each element in a collection that refers to Itself rather than the last element in the "for(){}" statement

On Window's load, every DD element inside Quote_App should have an onCLick event appended that triggers the function Lorem, however, Lorem returns the nodeName and Id of the last element in the For statement rather than that of the element that trigged the function. I would want Lorem to return the nodeName and Id of the element that triggered the function.
function Lorem(Control){
/* this.Control=Control; */
this.Amet=function(){
return Control.nodeName+"\n"+Control.id;
};
};
function Event(Mode,Function,Event,Element,Capture_or_Bubble){
if(Mode.toLowerCase()!="remove"){
if(Element.addEventListener){
if(!Capture_or_Bubble){
Capture_or_Bubble=false;
}else{
if(Capture_or_Bubble.toLowerCase()!="true"){
Capture_or_Bubble=false;
}else{
Capture_or_Bubble=true;
};
};
Element.addEventListener(Event,Function,Capture_or_Bubble);
}else{
Element.attachEvent("on"+Event,Function);
};
};
};
function Controls(){
var Controls=document.getElementById("Quote_App").getElementsByTagName("dd");
for(var i=0;i<Controls.length;i++){
var Control=Controls[i];
Event("add",function(){
var lorem=new Lorem(Control);
lorem.Control=Control;
alert(lorem.Amet());
},"click",Controls[i]);
};
};
Event("add",Controls,"load",window);
Currently you click on any DD element Lorem always returns the nodeName and Id of the last DD element.
Lorem should return the nodeName and Id of the Control (Control[i]) that triggered Lorem.
How do I go about making this happen?
Thank you!
you need a closure inside the loop where you are attaching the event handlers to capture the value of i in each loop iteration.
for(var i=0;i<Controls.length;i++) {
(function() {
var Control=Controls[i];
Event("add",function(){
var lorem=new Lorem(Control);
lorem.Control=Control;
alert(lorem.Amet());
},"click",Controls[i]);
})();
};
Here I've created a closure above using JavaScript's good friend, the self-invoking anonymous function.
The reason a closure is required is that without it, the value of i at the point at which any event handler function is executed would be the value of i in the last loop iteration, not what we want. We want the value of i as it is in each loop iteration, at the point at which we declare each event handler function, thus we need to capture this value in each iteration. Using an anonymous function that executes as soon as it's declared is a good mechanism for capturing the desired value.
Another point, slightly off topic but it may help you out, is that event capturing is not supported in every browser (ahem, IE) but event bubbling is. This effectively makes the useCapture boolean flag in addEventListener quite useless for developing cross browser web applications. I'd therefore advise not to use it.
One more thing, JavaScript generally uses camel casing for function names and variable names. Pascal casing is generally used only for constructor functions (functions that create objects).
When you create a function that refers to variables outside of it, these references will be resolved at the time you call this function.
In your case:
var functions = [];
function outer() {
for (var i = 0; i < N; i++) { // <------------
functions[i] = function() { // |
alert(i); // <-- this 'i' refers to this one
}
} // At the end of the for loop, i == N (or N+1?)
}
functions[x](); // Will show N (or N+1)
// because that's the current value of i in the outer function
// (which is kept alive just because something refers to it)
What you want to do is capture the value of 'i' at each step of the loop, for later evaluation, e.g.:
var functions = [];
function outer() {
for (var i = 0; i < N; i++) { // <---------------------------------------
functions[i] = (function(my_i) { // <---- |
return function () { // | |
alert(my_i); // my_i refers to this which is a copy of 'i' there
}
}(i)); // run function(with my_i = the i at each step of the loop)
}
}
functions[x](); // Will show x
You can see there is an inner function that gets a copy of the current counter as a parameter. This inner function stays alive with that copy, so that later calls to the stored innest function returns the value of the my_i of the function that created it -- Clear? :-)
This is the wonderful world of closures. It may take a bit of mind-bending to first get it, but then you'll be glad you did, so go and google "javascript closures" to death!
This may be a more obvious variant of Russ Cam's answer:
function Controls() {
var Controls = document.getElementById("Quote_App").getElementsByTagName("dd");
var createHandlerFor = function(CurrentControl) {
return function() {
var lorem=new Lorem(CurrentControl);
lorem.Control=CurrentControl;
alert(lorem.Amet());
}
};
for (var i=0; i<Controls.length; i++) {
Event("add", createHandlerFor(Controls[i]), "click", Controls[i]);
}
};

Categories

Resources