How to put a javascript function on a generated link - javascript

I'm trying to give a list of generated link tags a function, this is the way i'm doing this.
for (i = 0; i < friendsXML.length; i++) {
friendListInDiv = document.createElement("p");
var link = document.createElement("a");
link.onclick = function() {
openChat(friendsXML[i].textContent)
};
var friendText = document
.createTextNode(friendsXML[i].textContent + ":"
+ statusXML[i].textContent);
link.appendChild(friendText);
friendListInDiv.appendChild(link);
friendDiv.appendChild(friendListInDiv);
}
Currently the openChat(name) function only calls an alert to test its value
function openChat(name){
alert(name);
}
Now the problem is that when I go to my webpage and click one of the generated links it always alerts the first name (every link alerts the same name, the first one). So my question is how can I fix it that I alert the correct name for each link?
Here is a pastebin of the full code if necessary http://pastebin.com/8ggE7SHs

wrap it in a function closure:
for (i = 0; i < friendsXML.length; i++) {
(function(i) {
friendListInDiv = document.createElement("p");
var link = document.createElement("a");
link.onclick = function() {
openChat(friendsXML[i].textContent)
};
var friendText = document
.createTextNode(friendsXML[i].textContent + ":"
+ statusXML[i].textContent);
link.appendChild(friendText);
friendListInDiv.appendChild(link);
friendDiv.appendChild(friendListInDiv);
}(i))
}
Your variables are getting hoisted to the outer scope, Javascript only has function level scoping.

Related

How to fix scope issues when using an onclick event in a for loop [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 3 years ago.
I am curretly experiencing difficulties implementing an onclick event within a for loop. Instead of alerting the respective value it always returns undefined (presumably a scope problem, because the iteration itself works fine)
Until now I tried to pass on the i variable to the onclick function; however, with little success
for (var i = 0; i < timeSpanLength; i++) {
// creating the wrap for the month
var month = document.createElement("div");
month.className = 'month_element';
var reference_month = document.createElement("span");
reference_month.innerHTML = time_span[i];
//onclick event
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
//append to container
month.appendChild(reference_month);
document.getElementById('time_container').appendChild(month);
}
The expected outcome is to trigger an alert which displays the same month which is displayed in the span element above. I need the variable to pass it on to another function.
Any help is highly appreciated since I am beginner in javascript.
for (var i = 0; i < timeSpanLength; i++) {
(function (index) {
// creating the wrap for the month
var month = document.createElement("div");
month.className = 'month_element';
var reference_month = document.createElement("span");
reference_month.innerHTML = time_span[index];
//onclick event
reference_month.onclick = function() {
var month_beginning = signup_date;
var month_end = time_span[index];
alert(month_end);
//searchForData(month_beginning, month_end);
};
//append to container
month.appendChild(reference_month);
document.getElementById('time_container').appendChild(month);
})(i);
}
This callback function handler is forming a closure with respect to the outer scope. Also var has a function scope, so in essence the block of code can be re-written as:
var i;
for (i = 0; i < timeSpanLength; i++) {
...
//onclick event
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
...
}
So the var i is hoisted to the top and when the loop completes the value of i is timeSpanLength.length and this is what you use to access time_span[i] and that returns undefined.
Since with var the binding remains the same, the handlers registered will be referring the last value of i in the loop.
So you either need to use let in the for-loop:
for (let i = 0; i < timeSpanLength; i++) { ... }
Or an IIFE which forms a new scope bound to each new value of i from the loop:
for (var i = 0; i < timeSpanLength; i++) {
(function(i){
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
})(i)
}

Iterating parameters for a function within a loop

JS Nooblord here, to give some context I have recently created my first JQuery based image slider to which I'm currently trying to generate a list of control buttons dynamically when the page loads.
I have succeeded thus far in creating the buttons but when it comes to writing the onclick function I'm having issues calling another function (with a parameter) inside a for loop.
I suck at explaining things but here is the code;
function addControls(){
var x = document.getElementById('slider').childElementCount;
for (var i = 0; i < x; i++) {
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
}
}
function goto(index){
alert(index);
}
Here is the JSFiddle preview.
What I expect is for each button to call the goto function with their respective position in the loop however every generated button with the onclick function uses the last index from the loop (4).
My initial thoughts are that the buttons are being rendered after the loops are finished and not within each iteration of the loop? also if anyone has any tips and alternatives for what I'm doing I would greatly appreciate that.
Thanks,
-Dodd
As commented on Mikelis Baltruks, you will have to use .bind.
You can use
goto.bind(null, i+1)
to map only index to it. If you wish to get the button as well, you can use
goto.bind(btn, i+1)
Sample JSFiddle
Bind
.bind is used to change the context of a function. Its syntax is
functionName.bind(context, argumentList);
This will create a reference of function with a newly binded context.
You can also use .apply for this task. Difference is, apply expects arguments as array and bind expect a comma separated list.
Note: both this function will just register events and not call it.
Reference
.bind
.apply
call() & apply() vs bind()
The problem is the reference to i.
for (var i = 0; i < x; i++) {
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
// any variable reference will use the latest value
// so when `onclick` is actually run, the loop will have continued on to completion, with i == 4
};
}
You need a separate variable to reference for each onclick handler. You can do this by creating a closure:
function makeOnclick(i) {
// `i` is now a completely separate "variable",
// so it will not be updated while the loop continues running
return function() { goto(i); };
}
for (var i = 0; i < x; i++) {
var btn = document.createElement("Button");
btn.onclick = makeOnclick(i);
}
This can be done any number of ways, as others have shown. But this should explain why it's happening. Please ask any questions.
You need to create a closure in the loop, this should work:
var x = document.getElementById('slider').childElementCount;
for (var i = 0; i < x; i++) {
(function (i) {
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
})(i);
}
function goto(index) {
alert(index);
}
https://jsfiddle.net/g8qeq29e/6/
Or with ES6 let keyword;
function addControls(){
var x = document.getElementById('slider').childElementCount;
for (let i = 0; i < x; i++) {//change var to let here
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
}
}
function goto(index){
alert(index);
}

