Javascript : Pass String by value - javascript

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);}

Related

Use closure inside array with pure javascript

I need to copy a string inside an array to a value inside another array that is created in a loop. In the end when I print all names are the last in the array of names. I want to copy/clone the value so that I don't have a reference and I would like it to be only in native javascript without external libraries.
This is my code
var exp_names =["name1","name2","name3"];
var i;
for (i = 0; i < exp_names.length; i++) {
d3.tsv("data/"+exp_names[i], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[i];
});
});
});
And then all expId are "name3"
Data is loading correctly per file.
I have tried with jquery's extend function and also lodash's clone function, I have tried my own clone function and nothing works it will still throw "name3" for all the expId.
These didn't work:
var newname = new String(exp_names[i]);
var newname = $.extend(true, {}, exp_names[i]);
var newname = $.extend( {}, exp_names[i]);
var newname = _.clone(exp_names[i]);
var newname = exp_names[i].slice(0);
I am desperate by now.
You need to use bind function.
var exp_names =["name1","name2","name3"];
var i;
var func = [];
for (i = 0; i < exp_names.length; i++) {
func[i]=(function(index){
d3.tsv("data/"+exp_names[index], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[index];
});
});
}).bind(this,i);
}
for(i = 0; i < 3; i++){
func[i](i);
}
Another solution is to use let keyword.
ES6 provides the let keyword for this exact circumstance. Instead of using closures, we can just use let to set a loop scope variable.
Please try this:
for (let i = 0; i < exp_names.length; i++) {
d3.tsv("data/"+exp_names[i], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[i];
});
});
}
I guess usage of IIFE and bind together, in the first answer is a little weird. It's best to choose either one of them. Since in the newest versions of the browsers bind is way faster than an IIFE closure and the let keyword I might suggest you the bind way.
A similar example to your case might be as folows;
var exp_names = ["name1","name2","name3"],
lib = {doStg: function(d,cb){
cb(d);
}
},
data = [{a:1},{a:2},{a:3}];
for (i = 0; i < exp_names.length; i++) {
lib.doStg(data, function(i,d) {
d.forEach(function(e){
//Do stuff with doStg
e.expId = exp_names[i];
console.log(e);
});
}.bind(null,i));
}

Trigger event for each object

I want website to change class of an element if it is filled. So, when user blurs out of an input field the program checks if it has any value and if yes adds a class. The problem is to pass this behaviour to each element in class' collection.
var input = document.getElementsByClassName('input');
contentCheck = function(i){
if(input[i].value>0) input[i].classList.add('filled');
else input[i].classList.remove('filled');
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur',contentCheck(i));
}
This works once after reloading the page (if there's any content in cache), but contentCheck() should trigger each time you leave the focus.
You've half-applied the "closures in loops" solution to that code, but you don't need the closures in loops solution, just use this within contentCheck and assign it as the handler (rather than calling it and using its return value as the handler):
var input = document.getElementsByClassName('input');
var contentCheck = function(){ // <== No `i` argument (and note the `var`)
// Use `this` here
if(this.value>0) this.classList.add('filled');
else this.classList.remove('filled');
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur',contentCheck);
// No () here -------------------------------------^
}
Side note: classList has a handy toggle function that takes an optional flag:
var contentCheck = function(){
this.classList.toggle('filled', this.value > 0);
};
If you needed the "closures in loops" solution (again, you don't, but just for completeness), you'd have contentCheck return a function that did the check:
var input = document.getElementsByClassName('input');
var makeContentCheckHandler = function(i){
return function() {
if(input[i].value>0) input[i].classList.add('filled');
else input[i].classList.remove('filled');
};
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur', makeContentCheckHandler(i));
}
Note I changed the name for clarity about what it does.
Try to use anonymous function
input[i].addEventListener('blur',function(e){
console.log(e);
});
Example: https://jsfiddle.net/42etb4st/4/

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);
}

Creating a closure for .setTimeout() inside a for loop

I am trying to write a javascript program which stores the value from an input element in an array when a button is clicked. The array is the split and each individual letter added to a span element and then appended to the document. The idea is to create a typing effect using setTimeout.
I am running into an issue creating a closure within the loop, so currently the setTimeout function always returns the final value of the iteration.
The function in question is at the bottom of the code block and called addTextToBoard();
var noteButton = document.querySelector('[data-js="button"]');
noteButton.addEventListener("click",function() {
var messageIn = document.querySelector('[data-js="input"]');
var message = messageIn.value;
postToBoard(message);
});
function postToBoard(val) {
var noteBoard = document.querySelector('[data-js="noteboard"]');
var newElement = document.createElement('div');
newElement.classList.add('noteboard__item');
noteBoard.appendChild(newElement);
setTimeout(function(){
newElement.classList.add('active');
}, 200);
addTextToBoard(newElement, val);
}
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(function(x){
return function() {}
el.appendChild(letter);
}(i),1000);
}
}
I believe I am close, I'm just not fully understanding the syntax for creating the closure. If someone could give poke in the right direction, without necessarily giving the full solution that would be great.
I essentially tried to paste in the following code snippet from here but I've missed something somehwere along the way!
setTimeout(function(x) { return function() { console.log(x); }; }(i), 1000*i);
Best,
Jack
You are close.
Since the "letter" variable changes, you'll add only the last letter over and over again. You need to "save" the current letter on the setTimeout() callback function, One way to go is like this:
function appendMyLetter(letter) {
return(function() {
el.append.Child(letter);
});
}
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(appendMyLetter(letter), 1000);
}
}
This way, the appendMyLetter() function gets called with a different parameter (one for each letter) and returns a function with the correct "stored" value to be called by setTimeout().
EDIT
Looking at your setTimeout() code closely
setTimeout(function(x){
return function() {}
el.appendChild(letter);
}(i),1000);
It would work fine, if you used the proper parameters and used the appendChild() inside the returned function, like so:
setTimeout(function(x){
return(function() {
el.appendChild(x);
});
}(letter),1000);
You can create an immediately-invoked function expression IIFE to create a closure
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
(function(index) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(function(){
el.appendChild(letter);
},1000);
})(i);
}
}
I dont know if this will work but here you go a slight change in operator:
letter.innerHTML += wordArray[i];
if you dont get the effect you imagined you will get you better try to increment the timer by i like this
setTimeout(function(){
...
},1000*i);

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/

Categories

Resources