I'm getting an undefined message on all of my handlers. I want to bind a handler to every element and want to output the value. What is wrong with this code? Thanks!
for (var i = 0; i < document.forms[0].elements.length; i++ ){
document.forms[0].elements[i].onfocus = test(this);
}
function test(ele){
alert(ele.value);
}
You need to assign a function. At the moment you are assigning the return value of test(window) which is undefined.
onfocus = test;
Then reference the element inside the function:
function test(){
alert(this.value);
}
You need to change both the assignment and the function, since the element will no longer be passed in as a parameter, like this:
for (var i = 0; i < document.forms[0].elements.length; i++ ){
document.forms[0].elements[i].onfocus = test;
}
function test(){
alert(this.value);
}
As an event handler, this inside test will refer to the element you're dealing with, so just get the value from that.
The alternative version of your current approach would be the same test method, but with an anonymous function wrapper to pass the element itself as a parameter:
for (var i = 0; i < document.forms[0].elements.length; i++ ){
ddocument.forms[0].elements[i].onfocus = function() { test(this); };
}
function test(ele){
alert(ele.value);
}
As both Nick and David pointed out, the way you assign the event handler is not correct. However, to achieve what you are trying (pass in a context) you can use a delegate function. Like this:
for (var i = 0; i < document.forms[0].elements.length; i++ ){
var ele = document.forms[0].elements[i];
ele.onfocus = delegate(ele, test);
}
function delegate(obj, handler) {
return function () {
handler.call(obj);
}
}
function test() {
alert(this.value);
}
What the delegate function does, is call your handler function setting the context of this. See the documentation for the Function object for further information. For even further reading, I recommend The this keyword and Introduction to events on Quirksmode.
Related
I would like to create some objects dynamically and bind events to them (not important what events).
I'm passing a number to the event in order to distinguish those items. There is my code:
$('#add_btn').click(function() {
var cont = $('#buttons');
for(var i=0; i<5; i++) {
var new_btn = $('<input>').attr('type', 'button').val(i);
new_btn.click(function() {
alert(i);
});
cont.append(new_btn);
}
});
When I click on any from newly created buttons, displayed number is 5.
I think that i variable is passing by reference, but the question is: how to avoid passing variable by reference? More, even if I crate new variable before binding event (so the reference should point to another object, for example new_val = i.toString()), value is still same for all buttons (then its 4, understandable).
I know that I can attach new_btn.data() and read it in event, but I'm not sure if it won't be an overhead.
Link to jsfiddle: http://jsfiddle.net/Jner6/5/.
Since you are using a closure scoped variable in a loop, inside the loop you need to create a private closure.
$('#add_btn').click(function () {
var cont = $('#buttons');
for (var i = 0; i < 5; i++) {
(function (i) {
var new_btn = $('<input>').attr('type', 'button').val(i);
new_btn.click(function () {
alert(i);
});
cont.append(new_btn);
})(i)
}
});
Seems like you run into closures issue, try this:
(function( i ) {
new_btn.click(function() {
alert(i);
});
})( i );
This will create immediate invoked function that will closure your variable i so you can use it in future. For now you just overriding i variable in your for-loop so you will have always same value that will equal last for-loop iteration.
Don't make functions within a loop.
DEMO
var cont = $('#buttons');
$('#add_btn').click(function() {
for(var i=0; i<5; i++) {
$('<input>', {type:'button', value:i}).appendTo( cont );
}
});
cont.on('click', ':button', function() {
alert( this.value );
});
Okey so I got this following javaScript code.
function test(id)
{
alert(id);
}
var elem = document.getElementsByClassName('outsideDiv');
for(var i=0; i < elem.length; i++)
{
elem[i].addEventListener('mouseover', function(){test(i);}, false);
}
this gives all divs with the class a mouse over but the function always returns the latest i index. in this case i got 5 div elements and the alert is allways 5 no mather witch one i hover. Can anyone explain why?
Try using this instead:
function mouseOverFunc(i) {
return function () {
test(i);
};
}
function test(id) {
alert(id);
}
var elem = document.getElementsByClassName('outsideDiv');
for(var i=0; i < elem.length; i++) {
elem[i].addEventListener('mouseover', mouseOverFunc(i), false);
}
Just because you add event listeners to the elements doesn't mean the value of i is preserved for each listener. You need to create a closure that will create a new scope with i.
The reason this is happening is because the function bound to each listener is just a reference. When the event happens (mouseover), the function is finally called, but what's the value of i? The for loop finished executing a long time ago, so the value of i is the end value - 5.
I have the following code that adds an onmouseover event to a bullet onload
for (var i = 0; i <= 3; i++) {
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
}
This is the function that it is calling. It is supposed to add a css class to the menu item as the mouse goes over it.
function addBarOnHover(node) {
document.getElementById('menu').getElementsByTagName('li')[node].className = "current_page_item"; }
When the function is called, I keep getting the error:
"document.getElementById("menu").getElementsByTagName("li")[node] is
undefined"
The thing that is stumping me is I added an alert(node) statement to the addBarOnHover function to see what the value of the parameter was. The alert said the value of the parameter being passed was 4. I'm not sure how this could happen with the loop I have set up.
Any help would be much appreciated.
This is a common problem when you close over an iteration variable. Wrap the for body in an extra method to capture the value of the iteration variable:
for (var i = 0; i <= 3; i++) {
(function(i){ //here
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
})(i); //here
}
an anonymous function is created each time the loop is entered, and it is passed the current value of the iteration variable. i inside the anonymous function refers to the argument of this function, rather than the i in the outer scope.
You could also rename the inner variable for clarity:
for(var i=0; i<=3; i++){
(function(ii){
//use ii as i
})(i)
}
Without capturing the iteration variable, the value of i when it is finally used in the anonymous handler has been already changed to 4. There's only one i in the outer scope, shared between all instances of the handler. If you capture the value by an anonymous function, then the argument to that function is used instead.
i is being passed as a reference (not by value), so once the onmouseover callback is called, the value of i has already become 4.
You'll have to create your callback function using another function:
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('li');
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = (function(i) {
return function() {
addBarOnHover(i);
};
})(i);
}
You could make it a little more readable by making a helper function:
var createCallback = function(i) {
return function() {
addBarOnHover(i);
};
};
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = createCallback(i);
}
How to pass a parameter to a event's handler?
Here's what am trying to do, but it doesn't work:
for (var i = 0; i < myobj.length; i++) {
myobj[i].onmouseover = myfun(myobj[i]);
}
The following doesn't work neither:
myobj[i].onmouseover = myfun.call(myobj[i]);
myobj[i].onmouseover = function () {myfun(myobj[i]);};
myobj[i].onmouseover = function () {myfun.call(myobj[i]);};
Am primarily interested in why it doesn't work, and solution in the same style.
Just use a creator function for your handlers to encapsulate the parameter to pass on.
function createHandler( param ) {
return function() {
myfun( param );
}
}
for (var i = 0; i < myobj.length; i++) {
myobj[i].onmouseover = createHandler( myobj[i] );
}
The reason your approach doesn't work is, because you don't pass on a function reference, but the result of a function call. So in your first example myfun( myobj[i] ) is evaluated and the result is passed on as the event handler.
I think, what you really mean is, that in case the event is fired, the function shall be evaluated. To do so you either have to pass the parameter via some global var or as a dataset property.
The cleaner solution, however, is to have a generator function as shown above.
Another approach is to use the fact that onmouseover will be invoked as a method (not a function) on the DOM element which fires the event.
In other words, write your code as if you expected someone to do this:
obj = xahlees();
obj.onmouseover();
Here's a solution:
for (var i = 0; i < myobj.length; i++) {
myobj[i].onmouseover = function() { myFun(this) };
}
I've uploaded a more complete example .
I know this kind of question gets asked alot, but I still haven't been able to find a way to make this work correctly.
The code:
function doStuff () {
for (var i = 0; i< elementsList.length; i++) {
elementsList[i].previousSibling.lastChild.addEventListener("click", function(){
toggle(elementsList[i])}, false);
}
} // ends function
function toggle (element) {
alert (element);
}
The problem is in passing variables to the toggle function. It works with the this keyword (but that sends a reference to the clicked item, which in this case is useless), but not with elementsList[i] which alerts as undefined in Firefox.
As I understood it, using anonymous functions to call a function is enough to deal with closure problems, so what have I missed?
Try:
function startOfFunction() {
for (var i = 0; i< elementsList.length; i++) {
elementsList[i].previousSibling.lastChild.addEventListener(
"click",
(function(el){return function(){toggle(el);};})(elementsList[i]),
false
);
}
} // ends function
function toggle (element) {
alert (element);
}
The Problem is, that you want to use the var i! i is available in the onClick Event, (since closure and stuff). Since you have a loop, i is counted up. Now, if you click on any of the elements, i will always be elementsList.length (since all event functions access the same i )!
using the solution of Matt will work.
As an explanation: the anonymous function you use in the for loop references the variable "i" to get the element to toggle. As anonymous functions use the "live" value of the variable, when somebody clicks the element, "i" will always be elementsList.length+1.
The code example from Matt solves this by sticking the i into another function in which it is "fixated". This always holds true:
If you iterate over elements attaching events, do not use simple anonymous functions as they screw up, but rather create a new function for each element. The more readable version of Matts answer would be:
function iterate () {
for (var i = 0; i < list.length; i++) {
// In here, i changes, so list[i] changes all the time, too. Pass it on!
list[i].addEventListener(createEventFunction(list[i]);
}
}
function createEventFunction (item) {
// In here, item is fixed as it is passed as a function parameter.
return function (event) {
alert(item);
};
}
Try:
function doStuff () {
for (var i = 0; i< elementsList.length; i++) {
(function(x) {
elementsList[x].previousSibling.lastChild.addEventListener("click", function(){
toggle(elementsList[x])}, false);
})(i);
}
} // ends function
I think it might be an issue with passing elementsList[i] around, so the above code has a closure which should help.