Get the specific div id from an onclick event (Pure JS no Jquery)

When I try the code referenced in SO #1, I get the console logging a blank string:
installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("choice" + i);
choiceDiv.innerText = choice[0];
choiceDiv.onclick = function() {
console.log(this.id);
}
this.choicesContainer.appendChild(choiceDiv);
}
}
I want to bind to my class function clicked
installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("choice" + i);
choiceDiv.innerText = choice[0];
choiceDiv.onclick = this.clicked;
this.choicesContainer.appendChild(choiceDiv);
}
}
clicked(e) {
console.log(e.parentNode); //this is undefined
console.log(e.srcElement);
}
But that shows undefined. When I log srcElement, I get the full element
<choice0>path 1</choice0>
I want to get just the div id when I click, so I can parse that and do logic.
I'd recommend the following approach, as it is the standard:
//assign the event
choiceDiv.addEventListener('click', clicked)
//define the listener
function clicked(event) {
console.log(event.currentTarget)
}
update:
I'm tempted to offer a fix to your code, because I don't think you're achieving what are you trying to actually do:
function installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("div");
choiceDiv.id = "choice" + i;
choiceDiv.innerText = choice[0];
choiceDiv.addEventListener("click", clicked);
this.choicesContainer.appendChild(choiceDiv);
}
}
function clicked(ev) {
console.log(ev.currentTarget.id); //this will log "choice0"
}
Your "clicked" function are receiving an Event rather than a HTML Element. This is the default behavior when an onClick event triggered.
The click event have a srcElement property, indicating the source element the click event occurred upon. This event object have no parentNode property.
Use e.srcElement.parentNode instead.
BTW, in the SO #1 example, it assign "showIt(this)" to onClick, so browser pass "this", the target element rather than the event object, to the onClick function.

