addEventListener Created an Infinite Loop - javascript

I added an event listener and it created an infinite loop I really don't get what I did wrong.
What happen is it keeps clicking the images by itself
Here's my code:
function attachOnClickEvent(cases,theme,debut,capture){
var images=document.getElementsByTagName('img');
for(var i=0;i<images.length;i++){
images[i].addEventListener("click",x(cases,theme,debut,capture),false);
}
};
var x=function(cases,theme,debut,capture){newImage(this,cases,theme,debut,capture);};
function newImage(elem,cases,theme,debut,capture){
var images=preloadImages(cases,theme);
var imgTab=document.getElementsByTagName('img');
var Id=elem.id;
elem.src=images[randomTab[Id]];
};
Hope someone can find my mistake.. Thanks!

getElementsByTagName is returning a live NodeList
You are invoking your function x each time instead of adding it as a handler
A new <img> is created
the NodeList has it's length increased by 1
The next iteration is the same distance away from the end as the previous one
There are two things you need to consider, firstly as I mention in 2, you're invoking x when you aren't meaning to. Secondly, you may avoid some problems with live lists by looping downwards
One fix to get your desired result may be to rewrite attachOnClickEvent like this
function attachOnClickEvent(cases, theme, debut, capture) {
var images = document.getElementsByTagName('img'),
handler = function (e) {
return x(cases, theme, debut, capture);
},
i;
for (i = images.length - 1; i >= 0; --i) {
images[i].addEventListener("click", handler, false);
}
}

One issue would seem to be that you are not using callbacks properly.
Callbacks need to be function objects and cannot be returned as you are intending.
function (cases, theme, debut, capture) {
return function() { newImage(this, cases, theme, debut, capture); };
}
Try returning an anonymous function.

Would be better to see how you're calling these functions, but I noticed that on this line:
images[i].addEventListener("click",x(cases,theme,debut,capture),false);
You are calling your function x, rather than assigning it as the event listener. Since that function adds an image to the page, the loop never completes, since it iterates through all images on the page. This line should instead be:
images[i].addEventListener("click", x , false );

Related

Event listener is only being assigned to the first class that is called

I am attempting to add event listeners to the appropriate html elements.
var eventArray = [['scribbler', 'click', addNote], ['checkbox', 'change', strikethrough]];
for(var i=0;i<=eventArray.length; i++){
addEvents(eventArray[i][0], eventArray[i][1], eventArray[i][2]);
}
The above function loops through the provided array and supplies the addEvents function with the proper parameters.
function addEvents(className, event, fn){
var g = document.getElementsByClassName(className);
for(var i=0; i<=g.length; i++){
g[i].addEventListener(event, fn, false);
}
}
This is the addEvents function that is called in the for loop. It works for the first run-through, but anything after that seems to be ignored. I.e. the scribbler/click event listener is properly applied but the checkbox/change one is not. If I reverse the order of the array then the properly applied listeners are also flipped.
I have tried hard resetting the g variable at the end of the addEvents function but that did not seem to work.
document.getElementsByClassName(className).each.addEventListener(event, fn, false);
This does not seem like an appropriate solution.
you should replace <= in your code with < as you are probably getting some error like Cannot read property addEventListener of undefined
the reason for that change is that if your array has, let's say, 3 elements, their indexes are: 0, 1, 2, and array.length is 3
with <= your loop will iterate from 0 to 3 inclusive, giving you undefined

Delay a For Loop

