I'm building a <ul> dynamically within an object array where each list item calls a function when a link is clicked. However, the function is only ever being passed the parameter from the last item in the object array. Here is my code...
var list = document.createElement("ul");
var object;
for (var i = 0; i < myObjects.length; i++)
{
object = myObjects[i];
listItem = document.createElement("li");
image = document.createElement("img");
image.setAttribute("src", object.image_url)
listItem.appendChild(image);
listItem.appendChild(document.createTextNode(object.title));
link.setAttribute("href", "#");
link.addEventListener("click", function(){someFunction(object.id);}, false);
link.appendChild(document.createTextNode("Click Me!"));
listItem.appendChild(link);
list.appendChild(listItem);
}
element.appendChild(list);
All the variables are declared within this same function, so no globals are used. Before anyone suggests it, I'm not using jQuery because I'm trying to get my head round the basics of javascript before I move on to using any libraries.
Thanks in advance for any help.
Mister B.
Due to the way scope works, the anonymous function has a reference to the variable, not to its value. When you change that variable later, the change is reflected in the anon function.
Perhaps using a closure trick could work?
Change
link.addEventListener("click", function(){someFunction(object.id);}, false);
To
(function(O) {
link.addEventListener("click", function(){someFunction(O.id);}, false);
})(object);
If I'm not mistaken, that oughta create a new variable, whose value will be unaffected by subsequent changes to object in the for loop.
Ah, this is a classic closure issue.
You'll need to pass in the object.id to a wrapper function, like this:
(function(id) {
link.addEventListener("click", function(){someFunction(id);}, false);
})(object.id);
Related
This question already has answers here:
What exactly is the parameter e (event) and why pass it to JavaScript functions?
(5 answers)
Closed 4 years ago.
There is an example in "Head first JavaScript" book. This piece of code is unblurring an image on click. The code works, but I don't understand how, though it's an extremely simple piece of code.
The function init is called when a window is loaded. getElementsByTagName gives an HTMLCollection. A click on an image invokes a showAnswer function.
Now there is a mystery for me.
window.onload = init;
function init() {
var images = document.getElementsByTagName("img");
for (var i = 0; i < images.length; i++) {
images[i].onclick = showAnswer;
}
};
function showAnswer(e) {
var image = e.target;
var name = image.id;
name = name + ".jpg";
image.src = name;
}
There should be a parameter e. How this parameter is being created, from where? When showAnswer is called in init, there are no parameters given to it.
Considering the fact that I'm using a .target method on it, it should be an object. How does the browser know that this object has name e?
Why images[i].onclick = showAnswer; and not showAnswer();?
images[i].onclick = showAnswer is defining the event handler function to be run when images[i] is clicked. If you were to use showAnswer() there, it would run immediately in the init function which is probably not what you want.
images[i].addEventListener('click', showAnswer) is another way to write that, which might be more intuitive.
Event handler functions pass in an event object, which is what the 'e' is referring to. 'e.target' is referring to the element itself.
A reference for DOM events:
https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Events
(breaking myself of the habit of answering questions in comments...)
There should be a parameter e. How this parameter is being created, from where?
When you do images[i].onclick = showAnswer, that assigns "showAnswer" as the event handler for the "click" event for the DOM element named in images[i].
Later, when the user clicks that element (or the event gets triggered by some other method), the browser constructs the Event object, which contains a whole lot of information about that specific event. It then calls your event handler, passing along the Event object as the first (and only) parameter.
When showAnswer is called in init, there are no parameters given to it.
In your init function, you don't call showAnswer; you assign it as the event handler for clicks on images. The event is what passes the parameter to the handler, not your init.
Considering the fact that I'm using a .target method on it, it should be an object. How does the browser know that this object has name e?
That's the name you gave the parameter in the function. You could use any name, but e or evt are a common convention for event objects.
Why images[i].onclick = showAnswer; and not showAnswer();?
If it were showAnswer() you'd be assigning the return value of the function to the click handler. (Which in this case would be undefined, because showAnswer doesn't return anything.) With showAnswer you assign the function itself to the handler.
I have a function that creates a new <a> element and I want to add a onclick event to it, which is a function that increases the given value by 1.
In the function, I create these elements:
A number within spantags:
var spantags = document.createElement("span");
var anzahl = 1;
spantags.innerHTML = anzahl;
And the mentioned <a> element:
var plus = document.createElement("a");
plus.innerHTML = "+";
plus.onclick = more(spantags.innerHTML);
This, however, executed the function already in the function that creates this, so the number was increased to 2 on creation.
I read this answer on a similar question:
https://stackoverflow.com/a/249084/1972372
This example worked, the alert only came up when the + was clicked on, but it is an inner function there, not a declared one like my "more(element)".
So my question is: Is it possible to set the onclick attribute to a declared function on a newly created element, like in my example, but prevent it from running immediately?
(The other article had a jQuery answer too, but unfortunately I have no knowledge about jQuery and cannot use it)
Yes, just wrap it in a "proxy" function:
plus.onclick = function() {
more(spantags.innerHTML);
};
Sure, but first you have to understand that plus.onclick = more(spantags.innerHTML); will call more with the argument spantags.innerHTML and assign the result that is returned from that function call to plus.onclick.
You could wrap it in a proxy function as suggested previously, or you could take advantage of the bind method to bind the arguments to the function itself:
plus.onclick = more.bind(null, spantags.innerHTML);
Read up on Bind here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
The code in question:
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function (num) {
return function () {
alert(num);
};
}(i);
document.body.appendChild(link);
}
}
window.onload = addLinks;
My problem is since the returned function is a closure and variable num is the global variable each time the function executes num should hold the current value replacing the old one and should automatically update with that value everywhere. But this isn't happening -- when I click each link I get a different value. Why is this happening?
num is closed over by the anonymous function. The function (num) { return function () {}}(i) passes i as it is to the inner function and returns a new function based on whatever i's value is at the time.
If you want the click callback to always alert the maximum value of i, it's actually even easier:
link.onclick = function () {
alert(i);
}
function (var varname) is simply invalid syntax.
Think about it: what if you had three links like so:
0
1
2
You want them to alert out their number when you click on them, so you do it like this:
var links = $('a');
for (var i = 0; i < links.length; i++) {
links[0].onclick = function () {
alert(i);
}
}
At first glance, you'd expect that, for example, since you assigned the click handler on the first link when i = 0, it'll alert 0 when you click it. However when you click it, it'll actually alert 3.
You said it yourself, your code is creating a closure. What the code above does is that it's assigning a function handler to the click event of each link. Each of those function handlers is maintaining a reference to the variable i (note: not it's current value!).
At the point when you assign the function handler, it actually doesn't evaluate what value i has (because it doesn't need it). When you click, aha, that's when it checks what value i has and alerts it.
By the time you click a link, your for loop will be long finished, with i = 3, and that's what your click handler alerts.
Please look at the (i) after your function. This type of notation is for self-invocating functions only. It's as if you are setting link.onclick = a number, whereas it expects a function. You can simply use the following.
link.onclick = function (event) {
event.preventDefault();
alert(i);
};
Please note that click functions receive the "event" as a parameter by default. Make sure you call the preventDefault() method of the event otherwise it will bubble up the DOM and trigger a postback due to the nature of the anchor element.
This is strange and disagrees with what I thought to knew about closures. With the only change (in the first version of addLinks in the question):
link.onclick = function (num)
to
link.onclick = function ()
You get the expected result, that is, the actual value of the global num variable is alerted any time when a link is clicked.
It probably has to do with how the interpreter saves scope variables that are referenced within a closure when it encounters that closure. When a variable is referneced in a closure, the closest occurrence of that variable is searched for going upwards from the current scope.
While in the first case it is defined as a parameter (to the function that is called after declaration multiple times) it has different value each time so a different scope value is "remembered" by each closure.
In the 2nd case, the only occurrence found is in the global scope which results in the actual value of num is being used regardless which handler is called.
You haven't explained where the variable num has come from or how it's used. I'm guessing you mean to alert the current value of i. The click handler takes an event object as a parameter so I would try it like this:
function addLinks () {
for (var i=0; i<5; i++) {
var link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function (event) {
alert(i);
};
document.body.appendChild(link);
}
}
window.onload = addLinks;
This is a follow up question from my last question.
Simple javascript prototype issue
I am a bit new using JavaScript prototype so sorry for the second post.
I want to assign the clicked element id to the this.name array.
task.prototype.init=function(){
this.name=[]; //this.name array has to be defined here
for (var i; i<5; i++){
var Link=document.createElement('a');
Link.innerHTML='click';
Link.id=value[i]; //I want to assign the value to the this.name array
Link.href='#'
Link.onclick=this.changeName;
document.body.appendChild(Link);
}
}
task.prototype.changeName=function(){
//How do I push the this.id to the property this.name?
//the code below won't work because this refer to the <a> element.
this.name.push(this.id);
return false;
}
Any tips for the task?
Your prototype is okay, the problem is that this on event handlers is always the element that caused the event to be triggered. In JavaScript, the value of this inside a function depends on how the function is called.
If you want this to be bound to a certain value, you can create a bound function with Function.prototype.bind:
var newChangeName = this.changeName.bind(this);
Link.onclick = newChangeName;
Note however that bind is IE9+ only. A workaround would be:
var that = this;
Link.onclick = function() {
that.changeName();
};
(Style note: I'd use link instead of Link; the convention in js is to leave uppercase initials to constructors).
Use bind to set the desired this for the changeName callback:
Link.onclick=this.changeName.bind(this);
I am trying to reduce code size on a webpage by reading in an xml that contains a variable number of objects. In the javascript code I create an array to hold each of the objects and loop through the xml data to create each object.
I loop through the number of xml nodes to create that many objects and the object functions (mouseover, onclick, etc) but in the functions I use the same index variable to access the current objects properties, but when the function is actually called that index variable is no longer within my range.
Is there anyway I can get the calling object's key (index) value?
for(index=0, index < scenes.length; index+=1)
{
this.thumbs[index] = document.createElement('div');
//setup more properites
this.thumbs_image[index] = document.createElement('img');
//more setup
this.thumbs[index].onmouseover = function(){
me.thumbs_image[index].src = scenes[index].attributes.getNamedItem("src").nodeValue; //THIS IS THE PROBLEM - WHEN the function is actually called index is no longer the correct index of the array element
}
}
The code outside of the function onmouseover works, and it works if I hardcode the index within the onmouseover.
I tried creating a separate function with the index passed as parameter, but when I assign the function dynamically I still assign with index as I can't think of another way and this doesn't work either:
this.thumb[index].onmouseover = myFunction(index);
myFunction=function(i){
me.thumbs_image[i].src = scenes[i].attributes.getNamedItem("src").nodeValue;
}
Is there any way within the onmouseover to get the key of the element calling it?
I'm hoping there is an obvious solution that I am just overlooking - any help is greatly appreciated!
Thanks!
First solution: replace this:
this.thumbs[index].onmouseover = function(){
me.thumbs_image[index].src = scenes[index].attributes.getNamedItem("src").nodeValue;
}
with this:
this.thumbs[index].onmouseover = (function(i) {
return function() {
me.thumbs_image[index].src = scenes[index].attributes.getNamedItem("src").nodeValue;
};
})(i);
The function wrapper will capture the value of the variable i in a closure, so that you can access that value (as opposed the non-existent variable i) at the time the handler is invoked.
Second solution: onmouseover (and all other event handlers) will receive one argument, which is the event. The event knows where it originated. Try this:
this.thumbs[index].onmouseover = function(evt) {
console.log(evt.target);
}
I recommend the second solution in this particular case, but the pattern in the first solution is important to know about - never create functions that depend on the loop counter directly in a loop, instead always have a function call to capture the loop counter's value instead.