Im implementing a calculator, and I'm stuck trying to display the digit on the screen. I iterate trough all my digit to get them, but when I try to replace them in order to display them in my div with the id #nums it won't work. this is the function i'm stuck with
buttons.forEach(button => {
button.addEventListener('click', function(){
console.log('it work')
document.querySelector('#nums').textContent = buttons.innerHTML
})
})
here is a fiddle to see more
function add(a, b) {
return a + b
}
function substract(a, b) {
return a - b
}
function sum(arr) {
result = 0;
for (var i = 0; i < arr.length; i++) {
result += arr[i]
}
return result
}
/*
function multiply_range(arr){
result = 1;
for(var i = 0; i < arr.length; i++){
result *= arr[i]
}
return result
}
*/
function multiply(a, b) {
return a * b
}
function divide(a, b) {
return a / b
}
var sum = document.getElementById('sum');
var substract = document.getElementById('minus')
var multiply = document.getElementById('multiply')
var divide = document.getElementById('divide')
function operate(operator, a, b) {
if (operator === sum) {
return add(a, b);
} else if (operator === substract) {
return substract(a, b);
} else if (operator === multiply) {
return multiply(a, b);
} else if (operator === divide) {
return divide(a, b);
}
}
operate(sum, 1, 1);
var display_value = document.querySelector('#nums');
const buttons = document.querySelectorAll('.number-btn')
// loop through all the buttons
// Object.keys(buttons) transform my object in a array
/*
Object.keys(buttons).forEach(button => {
button.addEventListener('click', function(){
console.log('it work')
})
})
*/
buttons.forEach(button => {
button.addEventListener('click', function() {
console.log('it work')
document.querySelector('#nums').textContent = buttons.innerHTML
})
})
/*
var btn_1 = document.querySelector('#btn-1')
btn_1.addEventListener('click', function(){
console.log('it work')
document.querySelector('#nums').textContent = btn_1.textContent
})
*/
/*
document.querySelector('#nums').textContent = 0;
*/
/*
document.getElementsByClassName('number-btn').addEventListener('click', function(){
display_value == document.queryselector('nums');
})
*/
body {
background-color: black;
}
.container {
display: grid;
grid-template-columns: auto auto auto auto;
grid-gap: 10px;
padding: 10px;
width: 85%;
height: 300px;
margin: 0 auto;
background-color: #cc1515;
}
#btn-equals {
grid-row-start: 2;
grid-column-start: 4;
grid-row-end: 6;
grid-column-end: 4;
}
.number-btn {
border: 0.5px solid black;
background-color: white;
font-size: 30px;
}
.operator-btn {
border: 0.5px solid black;
background-color: black;
color: white;
font-size: 30px;
}
.results {
margin: 0 auto;
width: 90%;
height: 50px;
background-color: white;
}
.contour {
background-color: lightblue;
position: absolute;
top: 30%;
left: 35%;
width: 400px;
margin: auto;
vertical-align: middle;
}
#nums {
font-size: 40px;
text-align: right;
}
#operator {
font-size: 30px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="contour">
<p>The calculator</p>
<div id="results" class="results">
<div id="nums">55</div>
</div>
<div class="container">
<button id="sum" class="operator-btn">+</button>
<button id="minus" class="operator-btn">-</button>
<button id="multiply" class="operator-btn">x</button>
<button id="divide" class="operator-btn">/</button>
<button id="btn-7" class="number-btn">7</button>
<button id="btn-8" class="number-btn">8</button>
<button id="btn-9" class="number-btn">9</button>
<button id="btn-4" class="number-btn">4</button>
<button id="btn-5" class="number-btn">5</button>
<button id="btn-6" class="number-btn">6</button>
<button id="btn-1" class="number-btn">1</button>
<button id="btn-2" class="number-btn">2</button>
<button id="btn-3" class="number-btn">3</button>
<button id="btn-period" class="number-btn">.</button>
<button id="btn-O" class="number-btn">0</button>
<button id="btn-clear" class="number-btn">AC</button>
<button id="btn-equals" class="operator-btn">=</button>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
hope someone can help
use button.innerHTML not buttons.innerHTML
The array is called buttons - each item you're pulling out is being initialized as button. You want to set the div equal to that item's innerHTML, not the array buttons - which, as it is an array to begin with, does not have an innerHTML property. Furthermore, it wasn't clear in your question, but if you would like to keep adding digits to the calculator box, be sure to use the += operator instead of the =, like so document.querySelector('#nums').textContent += button.innerHTML That way it will keep adding to each box on button press.
If you would like the buttons to just replace the previous item in the calculator window, this will work:
buttons.forEach(button => {
button.addEventListener('click', function(){
document.querySelector('#nums').textContent = button.innerHTML
})
})
EDIT: As a matter of fact, since you just want the text node within your HTML, it would be better for performance to simply use button.textContent or as #Barmar pointed out, this.textContent ( this also references button )
textContent is faster because when you utilize innerHTML the Browser Engine has to reprocess and parse everything while it copies it over. textContent specifically only deals with a text node and the content therein.
buttons.forEach(button => {
button.addEventListener('click', function(){
document.querySelector('#nums').textContent = button.textContent;
})
})
It should be button and not buttons :)
document.querySelector('#nums').textContent = button.innerHTML
document.querySelector('#nums').textContent += button.innerHTML;
Related
I am stucked with the logic of one exercise from The Odin Project. I am actually working on a simple calculator and it's almost done (except for minor bugs I think) but I need to implement the last functionality and honestly I don't know where to start.
Basically the exercise says:
"Users should be able to string together several operations and get
the right answer, with each pair of numbers being evaluated at a time.
For example, 12 + 7 - 5 * 3 = should yield 42.
Your calculator should not evaluate more than a single pair of numbers
at a time. Example: you press a number button (12), followed by an
operator button (+), a second number button (7), and finally a second
operator button (-). Your calculator should then do the following:
first, evaluate the first pair of numbers (12 + 7), second, display
the result of that calculation (19), and finally, use that result (19)
as the first number in your new calculation, along with the next
operator (-)."
The thing is, I'm very lost and confused about this last step and when I try to operate like that on my calculator it simply does not work. It's like I have to priorize multiplying and dividing over adding and subtracting, right? Could anyone enlight me?
Here is the code:
const displayPrevResult = document.querySelector('.prev-result');
const displayCurrentResult = document.querySelector('.current-result');
const equal = document.querySelector('.equal');
const decimal = document.querySelector('.decimal');
const clear = document.querySelector('.clear');
const numberBtn = document.querySelectorAll('.number');
const operatorBtn = document.querySelectorAll('.operator');
let current = '';
let previous = '';
let opString = '';
numberBtn.forEach((button) => {
button.addEventListener('click', (e) => {
getNum(e.target.textContent);
})
})
operatorBtn.forEach((button) => {
button.addEventListener('click', (e) => {
getOp(e.target.textContent);
})
})
clear.addEventListener('click', clearCalc);
// Operate when clicking equal
equal.addEventListener('click', () => {
current = parseFloat(current);
previous = parseFloat(previous);
if (opString === '+') {
current = add(previous, current);
} else if (opString === '-') {
current = subtract(previous, current);
} else if (opString === 'x') {
current = multiply(previous, current);
} else if (opString === '÷') {
if (current === 0) {
clearCalc();
displayCurrentResult.textContent = 'ERROR';
return;
}
current = divide(previous, current);
}
displayCurrentResult.textContent = current;
})
function clearCalc() {
current = '';
previous = '';
displayCurrentResult.textContent = '0';
displayPrevResult.textContent = '';
}
// Store current number, get operator and display it
function getOp(opStr) {
opString = opStr;
previous = current;
displayPrevResult.textContent = previous;
current = '';
}
// Get the number and display it
function getNum(num) {
current += num;
displayCurrentResult.textContent = current;
}
// Operating functions
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
return a / b;
}
function operate(a, b) {
return divide(b, a);
}
console.log(operate(22, 4));
body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.calcContainer {
background: linear-gradient(276deg, #40a179, #77cea9);
padding: 1em;
border-radius: 5px;
border: 1px solid #000;
}
button {
padding: 1em;
margin: 0.1em;
width: 40px;
background: #a2ffaf;
border: 1px solid #fff;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background: #72e782;
}
.clr {
background: #87e4bd;
}
.clr:hover {
background: #53ad88;
}
.clear {
margin: 0em 0.1em 0.5em 0.5em;
padding: 0;
}
.output-clear-container {
display: flex;
}
.output {
flex-grow: 1;
height: 40px;
background: #c2fcca;
border-radius: 5px;
border: 1px solid #fff;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
padding-right: 0.5em;
margin-bottom: 0.5em;
}
.par {
margin-bottom: 0.3em;
}
.prev-result {
font-size: 14px;
padding-bottom: 0.3em;
color:#40a179;
}
.current-result {
font-size: 18px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<script src="main.js" defer></script>
<title>Calculator</title>
</head>
<body>
<div class="calcContainer">
<div class="output-clear-container">
<div class="output">
<div class="prev-result"></div>
<div class="current-result">0</div>
</div>
<button class="clear">AC</button>
</div>
<div class="par">
<button class="number">7</button>
<button class="number">8</button>
<button class="number">9</button>
<button class="operator clr">÷</button>
</div>
<div class="par">
<button class="number">4</button>
<button class="number">5</button>
<button class="number">6</button>
<button class="operator clr">x</button>
</div>
<div class="par">
<button class="number">1</button>
<button class="number">2</button>
<button class="number">3</button>
<button class="operator clr">-</button>
</div>
<div class="par">
<button class="decimal clr">.</button>
<button class="number">0</button>
<button class="equal clr">=</button>
<button class="operator clr">+</button>
</div>
</div>
</body>
</html>
Thank you very much.
I made a TicTacToe game that happily works. I'm trying to solve two things though.
The opponent's move in "DumbAI" shows immediately after I choose mine. When I impose a setTimeout(), and the AI opponent wins, the endgame sequence does not fire. It works when I win though.
The endgame sequence is that when anyone gets 3 in a row, an alert is supposed to flash, the 3 squares that won are highlighted and the eventlistener is removed so no more marks can be made.
Instead, the code lets me swap to the active player. And if the active player gets 3 in a row, the endgame sequence fires.
All these functions are in the same block. By putting a setTimeout() on the opponent's move, is it skipping over the endgame sequence?
Similarly, when I break out the endgame sequence into a separate block, another issue occurs.
When I take the endgame sequence out of the block and I win, the code will flash the alert and highlight the spaces, but it will also allow the AI opponent to make an extra move.
By taking the endgame sequence out of the block, is the computer moving too quickly through the code by allowing opponent to take his turn before firing the endgame sequence?
script.js:
var ONE_CLASS
var TWO_CLASS
const btn = document.querySelector('#PlayerOneSymbol');
btn.onclick = function () {
const XOs = document.querySelectorAll('input[name="choice"]');
for (const XO of XOs) {
if (XO.checked) {
ONE_CLASS = XO.value
TWO_CLASS = XO.value == 'X' ? 'O' : 'X'
break;
}
}
alert("First Move Belongs to " + ONE_CLASS + ". Select Player Two.");
};
var playerTwoIdentity
const btn2 = document.querySelector('#PlayerTwoChoice');
btn2.onclick = function () {
const Opponents = document.querySelectorAll('input[name="choice2"]');
for (const Opponent of Opponents) {
if (Opponent.checked) {
playerTwoIdentity = Opponent.value
break;
}
}
alert("Your Opponent is " + playerTwoIdentity + ". Start New Game.")
};
let playerOneTurn
function swapTurns() {
playerOneTurn = !playerOneTurn
};
const winningTrios = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[6, 4, 2]
]
restartBtn.addEventListener('click', startGame);
function startGame() {
if (ONE_CLASS == undefined || playerTwoIdentity == undefined) {return alert ("Make sure players are defined")}
console.log("player 1 = " + ONE_CLASS + ", player 2 = " + playerTwoIdentity)
drawBoard();
playerOneTurn = true;
}
const arrayfromBoxes = Array.from(document.getElementsByClassName('box'));
const stylingOfBoxes = document.querySelectorAll('.box');
function drawBoard() {
console.log(stylingOfBoxes)
for (let i = 0; i < stylingOfBoxes.length; i++) {
stylingOfBoxes[i].addEventListener('click', boxmarked, {once: true});}
stylingOfBoxes.forEach(gridBox => {
gridBox.classList.remove(ONE_CLASS)
gridBox.classList.remove(TWO_CLASS)
gridBox.classList.remove('winner')
gridBox.innerHTML = ""
})
}
function boxmarked(e) {
const index = arrayfromBoxes.indexOf(e.target)
// how to consolidate? maybe I just let ONE_CLASS mark and then if the AI or player
// or do it even earlier and link it with playerTurn?
if(playerOneTurn) {
arrayfromBoxes[index].classList.add(ONE_CLASS)
e.target.innerHTML = ONE_CLASS
} else {
arrayfromBoxes[index].classList.add(TWO_CLASS)
e.target.innerHTML = TWO_CLASS
}
// if (playerhasWon()) {
// declareWinner()
// return
// }
// if (emptySpaceRemains() == false) {
// declareTie()
// return
// }
hasGameEnded()
swapTurns()
// eliminate repetition -
if(playerTwoIdentity === "Dumb AI") {
var dumbAIArray = arrayfromBoxes.reduce((dumbAIArray, box, idx) => {
if (box.innerHTML === "") {
dumbAIArray.push(idx);
}
return dumbAIArray;
}, []);
let dumbAIpicked = dumbAIArray[Math.floor(dumbAIArray.length * (Math.random()))]
arrayfromBoxes[dumbAIpicked].classList.add(TWO_CLASS)
arrayfromBoxes[dumbAIpicked].innerHTML = TWO_CLASS
// why does Timeoutfunction prevent opponent sequence?
// setTimeout(() => {arrayfromBoxes[dumbAIpicked].classList.add(TWO_CLASS)}, 500);
// setTimeout(() => {arrayfromBoxes[dumbAIpicked].innerHTML = TWO_CLASS}, 500);
// if (playerhasWon()) {
// declareWinner()
// return
// }
// if (emptySpaceRemains() == false) {
// declareTie()
// return
// }
hasGameEnded()
swapTurns()
} else { console.log("Human")
}
}
function hasGameEnded() {
// fix declareWinner() appears before the added classes bc alert happens quicker than redraw
// I also cannot pull these out because then the opponent move fires and shows
// could have something to do with timing of in-block code
if (playerhasWon()) {
declareWinner()
return
}
if (emptySpaceRemains() == false) {
declareTie()
return
}
}
function checkClass() {
if(playerOneTurn) {
return ONE_CLASS
} else {
return TWO_CLASS
};}
function emptySpaceRemains() {
var innerHTMLempty = (insidebox) => insidebox.innerHTML===""
console.log(arrayfromBoxes.some(innerHTMLempty))
return (arrayfromBoxes.some(innerHTMLempty))
}
function declareTie() {
setTimeout(alert ("TIE GAME"), 1000)}
function playerhasWon() {
var indexOfSelected = arrayfromBoxes.reduce((indexOfSelected, box, idx) => {
if (box.classList[1] === checkClass()) {
indexOfSelected.push(idx);
}
return indexOfSelected;
}, []);
const winningThreeIndexes = winningTrios
.map(trio => trio.filter(i => indexOfSelected.includes(i)))
.filter(i => i.length === 3);
console.log(winningThreeIndexes)
console.log(winningThreeIndexes.length)
if (winningThreeIndexes.length === 1) {winningThreeIndexes[0].map((index) => {arrayfromBoxes[index].className += ' winner'})}
var isThereAWinner =
winningTrios.some(trio => {return trio.every(i => indexOfSelected.includes(i))});
console.log({isThereAWinner});
return isThereAWinner
}
function declareWinner() {
setTimeout(alert (checkClass() + " WINS"), 1000);
for (let i=0; i < stylingOfBoxes.length; i++) {
stylingOfBoxes[i].removeEventListener('click', boxmarked, {once: true});}
}
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tic Tac Toe</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1 id="playtext">Let's Play</h1>
<div class="radioContainer">
<div id="playerOne">
<h3>Player One</h3>
<form>
<input type="radio" name="choice" value="X"> X<br>
<input type="radio" name="choice" value="O"> O<br>
<input type="button" id="PlayerOneSymbol" value="Confirm">
</form>
</div>
<div id="playerTwo">
<h3>Player Two</h3>
<form>
<input type="radio" name="choice2" value="Human"> Human<br>
<input type="radio" name="choice2" value="Dumb AI"> Dumb AI<br>
<input type="radio" name="choice2" value="Smart AI"> Smart AI<br>
<input type="button" id="PlayerTwoChoice" value="Confirm">
</form>
</div>
</div>
<div class="buttonHolder">
<div class="buttonWrapper">
<button id="restartBtn">Start New Game</button>
</div>
</div>
<div class="gameboard">
<div class="box" ></div>
<div class="box" ></div>
<div class="box" ></div>
<div class="box" ></div>
<div class="box" ></div>
<div class="box" ></div>
<div class="box" ></div>
<div class="box" ></div>
<div class="box" ></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
style.css:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
}
#playtext {
text-align: center;
padding: 10px;
}
.buttonHolder {
height: 60px;
width: 100%;
float: left;
position: relative;
background-color: purple;
}
.buttonWrapper {
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.container {
background-color: purple;
justify-content: center;
/* display: flex;
flex-wrap: wrap; */
width: 400px;
height: 600px;
}
#gameboard {
border-top:10px;
border-bottom: 4px;
border-bottom-color: black;
background-color: chartreuse;
}
.box {
background-color: yellow;
width: 125px;
height: 125px;
float: left;
width: 33.33%;
}
button:hover {
cursor: pointer;
transform: translateY(-2px);
}
.winner {
background-color: black;
}
.X {
content: 'X';
font-size: 135px;
}
.O {
content: 'O';
font-size: 135px;
}
#spacer {
height: 10px;
width: 100%;
background-color: purple;
padding: 10px;
}
#playerOne {
background-color: blanchedalmond;
padding: 5px;
height: 110px;
float: left;
width: 50%;
}
#playerTwo {
background-color: mintcream;
padding: 5px;
height: 110px;
float: left;
width: 50%;
}
A friend helped me figure out what's happening in #1 -- I think. He points out that JS is asynchronous. I had three functions:
Opponent puts down marker in a space.
Board is evaluated to see if opponent won.
If not, it switches turns and lets player pick a space.
If so, it ends the game and prevents picking a space
I was hoping when (1) was delayed, (2) and (3) wouldn't fire until (1) did.
But in reality (1) is delayed, so (2) runs anyway and doesn't see the opponent win and so (3) lets player pick a space.
So to fix this, I put the timeout on all 3 functions:
setTimeout(() => {
let dumbAIpicked = dumbAIArray[Math.floor(dumbAIArray.length * (Math.random()))]
arrayfromBoxes[dumbAIpicked].classList.add(TWO_CLASS)
arrayfromBoxes[dumbAIpicked].innerHTML = TWO_CLASS
if (playerhasWon()) {
declareWinner()
return
}
if (emptySpaceRemains() == false) {
declareTie()
return
}
// hasGameEnded()
swapTurns()
``}, 500);
So, this is a simple counter script:
//variables
let counter = document.querySelector('.counter');
let decrementCounter = document.querySelector('.decrement-counter');
let incrementCounter = document.querySelector('.increment-counter');
let count = 0;
//event listeners
decrementCounter.addEventListener('click', minusCounter);
incrementCounter.addEventListener('click', plusCounter);
function plusCounter() {
count++;
counter.innerHTML = count;
if (counter.innerHTML > '0') {
counter.style.color = 'green';
} else if (counter.innerHTML === '0') {
counter.style.color = 'black';
}
}
function minusCounter() {
count--;
counter.innerHTML = count;
if (counter.innerHTML < '0') {
counter.style.color = 'red';
} else if (counter.innerHTML === '0') {
counter.style.color = 'black';
}
}
body {
margin: 0;
padding: 0;
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
}
<body>
<div class="counter">
<p>0</p>
</div>
<button class="decrement-counter">Decrement</button>
<button class="increment-counter">Increment</button>
</body>
(ignore the bad design, It was just for test purpose)
I wanted to do the same script but with a constructor/factory function. Or just with a simple object(encapsulation).
Maybe I missed something essential and that's why I failed, can someone show me an example of each?
As mentioned in the comments it is somewhat unclear what sort of encapsulation you are looking for, but here is a simple refactoring of your code into a function which accepts a container element and returns an object containing references to the set() function which instantiates your timer functionality on the children of that element, and a cleanup() function which removes the instantiation.
It requires that the necessary constituent elements exist within the container, but you could expand on this to either completely build the timer within the function or at least do some checks on the existence of elements so as not to break if they are missing.
const setCounter = (element) => {
//variables
const decrementCounter = element.querySelector('.decrement-counter');
const incrementCounter = element.querySelector('.increment-counter');
const output = element.querySelector('.output');
let count = 0;
function plusCounter () {
count++;
output.innerHTML = count;
if (output.innerHTML > '0') {
output.style.color = 'green';
} else if (output.innerHTML === '0') {
output.style.color = 'black';
}
}
function minusCounter () {
count--;
output.innerHTML = count;
if (output.innerHTML < '0') {
output.style.color = 'red';
} else if (output.innerHTML === '0') {
output.style.color = 'black';
}
}
const set = () => {
decrementCounter.addEventListener('click', minusCounter);
incrementCounter.addEventListener('click', plusCounter);
element.classList.add('active');
}
const cleanup = () => {
decrementCounter.removeEventListener('click', minusCounter);
incrementCounter.removeEventListener('click', plusCounter);
count = 0;
output.innerHTML = "0";
output.style.color = 'black';
element.classList.remove('active');
}
return {
set: set,
cleanup: cleanup,
}
}
const counter = setCounter(document.getElementById('counter-1'));
document.querySelector('.set-counter1').addEventListener('click', counter.set);
document.querySelector('.cleanup-counter1').addEventListener('click', counter.cleanup);
const counter2 = setCounter(document.getElementById('counter-2'));
document.querySelector('.set-counter2').addEventListener('click', counter2.set);
document.querySelector('.cleanup-counter2').addEventListener('click', counter2.cleanup);
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.output {
text-align: center;
background-color: white;
width: 2rem;
margin: 1rem auto;
}
.counter-control {
width: 100vw;
text-align: center;
padding: 8px;
}
.active {
background-color: aquamarine;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css.css" type="text/css">
<title>Document</title>
</head>
<body>
<div id="counter-1" class="counter">
<p class="output">0</p>
<button class="decrement-counter">Decrement</button>
<button class="increment-counter">Increment</button>
</div>
<div class="counter-control">
<button class="set-counter1">Set Counter</button>
<button class="cleanup-counter1">Cleanup Counter</button>
</div>
<div id="counter-2" class="counter">
<p class="output">0</p>
<button class="decrement-counter">Decrement</button>
<button class="increment-counter">Increment</button>
</div>
<div class="counter-control">
<button class="set-counter2">Set Counter</button>
<button class="cleanup-counter2">Cleanup Counter</button>
</div>
</body>
</html>
A simple multiple choice quiz with one problem I can't solve. At first When I clicked the 'next question' button the next question and answers didn't show only when clicked a second time the next question and answers showed.
When I placed runningQuestion++ above questions[runningQuestion].displayAnswers()
like I did in the nextQuestion function the initial problem is solved but reappears after the last question when you are asked to try again. Only now when you click 'try again' now ofcourse it skips the first question.
class Question {
constructor(question, answers, correct) {
this.question = question;
this.answers = answers;
this.correct = correct;
}
displayAnswers() {
document.querySelector('.question').innerHTML = `<div class="q1">${this.question}</div>`
let i = 0
let answers = this.answers
for (let el of answers) {
let html = `<div class="name" id=${i}>${el}</div>`
document.querySelector('.answers').insertAdjacentHTML('beforeend', html)
i++
}
}
}
const q1 = new Question('What\'s the capitol of Rwanda?', ['A: Dodoma', 'B: Acra', 'C: Kigali'], 2);
const q2 = new Question('What\'s is the square root of 0?', ["A: Not possible", 'B: 0', 'C: 1'], 1);
const q3 = new Question('Who was Rome\'s first emperor?', ['A: Tiberius', 'B: Augustus', 'C: Marcus Aurelius'], 1);
const questions = [q1, q2, q3];
let runningQuestion;
let gamePlaying;
init()
document.querySelector('.button1').addEventListener('click', nextQuestion)
function nextQuestion(e) {
console.log(e.target)
if (gamePlaying === true && runningQuestion <= questions.length - 1) {
clearAnswers()
document.querySelector('.button1').textContent = 'Next Question'
runningQuestion++
questions[runningQuestion].displayAnswers()
}
if (runningQuestion >= questions.length - 1) {
document.querySelector('.button1').textContent = 'Try again!'
runningQuestion = 0
}
}
function clearAnswers() {
document.querySelectorAll('.name').forEach(el => {
el.remove()
})
}
document.querySelector('.button2').addEventListener('click', resetGame)
function resetGame() {
document.querySelector('.button1').textContent = 'Next Question'
clearAnswers()
runningQuestion = 0
questions[runningQuestion].displayAnswers()
}
function init() {
gamePlaying = true;
runningQuestion = 0;
questions[runningQuestion].displayAnswers()
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.container {
display: flex;
width: 400px;
height: auto;
margin: 100px auto;
align-items: center;
flex-direction: column;
}
.question {
margin-top: 40px;
color: rgb(102, 0, 0);
font-size: 1.4rem;
}
.answers {
display: flex;
flex-direction: column;
margin-top: 10px;
height: 100px;
margin-bottom: 15px;
}
.name {
margin-top: 20px;
cursor: pointer;
color: rgb(102, 0, 0);
font-size: 1.2rem;
}
.button1 {
margin-top: 50px;
border-style: none;
width: 350px;
height: 50px;
font-size: 1.4rem;
}
ul>li {
list-style-type: none;
margin-top: 10px;
font-size: 1.2rem;
color: rgb(102, 0, 0);
height: 30px;
cursor: pointer;
display: block;
}
.button2 {
margin-top: 20px;
border-style: none;
width: 350px;
height: 50px;
font-size: 1.4rem;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<title>Quiz</title>
</head>
<body>
<div class="container">
<div class="question"></div>
<div class="answers"></div>
<button type="button" class="button1">Next Question</button>
<button type="button" class="button2">Reset</button>
</div>
<script src="app.js"></script>
</body>
</html>
The problem with the current version is that you reset runningQuestion to 0, and when clicking on the button, you execute nextQuestion, which, as the name implies, goes to the next question (runningQuestion++).
I see 2 ways of solving this. Either the "easy" way, by resetting runningQuestion to -1 so that it goes to 0:
class Question{constructor(e,s,t){this.question=e,this.answers=s,this.correct=t}displayAnswers(){document.querySelector(".question").innerHTML=`<div class="q1">${this.question}</div>`;let e=0,s=this.answers;for(let t of s){let s=`<div class="name" id=${e}>${t}</div>`;document.querySelector(".answers").insertAdjacentHTML("beforeend",s),e++}}}const q1=new Question("What's the capitol of Rwanda?",["A: Dodoma","B: Acra","C: Kigali"],2),q2=new Question("What's is the square root of 0?",["A: Not possible","B: 0","C: 1"],1),q3=new Question("Who was Rome's first emperor?",["A: Tiberius","B: Augustus","C: Marcus Aurelius"],1),questions=[q1,q2,q3];let runningQuestion,gamePlaying;init(),document.querySelector(".button1").addEventListener("click",nextQuestion);
/* Nothing changed above */
function nextQuestion(e) {
runningQuestion++; // <---------------------------------------------------------
if (gamePlaying === true && runningQuestion <= questions.length - 1) {
clearAnswers();
document.querySelector('.button1').textContent = 'Next Question';
questions[runningQuestion].displayAnswers();
}
if (runningQuestion >= questions.length - 1) {
document.querySelector('.button1').textContent = 'Try again!';
runningQuestion = -1; // <-----------------------------------------------------
}
}
/* Nothing changed below */
function clearAnswers(){document.querySelectorAll(".name").forEach(e=>{e.remove()})}function resetGame(){document.querySelector(".button1").textContent="Next Question",clearAnswers(),runningQuestion=0,questions[runningQuestion].displayAnswers()}function init(){gamePlaying=!0,runningQuestion=0,questions[runningQuestion].displayAnswers()}document.querySelector(".button2").addEventListener("click",resetGame);
/* Same CSS as yours */ *{box-sizing:border-box;margin:0;padding:0}.container{display:flex;width:400px;height:auto;margin:100px auto;align-items:center;flex-direction:column}.question{margin-top:40px;color:#600;font-size:1.4rem}.answers{display:flex;flex-direction:column;margin-top:10px;height:100px;margin-bottom:15px}.name{margin-top:20px;cursor:pointer;color:#600;font-size:1.2rem}.button1{margin-top:50px;border-style:none;width:350px;height:50px;font-size:1.4rem}ul>li{list-style-type:none;margin-top:10px;font-size:1.2rem;color:#600;height:30px;cursor:pointer;display:block}.button2{margin-top:20px;border-style:none;width:350px;height:50px;font-size:1.4rem}
<!-- Same HTML as yours --> <div class="container"> <div class="question"></div><div class="answers"></div><button type="button" class="button1">Next Question</button> <button type="button" class="button2">Reset</button></div>
or another way, which I find cleaner. A problem you can run into with your current code, is that if you have other things to keep track of, like a score, for example, you might forget to reset them as well, inside your nextQuestion function. And if you add other stuff, you'll need to reset them in multiple places in your code.
What I would do is simply reuse the resetGame function to reset everything:
class Question{constructor(e,s,t){this.question=e,this.answers=s,this.correct=t}displayAnswers(){document.querySelector(".question").innerHTML=`<div class="q1">${this.question}</div>`;let e=0,s=this.answers;for(let t of s){let s=`<div class="name" id=${e}>${t}</div>`;document.querySelector(".answers").insertAdjacentHTML("beforeend",s),e++}}}const q1=new Question("What's the capitol of Rwanda?",["A: Dodoma","B: Acra","C: Kigali"],2),q2=new Question("What's is the square root of 0?",["A: Not possible","B: 0","C: 1"],1),q3=new Question("Who was Rome's first emperor?",["A: Tiberius","B: Augustus","C: Marcus Aurelius"],1),questions=[q1,q2,q3];let runningQuestion,gamePlaying;
/* Nothing changed above */
const btn1 = document.querySelector('.button1');
init();
btn1.addEventListener("click", onButtonClick);
function isLastQuestion() { return runningQuestion >= questions.length - 1; }
function onButtonClick() {
if (gamePlaying === true && !isLastQuestion()) {
runningQuestion++;
displayQuestion();
} else {
resetGame();
}
}
function displayQuestion() {
clearAnswers();
btn1.textContent = isLastQuestion() ? 'Try again' : 'Next Question';
questions[runningQuestion].displayAnswers();
}
/* Nothing changed below */
function clearAnswers(){document.querySelectorAll(".name").forEach(e=>{e.remove()})}function resetGame(){document.querySelector(".button1").textContent="Next Question",clearAnswers(),runningQuestion=0,questions[runningQuestion].displayAnswers()}function init(){gamePlaying=!0,runningQuestion=0,questions[runningQuestion].displayAnswers()}document.querySelector(".button2").addEventListener("click",resetGame);function init(){gamePlaying=true;runningQuestion = 0;questions[runningQuestion].displayAnswers()}
/* Same CSS as yours */ *{box-sizing:border-box;margin:0;padding:0}.container{display:flex;width:400px;height:auto;margin:100px auto;align-items:center;flex-direction:column}.question{margin-top:40px;color:#600;font-size:1.4rem}.answers{display:flex;flex-direction:column;margin-top:10px;height:100px;margin-bottom:15px}.name{margin-top:20px;cursor:pointer;color:#600;font-size:1.2rem}.button1{margin-top:50px;border-style:none;width:350px;height:50px;font-size:1.4rem}ul>li{list-style-type:none;margin-top:10px;font-size:1.2rem;color:#600;height:30px;cursor:pointer;display:block}.button2{margin-top:20px;border-style:none;width:350px;height:50px;font-size:1.4rem}
<!-- Same HTML as yours --> <div class="container"> <div class="question"></div><div class="answers"></div><button type="button" class="button1">Next Question</button> <button type="button" class="button2">Reset</button></div>
I'm trying to make a Single Page Application with pure JavaScript (no additional frameworks or libraries). The problem is that the values I add to the TODO list are not storing in the localStorage (and are not showing).
I would appreciate any help with that task.
How can I simplify the code? (without using any additional libraries and frameworks (ex.jquery etc.))
Here is my code:
let inputTask = document.getElementById('toDoEl');
let editTask = document.getElementById('editTask');
let checkTask = document.getElementById('list');
let emptyList = document.getElementById('emptyList');
let items = [];
let id = [];
let labelToEdit = null;
const empty = 0;
let pages = ['index', 'add', 'modify'];
load();
function load() {
items = loadFromLocalStorage();
id = getNextId();
items.forEach(item => renderItem(item));
}
function show(shown) {
location.href = '#' + shown;
pages.forEach(function(page) {
document.getElementById(page).style.display = 'none';
});
document.getElementById(shown).style.display = 'block';
return false;
}
function getNextId() {
for (let i = 0; i<items.length; i++) {
let item = items[i];
if (item.id > id) {
id = item.id;
}
}
id++;
return id;
}
function loadFromLocalStorage() {
let localStorageItems = localStorage.getItem('items');
if (localStorageItems === null) {
return [];
}
return JSON.parse(localStorageItems);
}
function saveToLocalStorage() {
localStorage.setItem('items', JSON.stringify(items));
}
function setChecked(checkbox, isDone) {
if (isDone) {
checkbox.classList.add('checked');
checkbox.src = 'https://image.ibb.co/b1WeN9/done_s.png';
let newPosition = checkTask.childElementCount - 1;
let listItem = checkbox.parentNode;
listItem.classList.add('checked');
checkTask.removeChild(listItem);
checkTask.appendChild(listItem);
} else {
checkbox.classList.remove('checked');
checkbox.src = 'https://image.ibb.co/nqRqUp/todo_s.png';
let listItem = checkbox.parentNode;
listItem.classList.remove('checked');
}
}
function renderItem(item) {
let listItem = document.getElementById('item_template').cloneNode(true);
listItem.style.display = 'block';
listItem.setAttribute('data-id', item.id);
let label = listItem.querySelector('label');
label.innerText = item.description;
let checkbox = listItem.querySelector('input');
checkTask.appendChild(listItem);
setChecked(checkbox, item.isDone);
emptyList.style.display = 'none';
return listItem;
}
function createNewElement(task, isDone) {
let item = { isDone, id: id++, description: task };
items.push(item);
saveToLocalStorage();
renderItem(item);
}
function addTask() {
if (inputTask.value) {
createNewElement(inputTask.value, false);
inputTask.value = '';
show('index');
}
}
function modifyTask() {
if (editTask.value) {
let item = findItem(labelToEdit);
item.description = editTask.value;
labelToEdit.innerText = editTask.value;
saveToLocalStorage();
show('index');
}
}
function findItem(child) {
let listItem = child.parentNode;
let id = listItem.getAttribute('data-id');
id = parseInt(id);
let item = items.find(item => item.id === id);
return item;
}
// Chanhe img to checked
function modifyItem(label) {
labelToEdit = label;
editTask.value = label.innerText;
show('modify');
editTask.focus();
editTask.select();
}
function checkItem(checkbox) {
let item = findItem(checkbox);
if (item === null) {
return;
}
item.isDone = !item.isDone;
saveToLocalStorage();
setChecked(checkbox, item.isDone);
}
function deleteItem(input) {
let listItem = input.parentNode;
let id = listItem.getAttribute('data-id');
id= parseInt(id);
for (let i in items) {
if (items[i].id === id) {
items.splice(i, 1);
break;
}
}
if (items.length === empty) {
emptyList.style.display = 'block';
}
saveToLocalStorage();
listItem.parentNode.removeChild(listItem);
}
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
}
h2, li, #notification {
text-align: center;
}
h2 {
font-weight: normal;
margin: 0 auto;
padding-top: 20px;
padding-bottom: 20px;
}
#root {
width: 400px;
height: 550px;
margin: 0 auto;
position: relative;
}
#root>ul {
display: block;
}
#addButton {
display: block;
margin: 0 auto;
}
.checkbox, .delete {
height: 24px;
bottom: 0;
}
.checkbox {
float: left;
}
.delete {
float: right;
}
ul {
margin: 20px 30px 0 30px;
padding-top: 20px;
padding-left: 20px;
text-align: center;
}
#toDoEl {
width: 50%;
}
li {
width: 100%;
list-style: none;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
margin: 15px auto;
}
label {
margin: 0 auto;
text-align: justify;
text-justify: inter-word;
}
label:hover {
cursor: auto;
}
li.checked {
background-color: gray;
}
span.button {
cursor: pointer;
}
#add, #modify {
display: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Homework 12 - Simple TODO List</title>
<link rel="stylesheet" href="./assets/styles.css">
</head>
<body>
<div id="root">
<!--Main page-->
<div id="index">
<h2>Simple TODO Application</h2>
<button class="button" id="addButton" onclick="show('add')">Add New Task</button>
<p id="emptyList">TODO is empty</p>
<ul id="list">
<li id="item_template" style="display: none">
<input class="checkbox" type="image" alt="checkbox" src="https://image.ibb.co/nqRqUp/todo_s.png" onclick="checkItem(this)">
<label onclick="modifyItem(this)"></label>
<input id="delete" class="delete" type="image" alt="remove" src="https://image.ibb.co/dpmqUp/remove_s.jpg" onclick="deleteItem(this)">
</li>
</ul>
</div>
<!--Add page-->
<div id="add">
<h2>Add Task</h2>
<input type="text" id="toDoEl">
<button class="button cancel" onclick="show('index')">Cancel</button>
<button class="button save" onclick="addTask()">Save changes</button>
</div>
<!--Modify page-->
<div id="modify">
<h2>Modify item</h2>
<input type="text" id="editTask">
<button class="button cancel" onclick="show('index')">Cancel</button>
<button class="button save" onclick="modifyTask()">Save changes</button>
</div>
</div>
<script src="./src/app.js"></script>
</body>
</html>
Your code does appear to work. If you console.log(JSON.parse(localStorageItems)) right above line 49 in the loadFromLocalStorage function, it shows as expected in the console. Also, upon refreshing the items persist.
If what you mean is that you're checking localStorage and you don't see the items, it might be that you're looking at the preview version of localStorage. (I'm assuming you're using Chrome.) Hover over the top of the empty section and pull down, this should reveal the values stored. If you click on one, it should show in the preview section. I think this was a Chrome dev tools UI change recently implemented.
I checked your code in Codepen and it works.