I am trying to create a FOR loop that removes an element every 1000ms instead of rushing instantaneously through the array and perform the operations.
I am doing this for reasons of performance since going normally through the loop freezes my UI.
function removeFunction (i,selected) {
selected[i].remove();
}
function startLoop() {
var selected = paper.project.selectedItems;
for (var i = 0; i < selected.length; i++) {
if (selected[i].name === "boundingBoxRect") continue;
setTimeout(removeFunction(i,selected),1000)
}
}
It seems that the selected[i].remove() method is getting called without any delay. Why is that? Since I have set a Timeout of 1000ms shouldn't the items get removed with 1000ms interval between each?
Note
In the code above, I am skipping an item called boundingBoxRect since I don't want to remove that. Just stating this so there is no confusion
Simply turn it into a recursive function:
function removeFunction (i, selected) {
// If i is equal to the array length, prevent further execution
if (i === selected.length)
return;
// Remove ith child of selected array
selected[i].remove();
// Trigger same function after 1 second, incrementing i
setTimeout(function() {
removeFunction(++i,selected)
}, 1000);
}
// Trigger the function to begin with, passing in 0 as i
removeFunction(0, paper.project.selectedItems);
Here's a JSFiddle demo (using console.log instead of selected[i].remove(), as you haven't provided a definition for that function);
It seems that the selected[i].remove() method is getting called without any delay. Why is that?
Because that's what you're telling it to do. setTimeout(removeFunction(i,selected),1000) calls removeFunction immediately and passes its return value into setTimeout, exactly the way foo(bar()) calls bar and passes its return value into foo.
You can get the effect you want by using a builder function:
setTimeout(buildRemover(i,selected),1000);
...where buildRemover is:
function buildRemover(index, array) {
return function() {
removeFunction(index, array);
};
}
Note how buildRemover creates a function that closes over the index and array variables. Then it returns a reference to that function, which is what gets scheduled via setTimeout. When the timeout occurs, that generated function is called, and it calls removeFunction with the appropriate values.
You can also do something similar using ES5's Function#bind:
setTimeout(removeFunction.bind(null, i, selected),1000);
Function#bind returns a new function that, when called, will call the original (removeFunction above) use the given this value (null in our example) and arguments.

Adding a listener to an array of elements, need unique arguments for function on each item

I have a bit of HTML generated by PHP in the format of:
<div class=zoomButton>
<input type=hidden name=zoomURL value=*different per instance*>
</div>
I am trying to attach a listener (imageZoom(event, url)) to each of the class "zoomButton" elements, but call it with different arguments for each instance.
i.e.
var zoomButtonArray = document.getElementsByClassName('zoomButton');
for (i=0; i<zoomButtonArray.length; i++)
{
var zoomURL = zoomButtonArray[i].children[0].value;
zoomButtonArray[i].addEventListener("mousedown", function(){imageZoom(event,zoomURL);});
}
however it seems that zoomURL is always the value of the very last element. How can I change my code/approach so that the argument passed to the listener is the correct one, and not the last one in the "zoomButtonArray" array?
Thanks
You need to wrap the event listener in a closure:
function makeEventListenerForZoomURL(zoomURL) {
return function(event) {
imageZoom(event, zoomURL);
}
}
var zoomButtonArray = document.getElementsByClassName('zoomButton');
for (i=0; i<zoomButtonArray.length; i++)
{
zoomButtonArray[i].addEventListener(
"mousedown",
makeEventListenerForZoomURL(zoomButtonArray[i].children[0].value)
);
}
This can also be simplified using the ECMAScript5 forEach:
var zoomButtonArray = document.getElementsByClassName('zoomButton');
zoomButtonArray = Array.prototype.slice.call(zoomButtonArray, 0);
zoomButtonArray.forEach(function(node) {
node.addEventListener("mousedown", function(event) {
imageZoom(event node.children[0].value);
});
});
The reason is that each time the for loop executes a new function is created, this new scope references the variable i but i changes each time the loop iterates. So by the time the event listener runs it looks at the value of i only to find that it is the last value when the for loop ended. By using a closure described above the scope created is unique to each iteration of the loop so that when the event listener finally executes the value of the wrapped variable (zoomURL or node in the examples above) will not have changed.
Here is a good article explaining closures in for loops: http://trephine.org/t/index.php?title=JavaScript_loop_closures
I think you are missing quotes around attributes. I just added quotes and the tested at jsFiddle (Fiddle link in comments) and it's working see to console in developer tool. it is iterating through each element as desired. Console screen shot

Javascript: Async nested function in other async function gives unexpected results

I have two asynchronous functions the one nested in the other like this:
//Async 1
document.addEventListener("click", function(event){
for (var i in event){
//Async 2
setTimeout(function(){
console.log(i);
}, 200*i);
}
});
What I want is to be able and print each entry(i) of the event object. The output on Firefox is however this:
MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
..
If I move console.log(i) outside Async 2 then I get the correct result:
type
target
currentTarget
eventPhase
bubbles
cancelable
..
Why doesn't it work correctly when reading the i inside async 2? Shouldn't event be "alive" inside the whole Async 2 block of code as well?
setTimeout uses i as it appears in async1. That is, it references i instead of using the value of i when the timeout function is created. When your timeout function finally runs, it looks at the current value of i, which is the last key in event after the for-loop.
You can avoid this by using a self-calling function, such that the arguments of that function are local to the timeout function:
for (var i in event) {
setTimeout((function(i) {
return function() { console.log(i); }
})(i), 200 * i);
}
Use this:
document.addEventListener("click", function(event){
var count = 1;
for (var i in event){
//Async 2
setTimeout((function(i){
return function () {
console.log(i);
};
})(i), 200 * count++);
}
});
DEMO: http://jsfiddle.net/AQykp/
I'm not exactly sure what you were going for with 200*i, since i is a string (not even digits). But I tried to fix it with counter in my answer, assuming what you really wanted.
So the reason you were seeing the results you were was because of the common closure problem. The setTimeout callback will execute after the for loop completes, leaving i as the last key in event. In order to overcome that, you have to add a closure that captures the value of i at that point in the loop.
Event is indeed alive inside your callback.
The problem is that, by the time your function is executed, the value of i has changed (from what I can see from the output, the loop has ended and i has reached its maximum value) and thus outputting the same value for every callback.
If you use the function Ian commented, you will curry the actual value into a new function. That way the value of i can vary, you captured the current value inside the inner function

