clearTimeout(timer) not taken into account - New timer variable created each time - javascript

In the current slideshow I'm building, I use a timer variable to rotate the slides automatically every 4s. Since manual controls are also present, I wanted to reset this timer whenever the controls are used to avoid any premature succession between two slides, but didn't manage to do it.
I supposed it to be a scope problem, but the timer variable is out of the functions that are trying to share it (showSlide(n) and changeSlide(n)). Yet a brand new timer variable seems to be created each time the changeSlide function is called : the slides automatic rotation quickens each time the "next" control is used, as if multiple timeouts were calling the function simultaneously. What is wrong here ?
const slideshows = document.getElementsByClassName("js-slideshow");
[].forEach.call(slideshows, function(slideshow) {
slideshowlize(slideshow);
});
function slideshowlize(slideshow){
const desc = slideshow.getElementsByClassName("js-desc");
const slide = slideshow.getElementsByClassName("js-slide");
let timer;
let index = 0;
const slidePrev = slideshow.querySelector('.js-prev');
const slideNext = slideshow.querySelector('.js-next');
function showSlide(n){
clearTimeout(timer); // This one is not used yet
if(n < 0){
n = slide.length -1;
}
else if(n > slide.length -1){
n = 0;
}
let i;
for(i = 0; i < slide.length; i++){
slide[i].classList.remove("is-shown");
}
for(i = 0; i < desc.length; i++){
desc[i].classList.remove("is-shown");
}
slide[n].classList.add("is-shown");
desc[n].classList.add("is-shown");
index = n;
timer = setTimeout(function(){
changeSlide(1);
}, 4000);
}
function changeSlide(n){ // this is where the magic doesn't happen
clearTimeout(timer);
if (n > 0){
showSlide(index += 1);
} else {
showSlide(index -= 1);
}
timer = setTimeout(function(){
changeSlide(1);
}, 4000);
}
showSlide(index);
slidePrev.addEventListener('click', function(){
changeSlide(-1);
});
slideNext.addEventListener('click', function(){
changeSlide(1);
});
}
Edit : Two different timers were set up. Since showSlide(n) were already resetting the timer, changeSlide(n) had no need to do it too. Thanks to Bergi for pointing it out.
function changeSlide(n){
//removed "clearTimeout(timer);"
if (n > 0){
showSlide(index += 1);
} else {
showSlide(index -= 1);
}
//removed "timer = setTimeout(...);"
}

Related

How to update a variable to use in another scope

