How to pass a parameter to a event's handler - javascript

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 .

Related

JavaScript get element from event trigger

I'm currently trying to programm my first Website.
Therefore I want to get all Elements with a certain class an give them all the same EventListeners.
I did that like this:
const certs = document.getElementsByClassName("certificate");
for (var i = 0; i < certs.length; i++) {
certs[i].addEventListener("mouseover", mouseOver());
certs[i].addEventListener("mouseout", mouseOut());
}
function mouseOver() {
this.querySelector(".overlay").classList.add("active");
}
function mouseOut() {
this.querySelector(".overlay").classList.remove("active");
}
My Problem is, "this" doesnt seem to be the element which is triggering the mouseOver event.
I also tried puting "this" as a parameter like here:
const certs = document.getElementsByClassName("certificate");
for (var i = 0; i < certs.length; i++) {
certs[i].addEventListener("mouseover", mouseOver(this));
certs[i].addEventListener("mouseout", mouseOut(this));
}
function mouseOver(elem) {
elem.querySelector(".overlay").classList.add("active");
}
function mouseOut(elem) {
elem.querySelector(".overlay").classList.remove("active");
}
Both ways didnt work and know I am stuck in a way...
Is there a way to do this?
How can I use the exact element triggering the event?
(I dont want to give every element a unique ID to make it reusable)
The second argument of addEventListener is a function. Right now, you're calling the function immediately, which returns undefined, so whenever you move your mouse over or out it attempts to run undefined. Pass just the function variable itself, addEventListener will handle calling the function later.
addEventListener calls the callback function with an Event object, which has the property target which is how you get your element.
const certs = document.getElementsByClassName("certificate");
for (var i = 0; i < certs.length; i++) {
certs[i].addEventListener("mouseover", mouseOver);
certs[i].addEventListener("mouseout", mouseOut);
}
function mouseOver(event) {
event.target.querySelector(".overlay").classList.add("active");
}
function mouseOut(event) {
event.target.querySelector(".overlay").classList.remove("active");
}

Javascript: Cannot call method of undefined

This may be a common error, but I can't find another way of doing this. I'm creating a timeline showing multiple projects using timeline.js with the following code
function createTimeline(){
for(var length = data.section.length,i = 0; i < length; i++){
var divWrapper = document.createElement("div");
if(i != data.section.length - 1)
divWrapper.setAttribute('class','timelineWrapper');
$('body').append(divWrapper);
layer[i] = new links.Timeline(divWrapper);
links.events.addListener(layer[i], 'rangechange',
function(){
var that = this;
var range = layer[i].getVisibleChartRange();
for (var j = 0; j < layer.length; j++){
if(j != i){
layer[j].setVisibleChartRange(range.start, range.end);
}
}
}
);
layer[i].draw(data.section[i].project, options);
}
}
It gives the error Cannot call method 'getVisibleChartRange' of undefined .
What is the problem here? Why is layer[i] undefined? It is not being detected during the event rangechange itself.
You must bind i within a closure to save its value for your addListener call, as i is undefined when the function in your addListener later gets called. Try replacing the third argument of your addListener call with the below:
(function(i) {
return function() {
var that = this;
var range = layer[i].getVisibleChartRange();
// Rest of code
};
}(i)); // Anonymous function binds i in a closure
The issue is caused because the unnamed function used as event handler uses its parent scope's i variable.
At the end of your loop, i==data.section.length in this scope.
This is also the i value for all your events handlers. Because of this, layer[i] is undefined, and this is causing the error message.
The easiest way to address this issue is to create a functionBuilder function taking i as parameter and returning a new function (your handler).
In this returned handler's direct parent's scope, i value will be the parameter you passed to the functionBuilder function.
I'll post some code example later today, as soon as I have access to a pc (no way I can type that from a tablet :o) )
EDIT: I've been too slow... mc10 posted more or less what I wanted to post :o)
In case you don't understand why this works, or what closures, scopes or bind means, here is an old but complete explanation:
http://blog.niftysnippets.org/2008/02/closures-are-not-complicated.html

Why does my anonymous function not have the context of the current object in this example?

When I run this sample code in Google Chrome, the intended behavior--loading an image within a placeholder image tag on the current page--does not occur. I checked the value of currPic when showPic() is called, and it is "undefined." I know if I change the parameter to showPic from 'anchors[i]' to 'this', then it will work, but was trying to understand why this is so.
function showPic(currPic) {
var srcLoc = currPic.getAttribute("href");
var placeHolder = document.getElementById("placeholder");
placeHolder.setAttribute("src", srcLoc);
var imgLabel = document.getElementById("imglabel");
var currLinkTitle = currPic.getAttribute("title");
imgLabel.firstChild.nodeValue = currLinkTitle;
}
function prepareGallery() {
if(!(document.getElementsByTagName && document.getElementById)) return false;
var imgGallery = document.getElementById("imagegallery");
if(imgGallery) {
var anchors = imgGallery.getElementsByTagName("a");
var i;
for(i = 0; i < anchors.length; i++) {
anchors[i].onclick = function() {
showPic(anchors[i]);
return false;
}
}
}
}
Inside the anonymous function, anchors[i] provides a runtime reference. At the time the click occurs, anchors[i] no longer exists. While it existed at the time the assignment was made, it falls out of scope at the time of the click (since it's just an array reference). However, using this provides a solid reference to the immediate object that is always available at the time of the click.
More succinctly, anchors[i] is a reference to a position in an array (which leaves scope once the for loop exits). this is a reference to the dom element itself.
Because this would also work: showPic(document.getElementById(anchors[i].id)); - do you "get" it now (pun very much intended)?
Didn't see the obvious statement regarding how closures work, so here's my take on it.
var i;
for(i = 0; i < anchors.length; i++) {
anchors[i].onclick = function() {
showPic(anchors[i]);
return false;
}
}
Notice how you reference the i variable inside the loop? By the end of your loop, the value of i equals anchors.length.
So, when any of your onclick function is executed, that reference to i now points one position past the last index of anchors; this is why you see currPic is undefined.
One solution to this problem has been given in other answers: use this to reference the current anchor and don't pass anchors[i] to the onclick function.
As you may encounter similar situations, I'll show you another solution by closing over the value of i like so:
var i;
for(i = 0; i < anchors.length; i++) {
anchors[i].onclick = (function(i) {
// inside this function, i is closed over and won't change anymore
return function() {
showPic(anchors[i]);
return false;
}
}(i));
}

Javascript Closure Problem

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.

adding onfocus event handler to every element

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.

Categories