I am trying to make each number displayed clickable. "1" should alert() 80, "2" should produce 60, etc.
However, when the alert(adjust) is called, it only shows 0, not the correct numbers. However, if the commented out alert(adjust) is uncommented, it produces the correct number on page load, but not on clicking.
I was wondering why the code inside addEvents cannot access the previously defined variable adjust.
<html>
<head>
<script type="text/javascript" charset="utf-8" src="mootools.js"></script>
<script type="text/javascript" charset="utf-8">
window.addEvent('domready', function() {
var id_numbers = [1,2,3,4,5];
for(var i = 0; i<id_numbers.length; i++) {
var adjust = (20 * (5 - id_numbers[i]));
// alert(adjust);
$('i_' + id_numbers[i]).addEvents({
'click': function() {
alert(adjust);
}
});
}
});
</script>
</head>
<body>
<div id="i_1">1</div>
<div id="i_2">2</div>
<div id="i_3">3</div>
<div id="i_4">4</div>
<div id="i_5">5</div>
</body>
</html>
Thanks.
You are having a very common closure problem in that for loop.
Variables enclosed in a closure share the same single environment, so by the time the click callbacks are called, the for loop would have run its course, and the adjust variable will be left pointing to the last value it was assigned.
You can solve this with even more closures, using a function factory:
function makeClickHandler(adjust) {
return function() {
alert(adjust);
};
}
// ...
for(var i = 0; i<id_numbers.length; i++) {
var adjust = (20 * (5 - id_numbers[i]));
$('i_' + id_numbers[i]).addEvents({
'click': makeClickHandler(adjust)
});
}
This can be quite a tricky topic, if you are not familiar with how closures work. You may to check out the following Mozilla article for a brief introduction:
Mozilla Dev Center: Working with Closures
Related
I am total beginner (1 week learning this ...and already thinking about quitting before getting crazy :D) and this is my first question, so please, if I do something wrong, just let me know.
I am trying to solve a small Javascript exercise about adding a list to a HTML file with a JS function.
I have created this function, but It is not working. I would say the problem is that I don't know how to indicate the bands name variable inside the ".createTextNode()" .
This is the function I have in a JS file :
function addBands() {
for (i = 0, i < 0, i++) {
var banda = document.createElement("LI");
var nombre= document.createTextNode([0]);
banda.appendChild(nombre);
document.getElementById("band-list").appendChild(banda)
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1> MY FAVORITE BANDS</h1>
<ul id="band-list">
</ul>
<script src="exercisesJS.js"></script>
<script>addBands(['Dire Straits', 'Kansas', 'Steely Dan'])</script>
</body>
</html>
The output should be a list with the name of the 3 bands in the Function, or any other bands (could be 3, or 6 ...etc...
Always check the error console. It's currently telling you that
for (i = 0, i < 0, i++) {
is a syntax error. You mean:
for (i = 0; i < 0; i++) {
That fixes the syntax. However that's still logically wrong, since it means your loop will never run (since the iterator variable i starts at 0, and is told to run while it is under 0, a condition it fails right from the beginning.)
Looking at your code, there's other problems. You're passing an array of band names to the function, but the function isn't set up to receive it. So, we need:
function addBands(bands) {
That means the inner part of the function can access what was passed to it. It also means we can base our loop on the number of bands that were passed, and use the iterator band as the textual output.
function addBands(bands) { //<-- receive incoming array
for (i = 0; i < bands.length; i++) { //iterate bands.length times
var banda = document.createElement("LI");
var nombre= document.createTextNode(bands[i]); //output iterator band name
banda.appendChild(nombre);
document.getElementById("band-list").appendChild(banda)
}
}
While we're here, there's a couple of other optimisations we can make. Firstly, there's no sense in freshly looking up the ul element each time the loop runs. So let's cache it outside the loop. Secondly, while createTextNode() is fine, you may be interested to know that it's easier to just use textContent on the parent node. Putting it all together:
function addBands(bands) { //<-- receive incoming array
let ul = document.getElementById("band-list"); //cache UL
for (i = 0; i < bands.length; i++) {
var banda = document.createElement("LI");
banda.textContent = bands[i];
ul.appendChild(banda)
}
}
Refer to #Utkanos's answer to exactly understand what went wrong in your code. Given that, I suggest the following solution, which uses ES2015 .forEach method of arrays, for looping over your provided list.
function addBands(liArray) {
liArray.forEach(liText => {
const li = document.createElement('li')
const liTextNode = document.createTextNode(liText)
li.appendChild(liTextNode)
document.getElementById("band-list").appendChild(li)
})
}
addBands(['Dire Straits', 'Kansas', 'Steely Dan'])
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1> MY FAVORITE BANDS</h1>
<ul id="band-list">
</ul>
<script></script>
</body>
</html>
First, I am completely new to coding and have been using self-teaching tools to learn Javascript in my free time. I've learned enough to start building my own projects. My first attempt is to build a randomizer (in this case, random restaurant names). The Javascript works through my tests in the console as do the buttons. However, I cannot seem to get the .innerHTML to work and I'm not sure what I'm missing. I've done several searches here and none of the solutions I've found seem to be working.
The error I'm getting is listed in the title and it is appearing at line 29.
Here is Javascript:
var randomRestaurant = {
restaurantName: [],
findRestaurant: function() {
var restaurantName = Math.random();
if (restaurantName < 0.20) {
this.restaurantName.push("China Taste");
}
else if (restaurantName < 0.40) {
this.restaurantName.push("Pizza Johns");
}
else if (restaurantName < 0.60) {
this.restaurantName.push("Liberatore's");
}
else if (restaurantName < 0.80) {
this.restaurantName.push("Bill Bateman's");
}
else {
this.restaurantName.push("R&R Taqueria");
}
},
clearRestaurant: function() {
randomRestaurant.restaurantName.splice(0, 1);
}
};
var randomRestaurantButton = document.getElementById('randomRestaurantName');
randomRestaurantButton.addEventListener('click', function() {
randomRestaurant.findRestaurant();
document.getElementById("restaurantNameDisplay").innerHTML = randomRestaurant.restaurantName[0]; //<--line 29
});
var randomRestaurantButton = document.getElementById('refreshRestaurant');
randomRestaurantButton.addEventListener('click', function() {
randomRestaurant.clearRestaurant();
randomRestaurant.findRestaurant();
document.getElementById("restaurantNameDisplay").innerHTML = randomRestaurant.restaurantName[0];
});
And here is my HTML:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div><h1>Random Restaurant!</h1>
<button id="randomRestaurantName">Click me for a random restaurant!</button>
</div>
<br>
<h2="restaurantNameDisplay"></h2="restaurantNameDisplay">
<div>
<br>
<button id="refreshRestaurant">Nah. Give me another one.</button>
</div>
</body>
<script src="script.js"></script>
</html>
Thanks for your help and hopefully it's not due to something stupid like a typo.
There are some problems here.
the h2 tag id should be
<h2 id="restaurantNameDisplay"></h2>
your buttons are set on the same variable name, change the second to
var refreshRestaurantButton = document.getElementById('refreshRestaurant');
refreshRestaurantButton.addEventListener('click', function () {
randomRestaurant.clearRestaurant();
randomRestaurant.findRestaurant();
document.getElementById("restaurantNameDisplay").innerHTML = randomRestaurant.restaurantName[0];
});
If it's still not working, you should call your script after the page load event.
so insert your javascript code to a function (e.g. "myFunc()") and change your html body tag to:
body onload="myFunc()">
Most probably this line <h2="restaurantNameDisplay"></h2="restaurantNameDisplay"> should be
<h2 id="restaurantNameDisplay"></h2>
I have this click event attached to each button and when I click on each of them, it is printing the output meant for the third button. I'm not sure what is going on.
<!DOCTYPE html>
<html>
<head>
<title>JS Bin</title>
</head>
<body>
<button>test1</button>
<button>test2</button>
<button>test3</button>
</body>
<script>
var nodes = document.getElementsByTagName('button');
for (var i = 0; i < nodes.length; i++) {
nodes[i].addEventListener('click', function() {
console.log('You clicked element #' + i);
});
}
</script>
</html>
when I click on any of the buttons, it is printing
"You clicked element #3"
Simple solution to this:
<!DOCTYPE html>
<html>
<head>
<title>JS Bin</title>
</head>
<body>
<button>test1</button>
<button>test2</button>
<button>test3</button>
</body>
<script>
var nodes = document.getElementsByTagName('button');
console.log(nodes);
for (var i = 0; i < nodes.length; i++) {
//converted click function into an IIFE so it executes then and there only
nodes[i].addEventListener('click', (function (j) {
return function () {
console.log('You clicked element #' + j);
}
})(i));
}
</script>
</html>
You should go through two concepts to understand this thing
1) Closures
2) Javascript is single-threaded and synchronous. So how does it handle events?
Here is what it is happening in your code:
==> for loop gets executed synchronously as it is part of javascript engine post which javascript handles event queue which is a FIFO (first in first out)
==> When for loop finished value of i is three which remains in memory until the function inside it executes
==> Each time it takes a value 3 and prints it.
When this button is listening to event, at that time the value of i is nodes.length -1 that is 2. Because loop has already finished it's execution and have set value of i to 2.
So it is consoling You clicked element #3.
Such issues arise because of scope & closure
Create an IIFE and pass the value of i.
Hope this snippet will be useful
var nodes = document.getElementsByTagName('button');
for (var i = 0; i < nodes.length; i++) {
(function(i){
nodes[i].addEventListener('click', function() {
console.log('You clicked element #' + i);
});
}(i))
}
Check this jsfiddle
This is other way using jQuery.
$("button").each(function(e) {
$(this).data("number", e);
}).bind("click", function(e) {
console.log("You clicked element #" + $(this).data("number"));
});
https://jsfiddle.net/ChaHyukIm/uxsqu70t/3/
This is the closure with function inside a loop issue.
JavaScript closure inside loops – simple practical example
Watch out for this!
Side note: questions around this issue are frequently asked in interviews to demonstrate proficiency with JS.
I'm starter in JavaScript and I try to make simple code to print numbers (0-100) , but with pause in printing, for every next number(for exp. 3 seconds pause).
Code doesn't work properly... It waits 3 seconds and print the last number (100 in my case). Can you help me, where is my mistake?
This is the code:
<html>
<head>
<script type="text/javascript">
function funkcija_polnac()
{
var i = 0;
while (i <= 100) {
setTimeout(function(){ document.write(i + '%');}, 3000);
i++;
}
}</script>
</head>
<body>
<div style="margin: 0px auto;" onclick="funkcija_polnac()">Start</div>
</body>
</html>
What your code does is schedule 101 function callbacks, all of which will happen one right after another about three seconds after the code runs, and all of which will use the i variable, not its value as of when the function was created. So after three seconds you get 101 iterations of the value 101. This is because the functions you're creating are "closures over" the i variable (more accurately, the context in which the variable was created), and so they have an enduring reference to the variable and see its value as of when they use it, not as of when they were created. More about closures on my blog: Closures are not complicated
Or at least, that's what you'd see if it weren't that document.write, when used after initial parsing, blows away the page entirely. Basically: Don't use document.write. :-)
To fix it, you would schedule a single call to a function that, once it's run, schedules the next call. And you'd use the DOM or similar rather than document.write to see the output.
Example:
// NOTE: I used 30ms rather than 3000ms so it runs faster
var i = 0;
showOne();
function showOne() {
display(i);
++i;
if (i <= 100) {
setTimeout(showOne, 30); // You'd really want 3000
}
}
// Displays the given message by adding a paragraph element to the page
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
p {
padding: 0;
margin: 0;
font-family: sans-serif;
}
Well, you are running your while before the timeout happens. Try this
<html>
<head>
<script type="text/javascript">
function funkcija_polnac(i)
{
document.getElementById("output").innerHTML = i + "%";
if (--i > -1) {
setTimeout(function () { funkcija_polnac(i); }, 3000);
}
}
</script>
</head>
<body>
<div style="margin: 0px auto;" onclick="funkcija_polnac(5)">Start</div>
<div id="output"></div>
</body>
</html>
Really a newbie question but I can't seem to find the answer. I need to have this html file show a bunch of random numbers, separated by 1 second intervals. For some reason (maybe obvious) it is only showing me the last one unless I have 1 alert after each random number generated. How can I correct this?
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
var randomnumber
var message
function placePossibleWinner()
{
randomnumber=Math.floor(Math.random()*11);
message="Teste ";
message=message.concat(randomnumber.toString());
document.getElementById("WINNER").innerHTML=message;
//alert(".")
}
</script>
</head>
<body>
<script type="text/javascript">
function runDraw()
{
var i=1
alert("start")
while (i<20)
{
setTimeout("placePossibleWinner()",1000)
i++
}
}
</script>
<h1>H Draw</h1>
<p id="WINNER">Draw</p>
<p></p>
<button onclick="runDraw()">Get me winner!</button>
</body>
</html>
Thanks in advance for any answers/comments.
The problem is all your setTimeouts are being triggered at the same time. Adding alerts pauses the JavaScript execution, so you see each number. Without that, after 1 second, all 19 setTimeouts run (one after another) and you just see one number (the screen is updated so fast, you just see one).
Try using setInterval instead.
function runDraw() {
var i = 1;
var interval = setInterval(function(){
if(i < 20){
placePossibleWinner();
i++;
}
else{
clearInterval(interval);
}
}, 1000);
}
This will run the function once every second, until i is 20, then it will clear the interval.
I believe you want setInterval instead. using setTimeout in a loop will just queue up 20 calls immediately and they will all fire at once 1 second later. Also, you are setting the innerHTML of the p which will overwrite any previous text.
function placePossibleWinner() {
// add a var here, implicit global
var randomnumber=Math.floor(Math.random()*11);
// add a var here, implicit global
message="Teste " + randomnumber + '\n'; // new line
document.getElementById("WINNER").innerHTML += message; // concat, don't assign
}
function runDraw() {
var counter = 1;
var intervalID = setInterval(function () {
if (counter < 20) {
placePossibleWinner();
counter++;
} else {
clearInterval(intervalID);
}
}, 1000);
}
You are resetting your message in your functions and you are calling placePossibleWinner() the wrong way... you want to use setInterval. Below is a modification of your html/javascript
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
var randomnumber;
var message = "Teste ";
var timesCalled = 0;
var funtionPointer;
function placePossibleWinner()
{
timesCalled++;
randomnumber=Math.floor(Math.random()*11);
message=message.concat(randomnumber.toString());
document.getElementById("WINNER").innerHTML=message;
if (timesCalled > 20)
{
clearInterval(functionPointer);
}
}
</script>
</head>
<body>
<script type="text/javascript">
function runDraw()
{
var i=1
alert("start")
functionPointer = setInterval(placePossibleWinner,1000)
}
</script>
<h1>H Draw</h1>
<p id="WINNER">Draw</p>
<p></p>
<button onclick="runDraw()">Get me winner!</button>
</body>
</html>
To start with,
setTimeout("placePossibleWinner()",1000)
should be
setTimeout(placePossibleWinner,1000)
The parameter to setTimeput should be a reference to a function. See JavaScript,setTimeout