I have a variable let second = 20 that i make 1 lower until it hits 0. When it hits 0 i want to stop running a part of my code but the variable second is always 20 when i use it because i make it lower in another scope. Sorry if my explenation is a bit unclear.
Here is the code:
votingEnd = document.querySelector(".imposters__voting");
imposters = document.querySelectorAll(".imposter");
let second = 20;
window.addEventListener("load", function () {
let myinterval;
myinterval = setInterval(function () {
second--;
if (second < 11) {
votingEnd.style.color = "red";
}
votingEnd.innerHTML = `Voting ends in: ${second}s`;
if (second == 0) {
clearInterval(myinterval);
votingEnd.innerHTML = `Voting has ended`;
}
}, 1000);
});
if (second > 0) {
//second is still 20 here because i lowered it in my function above. How can i solve this
for (let i = 0; i < imposters.length; i++) {
imposters[i].addEventListener("click", function () {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
});
}
}
You could put the if (second > 0) inside the click function that way it will check for the most recent value of second instead of just once on load like so
for(let i = 0; i < imposters.length; i++){
imposters[i].addEventListener("click", function () {
if (second > 0) {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
}
});
The problem has nothing to do with scope. It has to do with timing. That last part of your code only runs once, before the interval runs twenty times.
Here's the order of operations:
Initialize second to 20.
Bind the countdown function to window.onload. (This does not run yet)
Check if seconds is greater than 0, and it is because the intervals haven't run yet. This is the only time this code ever runs.
window.onload is triggered, and your countdown begins
one second later, seconds is now 19
19 seconds later seconds is not 0, and the interval is cleared.
So what you need to do is trigger your code in each iteration of the interval.
You want something closer to:
let second = 20;
window.addEventListener("load", function () {
const myinterval = setInterval(function () {
second--;
// other logic here...
if (second > 0) {
countdownTick(); // runs every second with the interval handler
}
if (second == 0) {
clearInterval(myinterval);
// cleanup
}
}, 1000);
});
function countdownTick() {
// Do the stuff you need to do each second here
}
Your setInterval runs every second. That does not mean that the rest of the code also runs every second. Which is why second is still 20 when the code gets to if (second > 0).
So you need to make sure that this part of your code runs every second as well. One solution would be to wrap that code inside a function which you call inside the interval, like this:
votingEnd = document.querySelector(".imposters__voting");
imposters = document.querySelectorAll(".imposter");
let second = 20;
window.addEventListener("load", function () {
let myinterval;
myinterval = setInterval(function () {
second--;
if (second < 11) {
votingEnd.style.color = "red";
}
votingEnd.innerHTML = `Voting ends in: ${second}s`;
if (second == 0) {
clearInterval(myinterval);
votingEnd.innerHTML = `Voting has ended`;
}
check();
}, 1000);
});
function check() {
if (second > 0) {
//second is still 20 here because i lowered it in my function above. How can i solve this
for (let i = 0; i < imposters.length; i++) {
imposters[i].addEventListener("click", function () {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
});
}
}
}

Issue with triggering a function after has been run initially

I have a function called pageReload which sets the a timer and variables back on that page to start, when the time is counting down, however when the timer reaches 0 it seems to disable the function even though when the function is called again the time should be set back to 18 as specified in the function.
When it's between 18 and 0 it trigger ok and sets the time back to 18, the other parts seems to work ok (number of tries and matches set back)
I've tried different variations without getting it to work so below if the function together with the other code in the app which might give a bit of context to what I'm doing
"use strict";
//select each card
const cards = document.querySelectorAll('.card');
let isFlipped = false;
let setBoard = false;
let first, second;
let counter = 1;
//add event listeners to each square
for(let i = 0; i < cards.length; i++) {
let element = cards[i];
element.addEventListener('click', flipSquare);
}
function checkForMatch() {
//check for 2 matching squares
let isMatch = first.classList.value === second.classList.value;
$('#counter').html(`The number of tries made is: ${counter++}`);
isMatch ? disable() : unflip();
//check to see if completed - if so, score will be displayed
completed();
}
function checkScore(){
//determing whether a score A, B or unsuccessful were acheived
if(counter <= 15) {
$('#score').html("You got an A");
}
else if(counter > 15 && counter <= 20){
$('#score').html("You got an B");
} else {
$('#score').html("You had too many attempts and were therefore unsuccessful");
}
}
function completed(){
//pop up if all have been disabled
if($('.card:not(.open)').length === 0){
//display modal
$("#myModal").modal('show');
clearInterval(timerId);
clearTimeout(myTimeout);
elemComplete.html(timeComplete + ' seconds comleted in');
}
//check score on completion and output the result
checkScore();
}
let timeLeft = 18;
let timeComplete;
let elem = $('#some_div');
let elemComplete = $('#new_div');
let timerId = setInterval(showClock, 1000);
function shuffleCards() {
//give square random positions
for(let i = 0; i < cards.length; i++) {
let ramdomPos = Math.ceil(Math.random() * 12);
cards[i].style.order = ramdomPos;
}
}
function pageReload(){
shuffleCards();
//loop through any open cards to and remove their open status and add back click function to unflipped card
for(let i = 0; i < cards.length; i++) {
$(".card").removeClass('open');
let element = cards[i];
element.addEventListener('click', flipSquare);
}
isFlipped = false;
setBoard = false;
timeLeft = 18;
counter = 0;
n = 0;
$('#counter').html(`The number of tries made is: ${counter}`);
$('#updated').html(`The number of matches made is: ${n}`);
counter++;
}
I'm not 100% sure as I don't think this is all of the code, but I have a feeling that you are stopping your timer in the completed() function using clearInterval() and never restarting it?
Presuming this is the cause, I would try resetting the timer in your page reload function.
function pageReload(){
shuffleCards();
//loop through any open cards to and remove their open status and add back click function to unflipped card
for(let i = 0; i < cards.length; i++) {
$(".card").removeClass('open');
let element = cards[i];
element.addEventListener('click', flipSquare);
}
isFlipped = false;
setBoard = false;
timeLeft = 18;
counter = 0;
n = 0;
timerId = setInterval(showClock, 1000);
$('#counter').html(`The number of tries made is: ${counter}`);
$('#updated').html(`The number of matches made is: ${n}`);
counter++;
}
This makes the timer code a little fragile, so you could refactor the timer logic out into its own functions and do something like this to make things a little clearer:
let timerId = undefined;
function startTimer() {
if (timerId != undefined) {
stopTimer();
}
timerId = setInterval(showClock, 1000);
}
function stopTimer() {
clearInterval(timerId);
timerId = undefined;
}
You would then remove all of you existing timer code and call startTimer() in pageReloaded() and stopTimer() in completed()

cancel setTimeout inside an IIFE inside a loop

I have a function that will click on dom elements in order to show and hide them.
The function is working correctly and cycling through my dom elements however I am not able to stop the setTimeout once I call the function due to the IIFE I am using.
I've added a variable to the global space to break out of the loop however the setTimeout is already initiated so it never stops. I have also tried to assigne a name to the first setTimeout with a truthy/false flag in an attempt to catch and stop the next iteration of setTimeout in the loop but that has been unsuccessful as the console log continues when I attempt to stop the setTimeout.
function cycleSlides() {
var slides = [1,2,3,4]
var closeSlide = $(".close");
for(var i = 0; i < slides.length; i++) {
(function() {
var x = i;
setTimeout(function() {
if(x == 0) {
console.log("slides[x].click()")// first slide
}
else {
setTimeout(function() { console.log("closeSlide.click()") }, 1000) // timeout for animation to finish when clicked
setTimeout(function() { console.log("slides[x].click()") }, 2000) // timeout for animation to finish when clicked
}
// CLOSE LAST SLIDE
if(x == slides.length - 1 ) {
setTimeout(function() {
console.log("closeSlide.click()")
}, x * 1000)
}
}, x * 3000)
}(i))
}
}
setTimeout() returns a timeout ID that can passed to clearTimeout() to cancel the timeout. You could store the timeout IDs returned by your calls to setTimout() in an array. Then, when you want to cancel them, call clearTimeout() with the values and clear the array. Here's what that code might look like:
var timeouts = [];
function cancelTimeouts() {
for (var i = 0; i < timeouts.length; i++) {
clearTimeout(timeouts[i]);
}
timeouts = []
}
function cycleSlides() {
var slides = [1,2,3,4]
var closeSlide = $(".close");
for(var i = 0; i < slides.length; i++) {
(function() {
var x = i;
timeouts.push(setTimeout(function() {
if(x == 0) {
console.log("slides[x].click()")// first slide
}
else {
timeouts.push(setTimeout(function() { console.log("closeSlide.click()") }, 1000)) // timeout for animation to finish when clicked
timeouts.push(setTimeout(function() { console.log("slides[x].click()") }, 2000)) // timeout for animation to finish when clicked
}
// CLOSE LAST SLIDE
if(x == slides.length - 1 ) {
timeouts.push(setTimeout(function() {
console.log("closeSlide.click()")
}, x * 1000))
}
}, x * 3000))
}(i))
}
}

Looping using setInterval

I am trying to make a counter that counts from 10 to 0 using JavaScript with a loop function and setInterval but this does not work for me:
function count() {
var i;
for (i = numbers.textContent; 0 <= i; i--) {
numbers.textContent = i;
}
}
setInterval(count, 1000);
<div id="countDown">10</div>
You are doing a for loop inside setInterval
var numbers = document.getElementById('countDown');
function count() {
var i;
if(numbers.textContent>0)
numbers.textContent=numbers.textContent-1;
else
clearInterval(handle);
/*for(i=numbers.textContent; 0<=i; i--){
console.log(i)
numbers.textContent= i;
}*/
}
var handle= setInterval(count, 1000);
<div id="countDown">10</div>
This should work:
parseInt(numbers.textContent)
And there are many more:
You need a global variable.
You don't need a for loop for this.
Change the condition.
<div id="countDown">10</div>
<script>
var numbers = document.getElementById('countDown');
var interval = setInterval(count, 1000);
function count() {
var count = parseInt(numbers.textContent);
count--;
if (count == 0) {
clearInterval(interval);
}
numbers.textContent = count;
}
</script>
If you want to see it by loops, you can do using setTimeout:
<div id="countDown">10</div>
<script>
var numbers = document.getElementById('countDown');
function count() {
var count = parseInt(numbers.textContent);
count--;
numbers.textContent = count;
}
for (var i = 1; i <= parseInt(numbers.textContent); i++)
setTimeout(count, 1000 * i);
</script>
the first time count is called, the for loop will run till numbers.textContent is 0(and it's so fast that your eyes just missed that change on the page), and the 2nd call, 3rd call is just iterate around 0, nothing is changed.
BTW, after your counting is finished(or maybe you should give it a stop condition, reference to the DOC), you should unregister the interval, cause it never end by default.

Issue skipping first to last and vice versa in playlist

I am making a small music player for embedding into a blog. It has a playlist functionality. I am having an issue where I cannot skip from the first song in the playlist to the last if you press previous on the first song. Instead of showing the last value it just shows 'undefined'. Does anyone know what might be causing this?
Here is the JS Fiddle with my code in it, here are the two functions causing the issue:
function prevSong(){
if (counter === 0){
counter = songs.length;
}
else{
counter = counter - 1;
};
changeSong();
};
function nextSong(){
if (counter === songs.length){
counter = 0;
}
else{
counter = counter + 1;
};
changeSong();
};
You're trying to go beyond the end of your array. Your array has 3 elements (0, 1, 2). songs.length == 3. songs[3] is invalid.
Use the following instead of what you have:
function prevSong()
{
if (counter === 0)
{
counter = (songs.length-1);
}
else
{
counter = counter - 1;
}
changeSong();
}
function nextSong()
{
if (counter === (songs.length-1))
{
counter = 0;
}
else
{
counter = counter + 1;
}
changeSong();
}

Categories