Why is clearTimeout not working in this case? - javascript

Can someone explain why the clearTimeout function isn't working below? Whenever I call it, the setTimeout function is still called... Thanks!
let todo = document.getElementById('todo');
let todoList = document.getElementById('todo_list');
let timer;
form.addEventListener('submit', newtodo);
function newtodo() {
let todoItem = document.createElement('li');
let timesItem = document.createElement('i');
timesItem.classList.add('fa', 'fa-times-circle');
todoItem.innerHTML = todo.value;
todoList.appendChild(todoItem).prepend(timesItem);
timesItem.addEventListener('click', function() {
todoItem.style.textDecoration = "line-through";
todoItem.style.color = "grey";
timer = setTimeout(removeTodoItem, 3000);;
timesItem.addEventListener('click', function() {
todoItem.style.textDecoration = "none";
todoItem.style.color = "black";
clearTimeout(timer);
});
});
todo.value = '';
function removeTodoItem() {
todoList.removeChild(todoItem);
}
}

This is the solution I came up with for anyone who's interested:
let todo = document.getElementById('todo');
let todoList = document.getElementById('todo_list');
let timer;
form.addEventListener('submit', newtodo);
function newtodo() {
let todoItem = document.createElement('li');
let timesItem = document.createElement('i');
timesItem.classList.add('fa', 'fa-times-circle');
todoItem.innerHTML = todo.value;
todoList.appendChild(todoItem).prepend(timesItem);
let idx = 0;
timesItem.addEventListener('click', function() {
if(idx == 0) {
todoItem.style.textDecoration = "line-through";
todoItem.style.color = "grey";
timer = setTimeout(removeTodoItem, 3000);;
idx++;
} else {
todoItem.style.textDecoration = "none";
todoItem.style.color = "black";
clearTimeout(timer);
idx--;
}
});
todo.value = '';
function removeTodoItem() {
todoList.removeChild(todoItem);
}
}

Here's Whats happening in this scenario:
newTodo function will run after the form has been submitted.
and by running newTodo you define onClick event listener for a button and a timeout function that will trigger under 3 seconds.
if You click timesItem before 3 seconds it will clearTimeout and Wont run.
If you have any other question ask me.

Related

Variable value doesn't get updated when updating from setTimeout

Here I'm trying to update working inside the setTimeout it gets updated but the addEventListener still has its old value.
const button = document.getElementById('button');
var working = true;
button.addEventListener('click', function() {
const p = document.getElementById('paragraph');
p.innerText = "Im working on something that you guys can't see";
setTimeout(() => {
working = false
}, 5000);
while (working) {
console.log(working) // This one remains true always.
}
p.textContent = "Im done";
});
The timeout is simply not able to execute due to the while hogging the browser.
Use setInterval for the console.log to see it
const button = document.getElementById('button');
const p = document.getElementById('paragraph');
let working = true;
button.addEventListener('click', function() {
p.innerText = "I'm working on something that you guys can't see";
setTimeout(() => {
working = false
}, 5000);
let tId = setInterval(() => {
console.log(working)
if (!working) {
p.textContent = "I'm done";
clearInterval(tId)
}
},100);
});
<button id="button">Click</button>
<p id="paragraph"></p>
To hog on purpose for a time, test in the hogging loop
const button = document.getElementById('button');
const p = document.getElementById('paragraph');
button.addEventListener('click', function() {
p.textContent = "I'm working on something that you guys can't see";
setTimeout(() => {
let endTime = new Date()
endTime.setSeconds(endTime.getSeconds() + 5)
while (true) {
let now = new Date()
if (now.getTime() - endTime.getTime() >= 5000) {
p.textContent = "I'm done";
break;
}
}
}, 10)
});
<button id="button">Click</button>
<p id="paragraph"></p>

pointerEvents = "none" not working as expected

