Assign a Function Argument with a Loop - javascript

I have an array of list items in a piece of Javascript code. I would like to assign an onclick event handler to each one. Each handler would be the same function, but with a different input argument. Right now I have:
function contentfill(i) {
box = document.getElementById("text");
box.style.background="rgba(0,0,0,0.8)";
var content = new Array();
contentdivs = document.querySelectorAll("#contentfill>div");
box.innerHTML = contentdivs[i].innerHTML;
}
li[3].onclick = function() {contentfill(0);};
li[4].onclick = function() {contentfill(1);};
li[5].onclick = function() {contentfill(2);};
This works well enough, but I would like to achieve the same thing with a loop, for example:
for(i=3;i<=5;i++) {
j=i-3;
li[i].onclick = function() {contentfill(j);};
}
This, however, does not work. Since j seems to be defined as 2 at the end of the loop, each time I click, it only seems to call contentfill(2).

For an alternative approach, consider having each of the elements aware of what argument it should be using.
for (var i = 0; i < 3; i++) {
var el = li[i + 3];
el.dataset.contentIndex = i;
el.addEventListener('click', contentfill);
}
Then contentfill would have to extract the argument from .dataset instead of taking an argument, of course. (This is the same mechanism as jQuery's $.data.)
I tend to prefer this since (a) it doesn't generate tons of tiny wrappers, (b) it allows me to later examine and possibly change the "arguments", and (c) it lets me predefine them in the document using data- attributes. Effectively changes them from function arguments into behavior.

The value of i - 3 should be bound to the click handler function; a closure can provide this functionality:
li[i].onclick = (function(j) {
return function() {
contentfill(j);
}
)(i - 3));
Btw, it's better practice to use addEventListener or attachEvent to register click handlers.

Related

Not able attach click event for a button using Javascript oop

I'm working on attaching a click event on a button which is having class "nextPage", but its not working. Let me show you the code.
function myContent() {
}
myContent.prototype.clickNext = function() {
alert("clicked");
};
var objMyContent = new myContent();
var el = document.getElementsByClassName('nextPage');
el.onclick=objMyContent.clickNext();
Please take a look into it. Please let me know where I did mistake.
You need to reference the function, not execute it, when assigning the click handler.
Instead of:
el.onclick = objMyContent.clickNext();
Use this:
el.onclick = objMyContent.clickNext;
The first piece of code executes clickNext, and assigns it's return value to el.onclick.
The second line assigns a reference to the clickNext function to el.onclick, instead. (Which is what you want)
Also, getElementsByClassName returns an HTMLCollection (Which is basically an array of HTML elements).
You'll need to assign the click handler to each found element in that collection:
for(var i = 0; i < el.length; i++){
el[i].onclick = objMyContent.clickNext;
}
iterate the array and the assign the onclick event
var objMyContent = new myContent();
var el = document.getElementsByClassName('nextPage'); for(var i=0;i<el.length;i++) el[i].onclick=objMyContent.clickNext;

Why doesn't this javascript bind the correct parameters with the event?

In this code I am supposed to bind a rollover effect to each <area> tag in a <map> element.
function initLinks(webrt) {
var areas = document.querySelectorAll("map#streetmap > area");
var links = new Array(areas.length);
for (var i=0; i<links.length; i++) {
links[i] = new Image(786,272);
links[i].src = webrt+"templates/default/sketches/links"+(i+1)+".png";
areas[i].onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(i+1)+".png");};
areas[i].onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
}
}
Strangely, each <area> onmouseover event tries to load the non-existing image: /templates/default/sketches/links6.png. Why does it keep this variable i which has incremented to 6 as a global variable rather than take the string I am passing to the function?
How do I fix this?
Note: No jQuery!
i often find it cleaner to use array methods when using the index because you don't need extra wrappers and everything reads a little cleaner (imho):
function initLinks(webrt) {
[].forEach.call(document.querySelectorAll("map#streetmap > area"),
function(elm, index){
var img = new Image(786,272);
img.src = webrt+"templates/default/sketches/links"+(index+1)+".png";
elm.onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(index+1)+".png");};
elm.onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
});
}
the variable count is way down, and we avoid extra ram-hogging closures by not creating a extra new function in each iteration of the "loop".
to be sure, both ways work, but the newer array methods can also allow the procedure to be recycled by ripping it out of the forEach() call, and giving it a name.
Try using the following code:
function initLinks(webrt) {
var areas = document.querySelectorAll("map#streetmap > area");
var links = new Array(areas.length);
for (var i=0; i<links.length; i++) {
(function(index) {
links[index] = new Image(786,272);
links[index].src = webrt+"templates/default/sketches/links"+(index+1)+".png";
areas[index].onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(index+1)+".png");};
areas[index].onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
})(i);
}
}
You should wrap the i variable into a closure. Otherwise it gets incremented.

