I'm trying to iterate through an array and assign values to an element as such:
<tool>hammer</tool>
var tools = ["screwdriver", "wrench", "saw"];
var i;
for (i=0; i < tools.length; ++i){
$("tool").delay(300).fadeOut().delay(100).html(tools[i]).fadeIn();
};
However this doesn't seem to work as only "saw" is assigned as the html value and keeps fading in and out.
What am I doing wrong here?
jQuery.delay() pauses between effects queued items, so your .html() is being set instantaneously. Hence, you only see saw.
A solution is to scrap the for loop and "loop" against the length of the array, setting the tool text to the next first item in the array (as you remove it). However, you need to do this inside the context of the queue, so you can use the .fadeOut() callback to do this.
Wrap all this in a function (here, I immediately invoke it but give it a label, a, so it can be referenced, it's not anonymous) and pass that to .fadeIn() at the end so it continues the loop until the array is empty.
var tools = ["screwdriver", "wrench", "saw"];
(function a(){
if (tools.length) {
$("tool").delay(300).fadeOut(0, function(){
$(this).html(tools.shift());
}).delay(100).fadeIn(a);
}
})();
http://jsfiddle.net/tc1q1vv7/1/
Related
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 );
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.
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
How do I increment an integer inside a variable, every time that variable is called? Javascript.
var a=0;
var t=loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+x[a].getElementsByTagName("name")[0].childNodes[0].nodeValue+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en").getElementsByTagName("bio");
for (i=0;i<20;i++)
{
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=t[0].getElementsByTagName(\"summary\")[0].childNodes[1].nodeValue;'>Open Bio</button></div>");
}
I'm not sure how I would go about incrementing variable a. I need it to increase by 1 every time variable t is called in the for loop.
When I put all of the code in the for loop I get [object node list] returned so this method is not desired.
If I understood your question correctly, you could define your own getters and setters for the property.
var o = {}
o.__defineSetter__('property', function(value) { this._counter = 0; this._holder = value; })
o.__defineGetter__('property', function() { console.log(this._counter++); return this._holder; })
The counter would be reset every time o.property is assigned a value
o.property = 'Some value'
and then increase every time the property is accessed.
So,
console.log(o.property)
would print
0
Some value
to the console. And if you do it again, it would print
1
Some value
After your edit I think I can see your problem now. You will need to put the loadXMLDoc statement in the loop (since you want to load 20 different XML files), but you can't assign the result of every call to the same variable t - as once the button is clicked, the handler will evaluate t and get only the last value.
Instead, use an array:
var bios = []; // empty array
for (var i=0; i<20; i++) {
var artist = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue,
doc = loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+artist+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en"),
bio = doc.getElementsByTagName("bio")[0].getElementsByTagName("summary")[0].childNodes[1].nodeValue;
bios[i] = bio; // store it in the array
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=bios["+i+"];'>Open Bio</button></div>");
}
Of course, while that will work it's a bunch of bad practises, including
unsecured accessing of DOM nodes/properties. If the xml changes its format, you will get lots of exceptions here. You might be sure now that this never happens, but wrapping artist and bio in try-catch might not be a bad idea.
snychronous Ajax. One can do better than that.
loading 20 documents (and that sequentially!) even if you don't need them. It might be worth to try loading each of them only when the respective button is clicked.
document.write
Inline attribute event handlers
…and creating them even by JS.
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.