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

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/

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 to put a javascript function on a generated link

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.

Javascript : Pass String by value

I have a little problem with one of my javascript code. Here is the code
//assume array is an array containing strings and myDiv, some div in my doc
for(var i in array) {
var myString = array[i];
var a = document.createElement('a');
a.innerHTML = myString;
a.addEventListener("click", function() {myFunc(myString)}, false);
myDiv.appendChild(a)
}
function myFunc(s) {alert(s);}
However, since Strings are passed by reference in JavaScript, I see always the last string of my array when I click on the link a in question. Thus, my question is "How can I pass myString by value ?". Thank you for your help !
Phil
You should add a closure around your event handler:
JavaScript closure inside loops – simple practical example
a.addEventListener("click", function (s) {
return function () {
alert(s)
};
}(myString), false);
Also, you should not use for...in loops on arrays.
Primitive variables are not passed by reference in Javascript.
This is the classic 'loop variable called inside a closure' problem.
Here's one commonly-used solution to that problem:
for (var i = 0, n = array.length; i < n; ++i) {
var myString = array[i];
var a = document.createElement('a');
a.innerHTML = myString;
a.addEventListener("click", make_callback(myString), false);
myDiv.appendChild(a)
}
function make_callback(s) {
return function() {
alert(s);
}
}
Note that this isn't particularly memory efficient since it creates a new function scope for every element in the array.
A better solution might be to store the variable data actually on the element (i.e. as a new property) and retrieve that in the callback. In fact you're already storing that string in the .innerHTML property so you could just read that and then take advantage of delegation to register just the one handler on the elements' parent:
for (var i = 0, n = array.length; i < n; ++i) {
var a = document.createElement('a');
a.innerHTML = array[i];
myDiv.appendChild(a)
}
myDiv.addEventListener('click', function(ev) {
alert(ev.target.innerHTML);
}, false);
try this :
i think its always good not practice to iterate array using for...in
for(var i=0; i<array.length;i++) {
var myString = array[i];
var a = document.createElement('a');
a.innerHTML = myString;
a.addEventListener("click", function() {myFunc(myString)}, false);
myDiv.appendChild(a)
}
function myFunc(s) {alert(s);}

Categories

Resources