generating dynamic onclick events with javascript

I am dynamically generating a series of onclick events where an alert() is associated with loop number of the pretended content. My problem is that currently the alerts outputs the 'i' value of the last loop rather than the i'th loop associated with the pretended content. Any thoughts?
JavaScript:
for (i = 1; i < 4; i++) {
prepend_content = 'foo';
$('#dynamic_div').prepend(prepend_content);
}
Many thanks.
Try concatenating it like you do before:
for (i = 1; i < 4; i++) {
prepend_content = 'foo';
$('#dynamic_div').prepend(prepend_content);
}
You might want to declare i and prepend_content (with var) in case you already haven't, to make sure they don't leak into the global scope.
At the same time, I wouldn't suggest using or adding HTML with inline event handlers. Try creating the element like this:
prepend_content = $("<a>").attr({
href: "#",
id: "img1_link_" + i
}).text("foo").on("click", (function (i) {
return function () {
alert(i);
};
})(i));
DEMO: http://jsfiddle.net/ujv4y/
The extra use of the immediately invoked function for the click handler is to make a closure that captures the value of i in the loop.
You can create a function using currying for the alert (for more complex stuff):
function(i) {
return function(){alert(i);}
}

Javascript multiple dynamic addEventListener created in for loop - passing parameters not working

I want to use event listeners to prevent event bubbling on a div inside a div with onclick functions. This works, passing parameters how I intended:
<div onclick="doMouseClick(0, 'Dog', 'Cat');" id="button_id_0"></div>
<div onclick="doMouseClick(1, 'Dog', 'Cat');" id="button_id_1"></div>
<div onclick="doMouseClick(2, 'Dog', 'Cat');" id="button_id_2"></div>
<script>
function doMouseClick(peram1, peram2, peram3){
alert("doMouseClick() called AND peram1 = "+peram1+" AND peram2 = "+peram2+" AND peram3 = "+peram3);
}
</script>
However, I tried to create multiple event listeners in a loop with this:
<div id="button_id_0"></div>
<div id="button_id_1"></div>
<div id="button_id_2"></div>
<script>
function doMouseClick(peram1, peram2, peram3){
alert("doMouseClick() called AND peram1 = "+peram1+" AND peram2 = "+peram2+" AND peram3 = "+peram3);
}
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
for (var i=0; i<names.length; i++){
document.getElementById(names[i]).addEventListener("click", function(){
doMouseClick(i, "Dog", "Cat");
},false);
}
</script>
It correctly assigns the click function to each div, but the first parameter for each, peram1, is 3. I was expecting 3 different event handlers all passing different values of i for peram1.
Why is this happening? Are the event handlers not all separate?
Problem is closures, since JS doesn't have block scope (only function scope) i is not what you think because the event function creates another scope so by the time you use i it's already the latest value from the for loop. You need to keep the value of i.
Using an IIFE:
for (var i=0; i<names.length; i++) {
(function(i) {
// use i here
}(i));
}
Using forEach:
names.forEach(function( v,i ) {
// i can be used anywhere in this scope
});
2022 edit
As someone is still reading and upvoting this answer 9 years later, here is the modern way of doing it:
for (const [i, name] of names.entries()) {
document.getElementById(name).addEventListener("click", () => doMouseClick(i, "Dog", "Cat"), false);
}
Using const or let to define the variables gives them block-level scope and the value of i passed to the handler function is different for each iteration of the loop, as intended.
The old ways will still work but are no longer needed.
2013 answer
As pointed out already the problem is to do with closures and variable scope. One way to make sure the right value gets passed is to write another function that returns the desired function, holding the variables within the right scope. jsfiddle
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
function getClickFunction(a, b, c) {
return function () {
doMouseClick(a, b, c)
}
}
for (var i = 0; i < names.length; i++) {
document.getElementById(names[i]).addEventListener("click", getClickFunction(i, "Dog", "Cat"), false);
}
And to illustrate one way you could do this with an object instead:
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
function Button(id, number) {
var self = this;
this.number = number;
this.element = document.getElementById(id);
this.click = function() {
alert('My number is ' + self.number);
}
this.element.addEventListener('click', this.click, false);
}
for (var i = 0; i < names.length; i++) {
new Button(names[i], i);
}
or slightly differently:
function Button(id, number) {
var element = document.getElementById(id);
function click() {
alert('My number is ' + number);
}
element.addEventListener('click', click, false);
}
for (var i = 0; i < names.length; i++) {
new Button(names[i], i);
}
It's because of closures.
Check this out: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake
The sample code and your code is essentially the same, it's a common mistake for those don't know "closure".
To put it simple, when your create a handler function, it does not just accesses the variable i from the outer environment, but it also "remembers" i.
So when the handler is called, it will use the i but the variable i is now, after the for-loop, 2.
I've been struggling with this problem myself for a few hours and now I've just now managed to solve it. Here's my solution, using the function constructor:
function doMouseClickConstructor(peram1, peram2, peram3){
return new Function('alert("doMouseClick() called AND peram1 = ' + peram1 + ' AND peram2 = ' + peram2 + ' AND peram3 = ' + peram3 + ');');
}
for (var i=0; i<names.length; i++){
document.getElementById(names[i]).addEventListener("click", doMouseClickConstructor(i,"dog","cat"));
};
Note: I havn't actually tested this code. I have however tested this codepen which does all the important stuff, so if the code above doesn't work I've probably just made some spelling error. The concept should still work.
Happy coding!
Everything is global in javascript. It is calling the variable i which is set to 3 after your loop...if you set i to 1000 after the loop, then you would see each method call produce 1000 for i.
If you want to maintain state, then you should use objects. Have the object have a callback method that you assign to the click method.
You mentioned doing this for event bubbling...for stopping event bublling, you really do not need that, as it is built into the language. If you do want to prevent event bubbling, then you should use the stopPropagation() method of the event object passed to the callback.
function doStuff(event) {
//Do things
//stop bubbling
event.stopPropagation();
}