Why is this not an infinite loop?

I'm reading this this example and I'm stuck at understanding one line. I need to understand everything so I can't move on.
This function is supposed to hide all the elements inside an object. It's supposed to work. But to me, that for loop looks like an infinite one. Why is it not?
getChild: function (i) {
return this.children[i];
},
hide: function () {
for (var node, i = 0; node = this.getChild(i); i++) {
node.hide();
}
this.element.hide(0);
},
From what I see, the function takes the first element of the object with getChild(0) and then calls hide again on that 0-dimension object. Then it resets the counter (i) and gets the first element of the 0-dimension object (which is the same 0-dim object) and calls the function again.
I know I'm mistaken but that's what I see. Please show me the light! Thanks
In a for loop like the one above, the first bit (var node, i = 0) is only executed once, at the beginning of the loop. The loop stops executing when the middle section (node = this.getChild(i);) returns false. getChild will return false when there isn't anything at index i. (Technically, it'll return undefined, but that equates to false in this instance).
Secondly, even though hide() is called in the for loop, i is not reset. Why? This recursive call creates a new instance of hide() separate from the original. All of the variables in this new hide() are separate from the original. (and so on, down the rabbit hole).
See http://www.tizag.com/javascriptT/javascriptfor.php for more information on for loops.
The variable i is not reset on each iteration. The only actions that are recurisvely executed are the boolean expression and i++. node.hide() is not the same as this.hide(). The latter is a different function being called. If it were the same function, then yes, there would be an infinite loop.
The "outer" hide function is being used to "hide" all the elements in this.getChild(i). node.hide() will call the hide() method on those elements so they are hidden. There is no infinite loop because node.hide(), although it has the same name as the function it's being used in, is not the same function.
The code
node.hide();
is still a member of the tree and still traversable. It is just hidden from being displayed.
The initialization part of the for loop
var node, i=0
is executed only once, before the looping begins.
The conditional
node = this.getChild(i)
evaluates to true (non-null) when there is a child node, and false (null) when it has run out of descendants, thereby breaking out of the loop.
If there is no child at i, getChild will return undefined and break out of the loop.
Consider the following text from the article:
Now create the GalleryImage class. Notice that it uses all of the exact same methods as the GalleryComposite. In other words, they implement the same interface, except that the image is a leaf so it doesn't actually do anything for the methods regarding children, as it cannot have any. Using the same interface is required for the composite to work because a composite element doesn't know whether it's adding another composite element or a leaf, so if it tries to call these methods on its children, it needs to work without any errors.
And consider the constructor for GalleryImage:
var GalleryImage = function (src, id) {
this.children = [];
this.element = $('<img />')
.attr('id', id)
.attr('src', src);
}
And how the images and composites are constructed:
var container = new GalleryComposite('', 'allgalleries');
var gallery1 = new GalleryComposite('Gallery 1', 'gallery1');
var gallery2 = new GalleryComposite('Gallery 2', 'gallery2');
var image1 = new GalleryImage('image1.jpg', 'img1');
var image2 = new GalleryImage('image2.jpg', 'img2');
var image3 = new GalleryImage('image3.jpg', 'img3');
var image4 = new GalleryImage('image4.jpg', 'img4');
gallery1.add(image1);
gallery1.add(image2);
gallery2.add(image3);
gallery2.add(image4);
container.add(gallery1);
container.add(gallery2);
Since an image cannot contain children, its this.children will remain an empty array. So, when the hide function finally gets called on an image (at one of the leaves of the composite tree), the loop will attempt to evaluate this.children[0] which will return undefined. This will cause the code node = this.getChild(i) to evaluate to a "false" value, and that particular for loop will terminate. Thus preventing an endless loop.

Categories