This question already has answers here:
How do JavaScript closures work?
(86 answers)
Closed 4 years ago.
Im trying to dinamically insert names into the value of an input element. I try to pass players[i] as an argument for the function addPlayer but it says undefined. When I click on each name it should be passing it to the input field. Where am I failing?
var players = ["john","doe"];
function addPlayer(str) {
console.log(str);
document.getElementsByClassName("answerInput")[0].value += str;
}
for (var i = 0; i < players.length; i++) {
var node = document.createElement("div");
var textnode = document.createTextNode(players[i]);
node.appendChild(textnode);
node.classList.add("newPlayers");
document.getElementById("pushPlayer").appendChild(node);
node.addEventListener('click', function () {
addPlayer(players[i]);
}, false);
};
The problem is the variable i declared with the keyword var
The scope of a variable declared with var is its current execution context, so, the variable i will end the for-loop with the last value.
var players = ["john (click me)", "doe (click me)"];
for (var i = 0; i < players.length; i++) {
var node = document.createElement("div");
var textnode = document.createTextNode(players[i]);
node.appendChild(textnode);
node.classList.add("newPlayers");
document.getElementById("pushPlayer").appendChild(node);
node.addEventListener('click', function() {
console.log(i)
}, false);
};
<div id='pushPlayer'>
</div>
An alternative is declaring that variable with the statement let
Variables declared by let have as their scope the block in which they are defined, as well as in any contained sub-blocks. In this way, let works very much like var. The main difference is that the scope of a var variable is the entire enclosing function.
var players = ["john", "doe"];
function addPlayer(str) {
console.log(str);
document.getElementsByClassName("answerInput")[0].value += str;
}
for (let i = 0; i < players.length; i++) {
var node = document.createElement("div");
var textnode = document.createTextNode(players[i]);
node.appendChild(textnode);
node.classList.add("newPlayers");
document.getElementById("pushPlayer").appendChild(node);
node.addEventListener('click', function() {
addPlayer(players[i]);
}, false);
};
<input class='answerInput' placeholder='Enter answer'>
<div id='pushPlayer'>
</div>
Another alternative is using IIFE to return a function with a specific value/player.
var players = ["john", "doe"];
function addPlayer(str) {
console.log(str);
document.getElementsByClassName("answerInput")[0].value += str;
}
for (var i = 0; i < players.length; i++) {
var node = document.createElement("div");
var textnode = document.createTextNode(players[i]);
node.appendChild(textnode);
node.classList.add("newPlayers");
document.getElementById("pushPlayer").appendChild(node);
node.addEventListener('click', (function(player) {
return function () {
addPlayer(player);
}
})(players[i]), false);
};
<input class='answerInput' placeholder='Enter answer'>
<div id='pushPlayer'>
</div>
Related
So I'm trying to create a list of input with unique values which are gotten from a array of objects. But for some reason it only iterates once and stops.
function loadLayer() {
//Get the object from local storage
var project = JSON.parse(localStorage.getItem('project'));
var projectLayers = project.layers
for (var x = 0; x < projectLayers.length; x++) {
var x = createLayer(projectLayers[x].name)
appendLayer(x)
}
}
So project layer is basically an array like [{id=1,name="bob},{id=2,name="kevin"}]
function createLayer(name) {
var li = document.createElement("li");
li.className = "list-group-item"
var x = document.createElement("INPUT")
x.setAttribute("type", "text")
x.setAttribute("value", name)
li.appendChild(x)
return li
}
function appendLayer(layer) {
var layerList = document.getElementById("layerList")
layerList.appendChild(layer)
}
and appendlayer just adds the li to the ul
However, after i run the program my only has one with a input with bob inside it. Where did the other no go. I tried printing to see if second loop was called but it didn't seem like it. I don't know why and been stuck for hours.
Just like you have been told, you need to be careful with the way you name variables.
Secondly I will also suggest using let instead of var. Because you could easily overwrite a variable declared with var outside its initial scope.
And now to your code:
I made some edit to your code especially the loadLayer function and it appends both names to the list:
<script>
let projectList = [
{
id:1,
name:"bob"
},
{
id:2,
name:"kevin"
}
]
function loadLayer() {
window.localStorage.setItem('project', JSON.stringify(projectList));
let p = JSON.parse(window.localStorage.getItem('project'));
for (let i = 0; i < p.length; i++) {
let projectName = createLayer(p[i].name)
appendLayer(projectName)
}
}
function createLayer(name) {
let li = document.createElement("li");
li.className = "list-group-item"
let x = document.createElement("INPUT")
x.setAttribute("type", "text")
x.setAttribute("value", name)
li.appendChild(x)
return li
}
function appendLayer(layer) {
let layerList = document.getElementById("layerList")
layerList.appendChild(layer)
}
loadLayer()
</script>
You are naming the variable of the iteration in the loop as x and the var x = createLayer(projectLayers[x].name) conflicts with it, change one of them.
This question already has answers here:
How do I create dynamic variable names inside a loop?
(8 answers)
Closed 4 years ago.
I'm not clear on the correct syntax to do the following.
I have tried eval() I have tried [] and {[]} as well..
I start with this:
var player1InitStart = 0;
var player2InitStart = 0;
var player3InitStart = 0;
var player4InitStart = 0;
var playerID = event.target.id; // will return, player1, 2, 3, 4
To be used in a check like so:
if(eval(playerID + "InitString") == 0){
//do something, first time starting video
eval(playerID + "InitString") = 1;
alert('First Time Playing Video' + eval(playerID + "InitString"));
}
I am trying to use the playerID value together with the string: "InitStart"..
so I can then update the target xxInitStart variable above.
How can I concatenate the var playerID with the string InitStart so I can now target one of the playerXInitSTart variables to update it?
Update: answer/solution that worked for me-
no clue why the $ character is being used? (reminds me of PHP)
no clue why the back tick marks '' are there either? ''
var playerID = event.target.getIframe().id;
var targetInitID = ${playerID}InitStart;
//access (get or set)
alert(window[targetInitID]);
window[targetInitID] = 1;
Here we build string and address window[yourvar] to increment, does this work for you
var player1InitStart = 0;
var player2InitStart = 0;
var player3InitStart = 0;
var player4InitStart = 0;
//var playerID = event.target.id; // will return, player1, 2, 3, 4
document.querySelectorAll('div').forEach(div => {
div.addEventListener('click', clickEvent);
});
function clickEvent(event) {
let playerID = `${event.target.id}InitStart`;
window[playerID] += 1;
logVars();
}
function logVars() {
console.log(player1InitStart);
console.log(player2InitStart);
console.log(player3InitStart);
console.log(player4InitStart);
}
<div id="player1">1</div>
<div id="player2">2</div>
<div id="player3">3</div>
<div id="player4">4</div>
Here is a solution that follows CertainPerformance advice:
var players = {
player1InitStart: 0,
player2InitStart: 0,
player3InitStart: 0,
player4InitStart: 0
};
document.querySelectorAll('div').forEach(div => {
div.addEventListener('click', clickEvent);
});
function clickEvent(event) {
let playerID = `${event.target.id}InitStart`;
players[playerID] += 1;
logVars();
}
function logVars() {
for(let k in players) {
console.log(players[k]);
}
}
<div id="player1">1</div>
<div id="player2">2</div>
<div id="player3">3</div>
<div id="player4">4</div>
I'm really new to javascript from C# and i'm having a little trouble. I wrote this function to make adding menu's a bit easier on my site. It works well except I can't seem to give my div's an individual url, even though I can give them an individual innerHtml.
I've been stuck trying different things such as divs[i].location.url etc.. but I can't seem to have anything work. My current solution has each div link to /contact.html which I'm a little confused by.
function DrawMainMenu() {
var btns = [
["About", "/about.html"],
["Portfolio", "/portfolio.html"],
["Resume", "/resume.html"],
["Contact", "/contact.html"]
];
var numOfBtns = btns.length;
var divs = new Array(numOfBtns);
for (var i = 0; i < numOfBtns; i++) {
divs[i] = document.createElement("div");
divs[i].className = "menuBtn";
divs[i].innerHTML = btns[i][0];
divs[i].style.height = (30 / numOfBtns) + "%";
divs[i].style.lineHeight = 3.5;
var link = btns[i][1];
divs[i].addEventListener('click', function() {
location.href = link;
}, false);
document.getElementById("buttons").appendChild(divs[i]);
}
}
Thanks
The problem is that the variable link gets overwritten each iteration, so when the event handler it gets link, which is the string '/contact.html', since that was the last value given to it.
You can try setting onclick attribute to elements, which will store the variable in the attribute onclick. Therefore, it will have the old and correct value.
function DrawMainMenu() {
var btns = [
["About", "/about.html"],
["Portfolio", "/portfolio.html"],
["Resume", "/resume.html"],
["Contact", "/contact.html"]
];
var numOfBtns = btns.length;
var divs = new Array(numOfBtns);
for (var i = 0; i < numOfBtns; i++) {
divs[i] = document.createElement("div");
divs[i].className = "menuBtn";
divs[i].innerHTML = btns[i][0];
divs[i].style.height = (30 / numOfBtns) + "%";
divs[i].style.lineHeight = 3.5;
var link = btns[i][1];
divs[i].setAttribute('onclick', 'location.href = "' + link + '"');
document.getElementById("buttons").appendChild(divs[i]);
}
}
DrawMainMenu();
<div id="buttons"><div>
Updated answer
Here we make use of closures. Using a closure (closing the values of link) we bind the value to the scope of the click handler.
function DrawMainMenu() {
var btns = [
["About", "/about.html"],
["Portfolio", "/portfolio.html"],
["Resume", "/resume.html"],
["Contact", "/contact.html"]
];
var numOfBtns = btns.length;
var divs = new Array(numOfBtns);
for (var i = 0; i < numOfBtns; i++) {
(function() {
divs[i] = document.createElement("div");
divs[i].className = "menuBtn";
divs[i].innerHTML = btns[i][0];
divs[i].style.height = (30 / numOfBtns) + "%";
divs[i].style.lineHeight = 3.5;
var link = btns[i][1];
document.getElementById("buttons").appendChild(divs[i]);
divs[i].addEventListener('click', function() {
location.href = link;
}, false);
}());
}
}
DrawMainMenu();
<div id="buttons"><div>
Everyone is suggesting some framework, but I'd like to know how it can be done in native JavaScript. I've tried this code and some other things, no effect. Seems that I'm unaware of some basic underlying concept. Any help would be appreciated.
window.onload = function() {
var trCurrent
var main = document.getElementById('main');
var tr = main.getElementsByTagName('tr');
function hl() {
trCurrent.setAttribute('class', 'highlight');
};
for (var x = 0; x < tr.lenght; x++) {
trCurrent = tr[x];
trCurrent.addEventListener ('mousedown', hl, false);
}
};
You have to change trCurrent to this at your function h1, becayse trCurrent points to the last defined TR element (trCurrent = tr[x] for a high x).
Use .length instead of .lenght.
Final code:
window.onload = function() {
var main = document.getElementById('main');
var tr = main.getElementsByTagName('tr');
function hl() {
this.setAttribute('class', 'highlight');
};
for (var x = 0; x < tr.length; x++) {
tr[x].addEventListener ('mousedown', hl, false);
}
};
If you want to use an variable which is subject to change during a loop, it's required to wrap the body in an (anonymous) function:
window.onload = function() {
var main = document.getElementById('main');
var tr = main.getElementsByTagName('tr');
for (var x = 0; x < tr.length; x++) {
(function(trCurrent){ //Anonymous wrapper, first argument: `trCurrent`
function hl() {
trCurrent.setAttribute('class', 'highlight');
};
trCurrent.addEventListener ('mousedown', hl, false);
})(tr[x]); //Passing a reference to `tr[x]`
}
};
I have this function:
function addButtonLookup() {
var element = document.getElementById("btnToolBar");
var index;
for (var i = 0; i < lookupArray.length; i++) {
index = i;
var btn = document.createElement('input');
btn.type = 'button';
btn.value = '' + lookupArray[i];
btn.name = 'btnLookup' + i;
btn.id = i;
btn.className = 'CommonButtonStyle';
element.appendChild(btn);
btn.onclick = function() {
debugger;
tblExcpression.WriteMathElement(lookupArray[i], lookupArray[i]);
};
}
}
onbutton click the i is undefined
Instead of this:
btn.onclick = function() {
debugger;
tblExcpression.WriteMathElement(lookupArray[i], lookupArray[i]);
};
Try this:
btn.onclick = (function(i) {
return function() {
debugger;
tblExcpression.WriteMathElement(lookupArray[i], lookupArray[i]);
}
})(i);
The issue with the first version is that the i variable is copied from the current scope. However the i variable varies in the current scope (it's part of a for loop), this is why you're getting this weird behavior.
By passing the i variable as a paremeter to a new function (like the second example) the current i variable is copied.
You should take a look at how Closures work in JavaScript.