I'm creating a memory card game and it works until I try to click on the cards too fast. When I open two cards, I am calling compareCards function which adds document.body.style.pointerEvents = "none"; but obviously I can click on the third card if I am fast enough. How can I fix it? Here is my full JS code, note that class .flip adds pointer-events: none; among other things while .match adds short animation. I guess it probably has something to do with setTimeouts but I need them in order to show animatioins.
const playBtn = document.querySelector(".intro button");
const restartBtn = document.querySelectorAll(".restartBtn");
const introScreen = document.querySelector(".intro");
const game = document.querySelector(".game");
const gameContainer = document.querySelector("#gameContainer");
const timer = document.querySelector(".timer span");
const moves = document.querySelector(".moves span");
let time,
minutes = 0,
seconds = 0;
let numberOfMoves = 0;
moves.innerHTML = numberOfMoves;
let openCards = [];
let matchedCards = [];
function startGame() {
let shuffledDeck = shuffle(deckCards);
for (let i = 0; i < shuffledDeck.length; i++) {
const card = document.createElement("div");
card.classList.add("card");
const image = document.createElement("img");
image.setAttribute("src", "img/" + shuffledDeck[i]);
card.appendChild(image);
gameContainer.appendChild(card);
}
runTimer();
}
const deckCards = [
... images to add to game ...];
gameContainer.addEventListener("click", function (e) {
if (e.target.className === "card") {
flipCard();
}
function flipCard() {
e.target.classList.add("flip");
addCard();
}
function addCard() {
if (openCards.length == 0 || openCards.length == 1) {
openCards.push(e.target.firstElementChild);
}
compareCards();
}
});
function compareCards() {
if (openCards.length == 2) {
document.body.style.pointerEvents = "none";
}
if (openCards[0].src == openCards[1].src && openCards.length == 2) {
cardsMatched();
} else if (openCards[0].src !== openCards[1].src && openCards.length == 2) {
cardsNotMatched();
}
}
function countMoves() {
numberOfMoves++;
moves.innerHTML = numberOfMoves;
}
function cardsMatched() {
setTimeout(function () {
openCards[0].parentElement.classList.add("match");
openCards[1].parentElement.classList.add("match");
matchedCards.push(...openCards);
document.body.style.pointerEvents = "auto";
gameWon();
openCards = [];
}, 500);
countMoves();
}
function cardsNotMatched() {
setTimeout(function () {
openCards[0].parentElement.classList.remove("flip");
openCards[1].parentElement.classList.remove("flip");
document.body.style.pointerEvents = "auto";
openCards = [];
}, 500);
countMoves();
}
function gameWon() {
if (matchedCards.length == 16) {
stopTimer();
showModal();
}
}
const modal = document.querySelector(".modal");
function showModal() {
const closeModal = document.querySelector(".closeBtn");
modal.style.display = "block";
closeModal.addEventListener("click", () => {
modal.style.display = "none";
});
window.onclick = function (event) {
if (event.target == modal) {
modal.style.display = "none";
}
};
}
function resetEverything() {
modal.style.display = "none";
stopTimer();
timer.innerHTML = `00:00`;
numberOfMoves = 0;
moves.innerHTML = numberOfMoves;
matchedCards = [];
openCards = [];
startGame();
}
function shuffle(array) {
let currentIndex = array.length,
randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
function runTimer() {
time = setInterval(() => {
seconds++;
if (seconds == 60) {
minutes++;
seconds = 0;
}
timer.innerHTML = `${minutes < 10 ? `0${minutes}` : minutes}:${
seconds < 10 ? `0${seconds}` : seconds
}`;
}, 1000);
}
function stopTimer() {
seconds = 0;
minutes = 0;
clearInterval(time);
}
playBtn.addEventListener("click", () => {
introScreen.classList.remove("fadeIn");
introScreen.classList.add("fadeOut");
game.classList.add("fadeIn");
startGame();
});
What you're running into is a common problem where it takes time for things like CSS to propagate through the website, especially when you have timeouts. What you should do is VERY RARELY rely on CSS classes for your logic and let CSS be what it's meant to be: a purely visual medium.
The solution is simple. Instead of relying on pointer events, set up a flag that toggles whether the user is allowed to click on something or not, and then reference that flag for all your logic. This allows you to decouple what you see from what's happening underneath the hood. Something like this (only the relevant bits):
let canAction = true; // add a flag
function startGame() {
// ... start game logic
canAction = true; // set the flag to allow actions
}
gameContainer.addEventListener("click", function (e) {
if (canAction) {
// ... card click logic
}
});
function compareCards() {
if (openCards.length == 2) {
canAction = false; // stop user from taking further action
}
// ... rest of compare card logic
}
function cardsMatched() {
canAction = true; // re allow the user to click on cards - put this inside the setTimeout or outside depending on what you need
setTimeout(function () {
// ...
}, 500);
countMoves();
}
function cardsNotMatched() {
canAction = true; // re allow the user to click on cards - put this inside the setTimeout or outside depending on what you need
setTimeout(function () {
// ...
}, 500);
countMoves();
}
Of course you are free to keep the pointer-events CSS stuff as well, but don't rely on it for your logic. You'll just be inviting a whole lot of messy situations like this.

Trying to shorten my code to make multiple modals work javascript

I am trying to make modal-contents on 5 images, I've tried for several hours to shorten my code with for loops but I still have lots of lines which I don't know how to shorten. I'm just starting with javascript. This is my code below, working but way too long.
var modal = [];
for (i=0; i<5; i++)
{
modal[i] = document.getElementById('simplemodal'+i);
}
var modalbtn = [];
for (i=0; i<5; i++) {
modalbtn[i] = document.getElementById('pics-post'+i);
modalbtn[i].addEventListener('click', eval('openmodal'+i));
window.addEventListener('click', eval('clickoutside'+i));
}
var closebtn0 = document.getElementsByClassName('closebtn')[0];
var closebtn1 = document.getElementsByClassName('closebtn')[1];
var closebtn2 = document.getElementsByClassName('closebtn')[2];
var closebtn3 = document.getElementsByClassName('closebtn')[3];
var closebtn4 = document.getElementsByClassName('closebtn')[4];
for (i=0; i<5; i++) {
eval('closebtn'+i.addEventListener('click', eval('closemodal'+i)));
}
function openmodal0(){
modal[0].style.display = 'block';
}
function openmodal1(){
modal[1].style.display = 'block';
}
function openmodal2(){
modal[2].style.display = 'block';
}
function openmodal3(){
modal[3].style.display = 'block';
}
function openmodal4(){
modal[4].style.display = 'block';
}
function closemodal0(){
modal[0].style.display = 'none';
}
function closemodal1(){
modal[1].style.display = 'none';
}
function closemodal2(){
modal[2].style.display = 'none';
}
function closemodal3(){
modal[3].style.display = 'none';
}
function closemodal4(){
modal[4].style.display = 'none';
}
function closemodal5(){
modal[5].style.display = 'none';
}
function clickoutside0(e){
if(e.target == modal[0])
modal[0].style.display = 'none';
}
function clickoutside1(e){
if(e.target == modal[1])
modal[1].style.display = 'none';
}
function clickoutside2(e){
if(e.target == modal[2])
modal[2].style.display = 'none';
}
function clickoutside3(e){
if(e.target == modal[3])
modal[3].style.display = 'none';
}
function clickoutside4(e){
if(e.target == modal[4])
modal[4].style.display = 'none';
}
I've tried to use eval to increment the variables name but was not able to find the right way to make it work.
You can put everything into a single loop and use anonymous functions.
var modal = [];
var modalbtn = [];
var closebtn = [];
for (i=0; i<5; i++) {
modal[i] = document.getElementById('simplemodal'+i);
modalbtn[i] = document.getElementById('pics-post'+i);
modalbtn[i].addEventListener('click', () => {
modal[i].style.display = 'block';
});
window.addEventListener('click', eval('clickoutside'+i));
closebtn[i] = document.getElementsByClassName('closebtn')[i];
closebtn[i].addEventListener('click', () => {
modal[i].style.display = 'none';
});
}
You could use a common function for opening model and closing model like:
function openmodel (i) {
model[i].style.display = 'block'
}
function clickoutside(e, i){
if(e.target == modal[i])
modal[i].style.display = 'none';
}
And change you event listener like:
modalbtn[i].addEventListener('click', function () {
openmodel(i)
})
window.addEventListener('click', function (e) {
clickoutside(e, i)
})
And insted of this
eval('closebtn'+i.addEventListener('click', eval('closemodal'+i)))
you can use:
var closebtn = document.getElementsByClassName('closebtn')
for (let i = 0; i < closebtn.length; i++) {
closebtn[i].addEventListener('click', function () {
closeModel(i)
})
}
And change closeModel function like:
function closemodal(i){
modal[i].style.display = 'none';
}

Ending a function before I recursively call it again

function startGame(diff){
if (diff === 'easy'){
loadEasy();
}
}
function loadEasy(){
difficultyPage.style.display = 'none';
game.style.display = 'block';
medHardTemplate.style.display = 'none';
easyTemplate.style.display = 'block';
newGame.addEventListener('click', function(){
changeDifficulty('easy');
});
colors = randomColorsArray(3);
correctColor = colorToChoose(colors);
colorRGB.innerHTML = correctColor;
for (var i = 0; i < easySquares.length; i++) {
//add colors to squares
easySquares[i].style.backgroundColor = colors[i];
easySquares[i].addEventListener('click', function(){
var clickedColor = this.style.backgroundColor;
if(clickedColor === correctColor) {
changeColorsOnWinEasy(correctColor);
message.innerHTML = "Correct!"
again.textContent = "Play Again?"
again.addEventListener('click', function(){
again.textContent = "NEW COLORS";
header.style.backgroundColor = "#232323";
colorRGB.style.backgroundColor = "#232323";
message.innerHTML = "";
changeDifficulty('easy');
});
}
else {
this.style.backgroundColor = "#232323";
message.innerHTML = "Wrong!"
}
});
}
}
I don't know when to return the function so that I don't have many functions running at the same time if I spam the newGame button causing my app to lag. I added a return; at the end of the loadEasy function but that didn't seem to do anything.
You may set a flag indicating the current gametype, then you dont need to rebind a button handler everytime, you just need to define the button as start the current game:
let gametype = "easy":
function startGame(diff){
gametype = diff || gametype;
if (gametype === 'easy'){
loadEasy();
}
else {
throw Error('unknown gametype: ' + gametype);
}
}
function loadEasy(){
//... Whatever
}
newGame.addEventListener('onclick', function(){
startGame();
});
So to start a game with the current gametype do:
startGame();
To start a different one, pass a parameter:
startGame("medium");

how to control speed in this javascript toggle

i want control speed in this function , please help me!
<script>
function toggle(target)
{
var artz = document.getElementsByClassName('showhidemenu');
var targ = document.getElementById(target);
var isVis = targ.style.display=='block';
// hide all
for(var i=0;i<artz.length;i++)
{
artz[i].style.display = 'none';
}
// toggle current
targ.style.display = isVis?'none':'block';
return false;
}
</script>
If you just want a delay, try this:
function toggle(target, milliseconds)
{
setTimeout(function() {
var artz = document.getElementsByClassName('showhidemenu');
var targ = document.getElementById(target);
var isVis = targ.style.display=='block';
// hide all
for(var i=0;i<artz.length;i++)
{
artz[i].style.display = 'none';
}
// toggle current
targ.style.display = isVis?'none':'block';
return false;
}, milliseconds);
}
You'll lose the return value, though.
Call your toggle function with an timeout as per your requirement
Use
window.setTimeout(toggle(),2000);
This will call your toggle function after a delay of 2000 ms.
cant show this function with animation ??
function toggle(target, milliseconds)
{
setTimeout(function() {
var artz = document.getElementsByClassName('showhidemenu');
var targ = document.getElementById(target);
var isVis = targ.style.display=='block';
// hide all
for(var i=0;i<artz.length;i++)
{
artz[i].style.display = 'none';
}
// toggle current
targ.style.display = isVis?'none':'block';
return false;
}, milliseconds);
}

Categories