Reputation: 812
I'm discovering Raphael JS and I have a question about mouse events applied on elements generated in for loops.
Here is a sample code that prints 5 rectangles :
var paper = Raphael("canvasTest",200,200);
for(var i = 0 ; i < 5 ; i++){
var rect = paper.rect((i*15)+5,10,10,10);
rect.attr("fill","blue");
rect.hover(
function(){
rect.attr("fill","red");
document.getElementById("info").innerHTML="Hovered: "+i;
},
function(){
rect.attr("fill","blue");
}
);
}
This generates the following output :
The issue is that only the 5th rectangle becomes red (even when another one is hovered) and the info
div always prints that the 5th rectangle is hovered :
I found that I can use this
inside the inner function and it partially solves the problem. The code above colors the correct rectangle in red, but the info container still prints that the 5th rectangle is hovered.
rect.hover(
function(){
this.attr("color","red");
...
},
...
);
What is the cleanest way to solve this ? I'm thinking about setting custom fields for rect
such as rect.someVariable=i
and then referring to it in the inner function. But there is perhaps something cleaner ?
Thanks in advance for your help.
Upvotes: 0
Views: 696
Reputation: 4433
I'd recommend using either of Chris Wilson's techniques under most circumstances, but there is a variation which accomplishes the same aim without polluting your current namespace with a debri field of utility functions. Simply put, instead of creating a closure with a formally defined function, simply create one using an anonymous function inside your loop:
for(var i = 0 ; i < 5 ; i++) {
var rect = paper.rect((i*15)+5,10,10,10);
rect.attr("fill","blue");
( function( rect, i ) {
rect.hover(
function() {
rect.attr("fill","red");
document.getElementById("info").innerHTML="Hovered: "+i;
},
function() {
rect.attr("fill","blue");
}
); } )( rect, i );
}
Such a construct simply embeds particular instances of your loop variable i
into a closure created within the constraints of your for loop. Easy, peasy.
Upvotes: 3
Reputation: 6719
"Don't create functions in a loop" is the Javascript equivalent of "Don't make the first or third out at third" in baseball: You can violate it, but only if you have a good reason. You don't. So you should make a closure:
var paper = Raphael("canvasTest",200,200);
var mybox = function(i) {
var rect = paper.rect((i*15)+5,10,10,10);
rect.attr("fill","blue");
rect.hover(
function(){
this.attr("fill","red");
document.getElementById("info").innerHTML="Hovered: "+i;
},
function(){
this.attr("fill","blue");
}
);
return {
get_rect: function() { return rect; }
};
};
for(var i = 0 ; i < 5 ; i++){
mybox(i);
}
Doug Crockford's "Javascript: The Good Parts" is the best place to start to understand why. If you really don't want to do this for some reason, you can also make i a property of the object with Raphael's .data() method:
var paper = Raphael("canvasTest",200,200);
for(var i = 0 ; i < 5 ; i++){
var rect = paper.rect((i*15)+5,10,10,10);
rect.data("myindex", i + 1);
rect.attr("fill","blue");
rect.hover(
function(){
this.attr("fill","red");
document.getElementById("info").innerHTML="Hovered: " + this.data("myindex");
},
function(){
this.attr("fill","blue");
}
);
}
I highly recommend the first way.
Upvotes: 3