How can I pass different values to onclick handler [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
consider:
for (var i in somecollection){
var a = document.createElement('a');
a.onclick = new function(){callSomeMethod(somecollection[i]);};
...
}
At runtime, all 'a' elements wind up calling callSomeMethod with the same parameter value (the last element in 'somecollection'.
I have a hack of a solution as follows:
for (var i in somecollection){
var a = document.createElement('a');
var X = 'callSomeMethod(\''+somecollection[i]+'\');';
a.setAttribute('onclick', X);
...
}
But this forces me to exclude 'callSOmeMethod' from mangling/compression when I minify my JS files. How can I make each 'a' element's click handler callSomeMethod with a different parameter without hardcoding the function name in a string?
The closest my search found is the accepted answer in pass string parameter in an onclick function
but I do not know how to create a 'scope bubble' .
Thanks...
You could use the power of javascript ! juste add custom property to the object.
Here is a example:
var somecollection= [ 'a','b','c','d'];
function callSomeMethod() {
var i = this.__index; // retreive here your data
if (i) {
alert(somecollection[i]);
}
}
function init() {
for (var i in somecollection){
var a = document.createElement('a');
a.onclick = callSomeMethod;
a.innerHTML = "click for #" + i;
a.__index = i; // custom property to capture index or any data you want to pass
document.body.appendChild(a);
}
}
You can use a closure, it will capture the value of i
for (var i in somecollection){
var a = document.createElement('a');
a.onclick = (function(index) {
return function () {
callSomeMethod(someCollection[index])
};
})(i);
...
}
That way, the correct value of index will be available when the function is called, but it won't be called until the onClick event fires.
Interesting approach below. I also found the following which works exactly as I want so I thought to share here:
function attachSomeMethodClickHandler(a, value){
function functionX(){callSomeMethod(value);};
a.addEventListener('click', functionX);
}
:
:
for(var i in someCollection){
var a = document.createElement('a');
attachSomeMethodClickHandler(a, someCollection[i]);
:
}
Don't use inline bindings but instead try using event delegation:
Bind an event to the anchors parent and check the target once it's clicked,
this way, you're not limited to the amount of elements which are created,
and don't have to bind the event again if you'll create new ones later on.
Then pass the anchors index instead of a parameter.
var dataSource = ["dog", "cat", "horse"];
var container = document.getElementById("container");
function index(el) {
var parent = el.parentNode;
for(i = 0;i < parent.childNodes.length;i++) {
if(parent.childNodes[i] == el) {
return i;
}
}
}
container.addEventListener("click", function (e) {
e.preventDefault();
var idx = index(e.target);
alert("Index: " + idx + " value: " + dataSource[idx]);
});
for (i = 0; i < dataSource.length; i++) {
var data = dataSource[i];
var a = document.createElement("a");
var text = document.createTextNode(data);
a.href = "http://someurl.com?id=" + data;
a.appendChild(text);
container.appendChild(a);
}
Here's the fiddle: http://jsfiddle.net/RZw8e/2/

Canot get the href value

Hi I need to valid the href is empty or not on my page using javascript. I searched the site and found some example, but it didn't worked for me. I must miss something that I didn't notice. Would someone point me the good direction and my mistake. I got the error" Unable to get property 'getattribute' of undefined or null reference. The <a> element is like that <a name="playback" href=""> on html file.
Thanks in advance.
There is my code which is run on load event:
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
anchors[i].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = anchors[i].getAttribute("href");
//var link= anchors[i].attributes['href'] this line doesn't work too.
}
}
}
}
In your code, the call to getAttribute is inside a closure (that is, a function defined "inline" without a name) that is assigned to the onlick event handler of the link. Therefore that code isn't execxuted right away - it doesn't run before the onclick handler triggers.
When the onclick header triggers, two things are passed to the callback function: the element on which the event was triggered is assigned to the this variable of the functions context - and the event itself is passed as first parameter. anchors however is undefined in the scope of that callback.
So, use either of those:
anchors[i].onclick = function () {
var link = this.getAtrribute("href");
}
 
anchors[i].onclick = function (event) {
var link = event.target.getAttribute("href");
}
You have got a scope problem.
The following code will output 3:
for (var i = 0; i < 3; i++) {
}
console.log(i); // 3
Similar to the example above your onclick is fired after the loop is done.
So i in your example would equal to anchors.length.
And anchors[anchors.length] === undefined.
To solve this problem you have to create a new scope.
For example you could use an Immediately-Invoked Function Expression (IIFE):
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
(function(j){
anchors[j].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = anchors[j].getAttribute("href");
}
}
}
}(i));
}
You need to use closure if you want to do it this way since you are using the shared i variable which would have been having last value of iteration when your handler runs on click. But since you are looking at that particular anchor, try binding it with bind an event listener and access it using this.href:
You can use addEventListener and for older browser support attachEvent
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
anchors[i].addEventListener('click', function () {
var link = this.getAttribute("href");
})
};
Demo
Or :
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
anchors[i].onclick = getHandler(i);
}
function getHandler(i) {
return function () { //Now each of your handler has its own `i`
var link = anchors[i].getAttribute("href");
}
}
Demo
I have never seen getAttribute before so I performed some tests on it. It turns out that href and getAttribute("href") are quite different. Namely href is the absolute url and getAttribute("href") is the relative url to the page. See this fiddle.
The problem with your code is that the var is captured in the closure of onclick and when the onclick function runs the value will of i will be anchors.length.
Solution, Scratch that use the code from Johannes H. His is better
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
(function () {
var current = anchors[i]; //Capture the anchor element
current.onclick = function() {
var link = current.getAttribute("href");
};
} ());
}
See this w3 schools page for how to get the href attribute from anchor tags.
http://www.w3schools.com/jsref/prop_anchor_href.asp
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
anchors[i].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = this.href;
}
}
}

Categories

Resources