I have a simple countdown plugin which counts down second by second to a time, and upon reaching that time it runs a callback function of my choosing.
What I have discovered today is that if I have two countdowns on the same page, and one countdown finishes, a javascript error occurs because a variable becomes undefined, which also breaks the second countdown.
Here's the code:
(function($) {
$.fn.countdown = function(options, callback) {
var $self = $(this);
var settings = {
'date' : null,
}
if(options) {
$.extend(settings, options);
}
function countdownProcessor() {
var eventDate = Date.parse(settings.date) / 1000;
var currentDate = Math.floor($.now() / 1000);
if (eventDate <= currentDate) {
callback.call(this);
clearInterval(interval);
}
var secondsBetween = eventDate - currentDate;
// processing logic here.
}
countdownProcessor();
interval = setInterval(countdownProcessor, 1000);
}
})(jQuery);
The issue is with the if statement which checks to make sure the date has not already occurred:
if (eventDate <= currentDate) {
callback.call(this);
clearInterval(interval);
}
When this condition becomes true, the callback completes successfully, but clearInterval does not because the variable interval is not defined - this is because the countdown function is run before interval is declared.
I've tried fixing it by switching the interval variable declaration and countdownProcessor(); around, but this doesn't help because it simply causes the first, ended countdown to count into the negatives.
I've a few other methods like changing the scope and order of declaration of some of the code, but it invariably leads to the countdown either A) counting into the negatives, or B) still erroring out.
How can I fix this?
Add var:
var interval = setInterval(countdownProcessor,1000);
This makes the interval local to each countdown that is being run, rather than global to the entire page.
Related
I was wondering if there is a nicer object oriented way of creating this timer? (without global vars!)
let secondsPassed = 0;
let timerId;
function startTimer() {
clearInterval(timerId);
timerId = setInterval(function() {
const seconds = twoDigits((Math.floor(secondsPassed )) % 60);
const minutes = twoDigits(Math.floor(secondsPassed / 60) % 60);
const hours = Math.floor(secondsPassed / 60 / 60);
$('#timer').text(`${hours}:${minutes}:${seconds}`);
secondsPassed++;
}, 1000);
$(window).blur(function() {
clearInterval(timerId) // stop timer when user leaves tab
});
$(window).focus(function() {
startTimer(); // continue timer when user comes back
});
}
Your current implementation is actually wrong. Every time you call startTimer, it installs startTimer as a new window focus event handler, leading to multiple started intervals when you focus the window the second time; growing exponentially. The onfocus handler should only run the timerId = setInterval(…) line - put that in a nested helper function to call only that.
This also makes it unnecessary to declare the variables globally.
function createTimer() {
let secondsPassed = 0;
let timerId;
function resume() {
if (timerId) return; // prevent multiple intervals running at the same time
timerId = setInterval(() => {
const seconds = twoDigits((Math.floor(secondsPassed )) % 60);
const minutes = twoDigits(Math.floor(secondsPassed / 60) % 60);
const hours = Math.floor(secondsPassed / 60 / 60);
$('#timer').text(`${hours}:${minutes}:${seconds}`);
secondsPassed++;
}, 1000);
}
function pause() {
clearInterval(timerId);
timerId = undefined;
}
$(window).blur(pause); // stop timer when user leaves tab
$(window).focus(resume); // continue timer when user comes back
resume(); // now start the timer
}
Now how to make that object-oriented? Just return an object from createTimer. Put resume and pause as methods on that object. Maybe add some more methods for starting, stopping, resetting, whatever you need. Maybe use a property on the object instead of the secondsPassed local variable. Or expose the local variable using a getter.
And to make it reusable, of course you can make createTimer accept arguments, from the selector of the output element, to the output element itself, to a callback function that will be called with the current time on every tick.
Edit: With this answer, you have to implement the Timer class yourself first. The code only shows how you could name the methods of the timer, how you create the instance and call its functions. The timer should (principle "separation of concerns") only handle the counting and provide the functionalities needed, like starting and stopping.
If you want to have an OOP solution for your timer, you shouldn't let the Timer class know the ID of the DOM container (like one of your comments to your question suggested).
You should read into the topic using this:
https://appdividend.com/2019/05/22/javascript-class-example-how-to-use-class-in-javascript-tutorial/
Let us assume, that you already implemented the class. Your code above should look like the following:
// Create own scope for the function, so that variable are not assigned to windows-object.
(function() {
let secondsPassed = 0;
let timer = new Timer();
// events, if necessary
timer.onTick((seconds) => { secondsPassed = seconds });
timer.onStop(() => { secondsPassed = 0; })
// Called by a button
function startTimer() {
timer.start();
}
// Example: Display alert with current timer seconds on click
function displaySecondsOfTimer() {
alert(timer.getSeconds());
}
$(window).blur(function() {
timer.stop(); // stop timer when user leaves tab
});
$(window).focus(function() {
timer.start(); // continue timer when user comes back
});
})();
So I think, you have a good example to code your first Timer class in native JavaScript! :)
My setTimeout() function works, but my clearTimeout() is not working. Even though I have an 'if' statement that's supposed to run the clearTimeout function once my variable 'secs' is less than 0, the timer keeps counting down into negative numbers. When I type my variable name, 'secs' into the console, I get undefined, even though it's defined as a parameter in the function called by my setTimeout. I don't know what I'm doing wrong. Can anyone help, please?
My full code is at https://codepen.io/Rburrage/pen/qBEjXmx;
Here's the JavaScript snippet:
function startTimer(secs, elem) {
t = $(elem);
t.innerHTML = "00:" + secs;
if(secs<0) {
clearTimeout(countDown);
}
secs--;
//recurring function
countDown = setTimeout('startTimer('+secs+',"'+elem+'")', 1000);
}
Add a condition to call recursive function like below.
if (secs < 0) {
secs = secsInput;
}
//recurring function
countDown = setTimeout('startTimer('+secs+',"'+elem+'")', 1000);
For a countdown timer, I would recommend using setInterval and clearInterval instead. setInterval will repeatedly run the callback function for you. It might look like this:
let countdown;
function startTimer(secs, elem) {
countdown = setInterval(function(){
t = $(elem);
t.innerHTML = "00:" + secs;
secs--
if (secs < 0) {
clearInterval(countdown);
}
}, 1000);
}
By the time you call clearTimeout(countDown), countDown refers to the previous timeout, that already timed out. It will not stop the one yet to start. You could just not re set the timeout, like
if(!/*finished*/) setTimeout(startTimer, 1000, secs, elem);
In your case, it's more convenient to use setInterval and clearInterval.
To keep the setTimeout and clearTimeout functions, you should add return in the if statement.
function startTimer(secs, elem) {
t = $(elem);
t.innerHTML = "00:" + secs;
if(secs<0) {
clearTimeout(countDown);
return;
}
secs--;
//recurring function
countDown = setTimeout('startTimer('+secs+',"'+elem+'")', 1000);
}
So there are 4 events in my opinion that will have to be addressed by the timer:
The quiz starts
The quiz ends
The timer runs out
The player answers a question
This can be solved by a function returning an object with some options.
The createTimer can be used to set the parameters for the timer.
Point 1. would be timer.start() --> will start a timer with the parameters
Point 3. can be addressed with the callback that will be called if the timer runs out --> createTimer(5,'display', ()=>{ // your code goes here })
Point 2. can be achieved with --> timer.stop()
Point 4. is needed when the timer needs to be reset without running out timer.reset()
Further on the interval is not in the global scope so you could have multiple timers with different settings and they wouldn't interfere with each other
// function for creating the timer
function createTimer(seconds, cssSelector, callbackOnTimeout) {
// interval the timer is running
let interval;
// the html node where innerText will be set
const display = document.getElementById(cssSelector)
// original seconds passt to createTimer needed for restart
const initSec = seconds
// starting or continuing the interval
function start() {
// setting interval to the active interval
interval = setInterval(() => {
display.innerText = `00:${seconds}`;
--seconds;
if (seconds < 0) {
// calling restart and callback to restart
callbackOnTimeout()
restart()
}
}, 1000);
}
// just stopping but not resetting so calling start will continue the timer
// player takes a break
function stop(){
clearInterval(interval)
}
// opted for a restart and not only a reset since it seemed more appropriate for your problem
function restart(){
clearInterval(interval)
seconds = initSec
start()
}
// returning the object with the functions
return {
start: start,
stop: stop,
restart: restart
}
}
// example for creating a timer
const timer1 = createTimer(5,'display',()=>{
console.log(`you where to slow ohhh...`)
})
// calling the timer
timer1.start()
I am very new in Node JS, my job is really simple: just clear the exiting interval and run a new on every user button click.
I've tried using global.clearInterval, but it didn't work
function time() {
if (today.getTime() === subuh.getTime()){
admin.messaging().sendToDevice(token, notifikasi)
}
console.log(today.toTimeString)
}
clearInterval(clock);
var clock = setInterval(time, 1000);
What I expect is var clock is cleared before setInterval
Please help me, help me solve this and make me sleep
I think you need two functions here: one will be called when the user clicks the button (I called it restart()) - it will clear the previous timer and start a new one. And the second function is what you actually want to be repeated every second and what you pass to the setInterval (I just log the current timer id).
Check out this example:
var timer;
function time() {
console.log('Timer ID:', timer);
}
function restart() {
clearInterval(timer);
timer = setInterval(time, 1000);
}
<button onclick="restart()">(Re)start</button>
Before your call to clearInterval(clock); the variable clock does not exist as such you should get a reference error of clock not being defined. You can however be able to clear value of clock after the call to set interval. this is because the variable clock only start existing after the call to set var clock = setInterval(time, 1000); Alternatively, you could define clock before you call clearInterval.
function time() {
if (today.getTime() === subuh.getTime()){
admin.messaging().sendToDevice(token, notifikasi)
}
console.log(today.toTimeString)
}
var clock;
clearInterval(clock);
clock = setInterval(time, 1000);
Either way, it is the same as doing this
var clock = setInterval(time, 1000); and clearing the interval at some point when some certain event occur.
I'm making a countdown timer. When I take out the setInterval() function, and decrement the time without pausing, the program works correctly.
But when I use the setInterval() function to call the code that decrements my time only every second, the program hangs.
function timeControl() {
var minutesLeft = $('#minutes').text();
var secondsLeft = $('#seconds').text();
while(Number(minutesLeft) > 0 || Number(secondsLeft) > 0) {
window.setInterval(decreaseTime, 1000);
minutesLeft = $('#minutes').text();
secondsLeft = $('#seconds').text();
}
}
function decreaseTime() {
var secondsLeft = $('#seconds').text();
var minutesLeft = $('#minutes').text();
if((Number(minutesLeft) > 0) && (Number(secondsLeft) == 0)) {
$('#minutes').text(Number(minutesLeft) - 1);
$('#seconds').text(4);
} else {
$('#seconds').text(Number(secondsLeft) - 1);
}
}
You add while and it looping very fast, so you setInterval() calling many times, so browser hanging. You don't need to while, because setInterval run every time that defined in setInterval second parameter. You can use following code:
var decreaseTimeInterval;
function timeControl() {
decreaseTimeInterval = window.setInterval(decreaseTime, 1000);
}
function decreaseTime() {
var secondsLeft = $('#seconds').text();
var minutesLeft = $('#minutes').text();
if((Number(minutesLeft) > 0) && (Number(secondsLeft) == 0)) {
$('#minutes').text(Number(minutesLeft) - 1);
$('#seconds').text(4);
} else if(Number(minutesLeft) == 0 && Number(secondsLeft) == 0) { //end of script
clearInterval(decreaseTimeInterval); //clear interval to prevent do again
} else {
$('#seconds').text(Number(secondsLeft) - 1);
}
}
You appear to be conflating the asynchronous setInterval function with a synchronous sleep kind of function. They are totally different. The setInterval function sets a timer for you. It says, "run function x every y milliseconds". But here, you are setting that function umpteen different times in that while loop. Essentially, you start setting off more and more timers, hundreds or thousands of them, each one running the decreaseTime function every 1 second, until the program hangs.
What you want to do is put the setInterval function at the top level of your program, and have it run the timeControl function every 1000 ms. Remove the while loop from the timeControl function -- you don't need it since the setInterval function itself is running a loop for you. However, you will need to save the return value of setInterval in a variable so that you can clear it when your stop condition is reached. In short, read more about setInterval: it's a totally different concept from sleep.
I want a counter which reset in specific interval of time. I wrote this code. When I refresh the page it is executing perfectly. But as time passes the timer goes really fast, skipping seconds. Any idea why this is happening.
function countdown_new() {
window.setInterval(function () {
var timeCounter = $("b[id=show-time]").html();
var updateTime = eval(timeCounter) - eval(1);
$("b[id=show-time]").html(updateTime);
if (updateTime == 0) {
//window.location = ("ajax_chart.php");
$("b[id=show-time]").html(5);
clearInterval(countdown_new());
// countdown_new();
//my_ajax();
}
}, 1000);
}
window.setInterval(function () {
countdown_new();
}, 5000)
HTML
Coundown in 5 seconds
The issue is because you are not clearing the previous timer before starting a new one, so you start a new one for each iteration. To clear the timer you should save a reference to it, and pass that to clearInterval, not a function reference.
Also, note that your pattern of using multiple intervals for different operations can lead to overlap (where two intervals are acting at the same time and cause odd behaviour). Instead, use setTimeout for the initial 5 second delay and then chain further calls to stop this overlap.
Try this:
var timer;
function countdown_new() {
timer = setInterval(function () {
var $showTime = $("#show-time")
var updateTime = parseInt($showTime.text(), 10) - 1;
$showTime.html(updateTime);
if (updateTime == 0) {
$showTime.html('5');
clearInterval(timer);
setTimeout(countdown_new, 5000);
}
}, 1000);
}
setTimeout(countdown_new, 5000);
Example fiddle
Note that you should use the # selector to select an element by its id attribute, and you should never use eval - especially not for type coercion. To convert a value to an integer use parseInt().
You are calling window.setInterval(), which schedules a function call to countdown_new() ever 5 seconds without stop.
Compounding the problem, you are calling countdown_new() again inside your clear interval.
You need to call setInterval just once to continuously execute a function every 5 seconds.
If you want to cancel an interval timer, you need do to this:
var intervalObj = setInterval(function() { ... }, 5000);
clearInterval(intervalObj);
Yes clearinterval does the trick.
function countdown_new(){
t = window.setInterval(function() {
var timeCounter = $("b[id=show-time]").html();
var updateTime = eval(timeCounter)- eval(1);
$("b[id=show-time]").html(updateTime);
if(updateTime == 0){
//window.location = ("ajax_chart.php");
$("b[id=show-time]").html(5);
clearInterval(t);
// countdown_new();
my_ajax();
}
}, 1000);
}