How to pass 'this' into a function using JavaScript

I have the following code which is designed to create an onchange event handler for all elements with class name 'State'. The only problem is that I want the element to be passed into the 'StateChange' function. How can I update this JS to pass 'this' into the StateChange function? Thanks!
var c = document.getElementsByClassName('State');
for (i = 0; i < c.length; i++) c[i].onchange = createEventHandler( StateChange, c[i] );
Edit: I forgot to provide the createEventHandler function. Sorry about that... Here it is:
function createEventHandler(fn, input) {
return function () {
fn(input);
};
}
Also, some clarification. The purpose of the function is to obviate the need to put the onchange event next to each element with class name = 'State'. The result should be the same as if I were to write:
<select id="MyState1" class="State" onchange="StateChange(this)">
Update:
Re your updated question: You've said that the end result you want is as though you'd done this:
<select id="MyState1" class="State" onchange="StateChange(this)">
Your quoted createEventHandler function does exactly that.
Original Answer(s):
I'm not entirely sure I know exactly what you're trying to do. I can read the question at least two ways:
Inside the StateChange function call, you want this to refer to the element that changed.
Inside the StateChange function call, you want this to be the same as this where you're setting up your event handler.
Option 1: You want this = element within StateChange
You don't actually have to pass the element instance into createEventHandler, because when the event occurs, this will refer to the element because of the way you're hooking it up. But if you prefer to set it explicitly, your createEventHandler function could look like this:
function createEventHandler(handler, element) {
return function(event) {
return handler.call(element, event || window.event);
};
}
What that does is return a function that, when the event is triggered, will call the function you pass in (StateChange) with this set to the element you pass in.. This uses the JavaScript call feature of function objects, which allows you to define what this will be during the function call. You just pass it in as the first argument to call (subsequent arguments are passed on to the function being called).
If you want to rely on the fact that the way you're setting up the handler, this will already be set to the element instance, you can do away with the element argument:
function createEventHandler(handler) {
return function(event) {
return handler.call(this, event || window.event);
};
}
That just passes along the this value set up for the event handler by the browser.
Option 2: You want this = this as of where you're setting up the handler
It's the same principle as the above, just with a different argument. In this case, you'll need to pass this in:
var c = document.getElementsByClassName('State');
for (i = 0; i < c.length; i++) c[i].onchange = createEventHandler( StateChange, this, c[i] );
...and then createEventHandler looks like this:
function createEventHandler(handler, thisArg, element) {
return function(event) {
return handler.call(thisArg, event || window.event, element);
};
}
(Note I've passed in the element as a second argument to StateChange.)
More reading:
Mythical methods
You must remember this
One way is:
var c = document.getElementsByClassName('State');
for (i = 0; i < c.length; i++)
c[i].onchange = createEventHandler(function(){
StateChange(c[i]);
});

Categories

Resources