I have THIS timer in my project.
When it runs out, it shows a Time Up screen, which works fine.
But when the player is Game Over, i show the Game Over screen, but the timer keeps running and when it hits 00:00 then it switches to the Time Up screen.
How can i make this timer stop counting down and set to 00:00 again?
I tried adding a function like this:
CountDownTimer.prototype.stop = function() {
diff = 0;
this.running = false;
};
I also tried to change the innerHTML but its obvious that its just changing the numbers without stopping the timer and after a second it will show the count down again... I don't know what to call.
//Crazy Timer function start
function CountDownTimer(duration, granularity) {
this.duration = duration;
this.granularity = granularity || 1000;
this.tickFtns = [];
this.running = false;
}
CountDownTimer.prototype.start = function() {
if (this.running) {
return;
}
this.running = true;
var start = Date.now(),
that = this,
diff, obj;
(function timer() {
diff = that.duration - (((Date.now() - start) / 1000) | 0);
if (diff > 0) {
setTimeout(timer, that.granularity);
} else {
diff = 0;
that.running = false;
}
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
}());
};
CountDownTimer.prototype.onTick = function(ftn) {
if (typeof ftn === 'function') {
this.tickFtns.push(ftn);
}
return this;
};
CountDownTimer.prototype.expired = function() {
return !this.running;
};
CountDownTimer.parse = function(seconds) {
return {
'minutes': (seconds / 60) | 0,
'seconds': (seconds % 60) | 0
};
};
window.onload = function () {
var display = document.querySelector('#countDown'),
timer = new CountDownTimer(timerValue),
timeObj = CountDownTimer.parse(timerValue);
format(timeObj.minutes, timeObj.seconds);
timer.onTick(format).onTick(checkTime);
document.querySelector('#startBtn').addEventListener('click', function () {
timer.start();
});
function format(minutes, seconds) {
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.textContent = minutes + ':' + seconds;
}
function checkTime(){
if(this.expired()) {
timeUp();
document.querySelector('#startBtn').addEventListener('click', function () {
timer.start();
});
}
}
};
Instead of recursively calling setTimeout, try setInterval instead. You could then store a reference to the timer:
this.timer = setInterval(functionToRunAtInterval, this.granularity);
and kill it when the game finishes::
clearInterval(this.timer)
(see MDN's docs for more info on setInterval)
It's been a while and I'm not sure if you've figured it out but check out the fiddle below:
https://jsfiddle.net/f8rh3u85/1/
// until running is set to false the timer will keep running
if (that.running) {
if (diff > 0) {
setTimeout(timer, that.granularity);
} else {
diff = 0;
that.running = false;
}
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
}
I've added a button that causes running to be set to false which stops the timer.
Button:
<button id="stop">Game Over</button>
Code:
$( "#stop" ).click(function() {
timer.running = false;
});
So that should hopefully get you to where you need to be.
Similar to Tom Jenkins' answer, you need to cancel the next tick by avoiding the diff > 0 branch of your if statement. You could keep your code as it stands and use your suggested stop method, however, you'd need to change your logic around handling ticks to check that both running === true and a new param gameOver === false.
Ultimately, I think your problem is that no matter whether the timer is running or not, you'll always execute this code on a tick:
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
If you have a game over state, you probably don't want to call the provided callbacks, so add some conditional check in there.
Related
Is there a way to create a "timer"(or stopwatch) class, that the timers created using this class can be paused and resumed on triggering an event, e.g. clicking a button?
I tried to create the class and create timer objects using it, but the timer cannot be paused once it starts.
My attempt on creating this class:
class countdown {
constructor(min, sec) {
this.mins = min;
this.secs = sec;
this.handler = 0;
}
static setTimer(x,minfield,secfield) {
this.handler = setTimeout(() => {
if (x.mins == 0 && x.secs == 0) {
clearTimeout();
} else {
if (x.secs == 0) {
x.mins -= 1;
x.secs = 59;
} else {
x.secs -= 1;
}
}
this.updateTimer(x,minfield,secfield);
this.setTimer(x,minfield,secfield)
}, 1000)
}
static updateTimer(x,minfield, secfield){
document.getElementById(minfield).innerHTML = x.mins;
document.getElementById(secfield).innerHTML = x.secs;
}
static stopTimer(x,minfield,secfield) {
// document.getElementById(minfield).innerHTML = x.mins;
// document.getElementById(secfield).innerHTML = x.secs;
clearTimeout(x.handler);
}
}
Usage:
let countdown1 = new countdown(15,0);
let countdown_act = false;
document.getElementById('button').addEventListener( 'click', () => {
if (!countdown_act) {
countdown.setTimer(countdown1, 'ctdwn-mins', 'ctdwn-secs');
countdown_act = true;
} else {
countdown.stopTimer(countdown1, 'ctdwn-mins', 'ctdwn-secs');
countdown_act = false;
}
console.log(countdown_act);
}
)
The countdown_act flag is used for indicating the state of the timer.
There's no need for any of your methods to be static. Also, it's much simpler and easier to use if you just record seconds and then calculate minutes when you need the output. As a standard, capitalize the name of classes. Finally, you're probably looking for setInterval, not timeout.
Think of a real-world timer. You can set it, start it, pause it, and stop it. Stopping a timer is really just restarting it and pausing it, and it's set upon creation, so let's make a Timer with start, pause, set and stop or reset. This still behaves the same way as yours in the background by using a 1s timeout, but it's a lot cleaner and easier for someone reading your code to understand:
class Timer {
constructor(seconds, callback) {
this.originalTime = seconds;
this.remainingTime = seconds;
this.timeout = null;
this.callback = callback; // This is what the timer does when it ends
}
start() {
this.timeout = setInterval(() => {
this.remainingTime -= 1; // Remove a second from the timer
if(this.remainingTime === 0) {
this.callback(); // Run the callback function
this.stop(); // Stop the timer to prevent it from counting into the negatives
}
}, 1000);
}
pause() {
clearInterval(this.timeout);
}
stop() {
this.pause();
this.remainingTime = this.originalTime; // Reset the time
}
setTime(seconds) {
this.originalTime = seconds; // Set a new time
this.stop(); // Have to restart the timer to account for the new time
}
get remaining() {
return {
seconds: this.remainingTime % 60,
minutes: Math.floor(this.remainingTime / 60)
}
}
}
I've got a user which i get from database. For each user is possible to reset his password. But it's possible only one time for three minutes. If password was reseted less than three minutes, I show timer with JS, which show how much time left for next reset and reset button is disabled. When countdown is finished, button become availiable and displayed inscription "Password reset avaliable". Because the each user has his own timer, when I select another user, previous timer must be stopped with reset() function, and must be started other timer. It's just the only one timer must be in one time.
Timer:
var timerCount = 0;
function startTimer(minute, second) {
timerCount++;
start(minute, second);
}
function start(minute, second) {
disableButton('resetPasswordButton');
var m = minute;
var s = second;
if (timerCount == 0) {
document.getElementById('expiredTimeOutputText').innerHTML = "Password reset avaliable";
m = 0;
s = 0;
enableButton('resetPasswordButton');
return ;
}
if (s == 0) {
if (m == 0) {
reset();
document.getElementById('expiredTimeOutputText').innerHTML = "Password reset avaliable!";
enableButton('resetPasswordButton');
return ;
}
m--;
s = 59;
} else
s--;
document.getElementById('expiredTimeOutputText').innerHTML = m + ":" + s;
setTimeout(function () {
start(m, s);
}, 1000);
}
function reset() {
if (timerCount > 0) {
timerCount = 0;
}
}
function enableButton(id){
document.getElementById(id).disabled = false;
}
function disableButton(id){
document.getElementById(id).disabled = true;
}
method to start timer on button click
public void changePassword() {
RequestContext requestContext = RequestContext.getCurrentInstance();
requestContext.execute("startTimer(\"0\", \"40\")");
Date tmpDate = new Date();
Long diff = tmpDate.getTime();
mainDataBean.setResetTimer(applicantsTableSelectedRow.get("ID"), diff.toString());
}
method to start timer on user change
public void checkTimerForNewUser() {
RequestContext requestContext = RequestContext.getCurrentInstance();
Date tmpDate = new Date();
Long currentTime = tmpDate.getTime();
requestContext.execute("reset()");
if (applicantsTableSelectedRow != null) {
if (!mainDataBean.getResetTimer(applicantsTableSelectedRow.get("ID")).equals("noTimer")) {
Long applicantTimerTime = Long.parseLong(mainDataBean.getResetTimer(applicantsTableSelectedRow.get("ID")));
if (currentTime - applicantTimerTime > timerValue) {
mainDataBean.deleteResetTimer(applicantsTableSelectedRow.get("ID"));
}
else {
expiredTimeMinute = (timerValue - (currentTime - applicantTimerTime)) / 60000;
expiredTimeSecond = (timerValue - (currentTime - applicantTimerTime)) / 1000 - expiredTimeMinute * 60;
requestContext.execute("startTimer(\"" + expiredTimeMinute + "\", \"" + expiredTimeSecond + "\")");
}
}
}
}
If consistently reset user password, one after another, everything works fine. But if I consistently reset password for 5 users, and after that I return to first user with reseted password, if time isn't expired, I've got all 5 timers, which overlap one another, display 5 different times 5 time in second. But in theory they must stop because of reset function. How does it possible to make the only one timer exist? Do I stop functions wrong?
Example with clearTimeout:
function start(minute, second) {
disableButton('resetPasswordButton');
var m = minute;
var s = second;
if (timerCount == 0) {
document.getElementById('expiredTimeOutputText').innerHTML = "Reset Button Available!";
clearTimeout(timeout);
enableButton('resetPasswordButton');
return ;
}
if (s == 0) {
if (m == 0) {
reset();
clearTimeout(timeout);
document.getElementById('expiredTimeOutputText').innerHTML = "Reset Button Available!";
enableButton('resetPasswordButton');
return ;
}
m--;
s = 59;
} else
s--;
document.getElementById('expiredTimeOutputText').innerHTML = m + ":" + s;
timeout = setTimeout(function () {
start(m, s);
}, 1000);
}
You can clear timers with:
clearTimeout for setTimeout
clearInterval for setInterval
Example:
// will *never* run since we clear it below, immediately after we
// create it.
var timeout = setTimeout(() => {
console.log('foo')
}, 1000)
clearTimeout(timeout)
// will run only *once* since we clear it when it's called
var interval = setInterval(() => {
console.log('bar')
clearInterval(interval)
}, 1000)
I have two timers with two boolean variables and two functions. The first timer is triggered by a click that put true one of the booleans; once the timer reach some conditions this timer is off setting false the boolean and another timer is triggered setting true the second boolean and calling the second timer.
Same once the second timer reach some conditions, the timer is off setting the second boolean false and I try to trigger the first timer setting the first boolean true and calling the first timer, but it dosn't work.
I cannot call the function that is declared underneath the funcion where I do the call.
My code in JavaScript is:
var active=false;
var activeBreak=false;
...
function breakDown(){
if(activeBreak){
...
if(some conditions){
active=true;
activeBreak=false;
countDown();// **This call doesn't work.Black in jsbin**
}
}
}
function countDown(){
if(active){
...
if(someConditions){
active=false;
activeBreak=true;
breakDown();// *This call works. Blue in jsbin*
}
}
}
...
$("#circle").click(function(){
active=true;
countDown(); // *This call works*/
});
Try
$(document).ready(function(){
/* Global variables */
var state = 'STOPPED';
var timeSession = 1;
var timeBreak = 1;
var timeout = 0;
function resetTimer(time) {
timeout = (new Date()).getTime() + (time*60*1000);
}
function changeState() {
if (state === 'SESSION') {
state = 'BREAK';
resetTimer(timeBreak);
} else if (state === 'BREAK' || state === 'STOPPED') {
state = 'SESSION';
resetTimer(timeSession);
}
$('#text').text(state);
}
/* Function for the countdown ****************************/
function countDown (){
if (state !== 'STOPPED') {
var time = (new Date()).getTime();
if (timeout <= time) {
changeState();
return;
}
var s = Math.floor((timeout - time) / 1000);
var m = Math.floor(s / 60);
s = s - m*60;
$('#timer').text(""+((timeout - time) / 1000)+" "+timeout+" "+m+":"+("0"+s).substr(-2));
}
setTimeout(countDown, 1000);
}
/*****Click Break ********************/
$("#breakMinus").click(function(){
if (timeBreak > 1) {
--timeBreak;
}
$("#breakContent").html(timeBreak);
});
$("#breakPlus").click(function(){
++timeBreak;
$("#breakContent").html(timeBreak);
});
/******Click Session Length****************/
$("#seMinus").click(function(){
if (timeSession > 1) {
--timeSession;
}
$("#seContent").html(timeSession);
});
$("#sePlus").click(function(){
++timeSession;
$("#seContent").html(timeSession);
});
/***********Click circle****************/
$("#circle").click(changeState);
setTimeout(countDown, 1000);
$('#text').text(state);
});
I had written following code for implementing a timer in JS. But the issue is, for the subsequent recursive calls, the method throws reference error for timeChkSplitTime. How does it happen as its being passed in settimeout().
Also, later I used the easy timer js lib for this. If possible, pls provide an idea to configure the timer for minutes and seconds alone.
function timeChkold(timeChkSplitTime) {
var min = timeChkSplitTime[0], sec = timeChkSplitTime[1];
if (!(timeChkSplitTime[0]==0 && splitTime[1]==0)) {
var strSec, strMin = "0"+min.toString();
if (sec < 10) strSec = "0"+ sec.toString();
else strSec = sec.toString();
$(".timer-btn time").html(strMin+":"+strSec);
timeChkSplitTime[0]=0;
if (sec > 0) timeChkSplitTime[1]--;
else timeChkSplitTime[1] = 59;
setTimeout( "timeChk(timeChkSplitTime);", 1000);
}
else {
var startBtn = $(".start-btn");
startBtn.html("Start");
startBtn.css( {
"border": "1px solid #56B68B",
"background": "#56B68B",
});
var startTime = "01:00";
$(".timer-btn time").html(startTime);
}
}
setTimeout( "timeChk(timeChkSplitTime);", 1000);
should be
setTimeout( timeChk(timeChkSplitTime), 1000);
Variables aren't parsed through strings, on the line with the code:
setTimeout( "timeChk(timeChkSplitTime);", 1000);
It's literally reading the parameter as the value as the text timeChkSplitTime and not the value of the variable timeChkSplitTime. Other than using a string use a function for setTimeout:
setTimeout( timeChk(timeChkSplitTime), 1000);
your code is a little bit of a spaghetti code. you should seperate your code logic from the view. split them into functions. and most importantly, using setTimeout is not efficient in this case.
var CountdownTimer = function(startTime) {
var timeInSeconds = this.stringToSeconds(startTime);
this.original = timeInSeconds;
this.time = timeInSeconds;
this.running = false;
}
CountdownTimer.prototype.start = function(callback) {
this.running = true;
this.interval = setInterval(function() {
if(this.time < 1) {
this.running = false;
clearInterval(this.interval);
} else {
this.time -= 1;
callback();
}
}.bind(this), 1000);
}
CountdownTimer.prototype.pause = function() {
if(this.running) {
this.running = false;
clearInterval(this.interval);
}
}
CountdownTimer.prototype.restart = function() {
this.time = this.original;
}
CountdownTimer.prototype.stringToSeconds = function(timeSting) {
var timeArray = timeSting.split(':');
var minutes = parseInt(timeArray[0], 10);
var seconds = parseInt(timeArray[1], 10);
var totalSeconds = (minutes*60) + seconds;
return totalSeconds;
}
CountdownTimer.prototype.secondsToStrings = function(timeNumber) {
finalString = '';
var minutes = parseInt(timeNumber/60, 10);
var seconds = timeNumber - (minutes*60);
var minStr = String(minutes);
var secStr = String(seconds);
if(minutes < 10) minStr = "0" + minStr;
if(seconds < 10) secStr = "0" + secStr;
return minStr + ":" + secStr;
}
to run this code you can add the following
var countdownTest = new CountdownTimer("01:15");
countdownTest.start(onEachTick);
function onEachTick() {
var time = countdownTest.secondsToStrings(countdownTest.time);
console.log(time)
}
you can write your custom code in the onEachTick funciton.
you can check if the timer is running by typing countdownTest.running.
you can also restart and pause the timer. now you can customize your views however you want.
I am using a javascript timer on a page, which redirects the user after a finite time. The code is doing exactly what I want it to but it continues to throw errors in the console. The error message I get is
Uncaught TypeError: Cannot set property 'textContent' of null
//Countdown Timer
function CountDownTimer(duration, granularity) {
this.duration = duration;
this.granularity = granularity || 1000;
this.tickFtns = [];
this.running = false;
}
CountDownTimer.prototype.start = function() {
if (this.running) {
return;
}
this.running = true;
var start = Date.now(),
that = this,
diff, obj;
(function timer() {
diff = that.duration - (((Date.now() - start) / 1000) | 0);
if (diff > 0) {
setTimeout(timer, that.granularity);
} else {
diff = 0;
that.running = false;
}
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
}());
};
CountDownTimer.prototype.onTick = function(ftn) {
if (typeof ftn === 'function') {
this.tickFtns.push(ftn);
}
return this;
};
CountDownTimer.prototype.expired = function() {
return !this.running;
};
CountDownTimer.parse = function(seconds) {
return {
'minutes': (seconds / 60) | 0,
'seconds': (seconds % 60) | 0
};
};
window.onload = function() {
var display = document.querySelector(".cdtimer"),
s6timer = new CountDownTimer(20);
s6timer.onTick(format).onTick(redirect).start();
function redirect() {
if (s6timer.expired()) {
window.location.replace("../assessmentportal/sectiontimeout.php");
}
};
function format(minutes, seconds) {
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.textContent = minutes + ':' + seconds;
};
};
});
This happens because this call document.querySelector(".cdtimer") returns null. There are 2 possible reasons:
There are no element with class name cdtimer
You are running your script before DOM loaded
Is your assignment of display actually returning a DOM element? If it can't find anything (typo, perhaps?) it will return null.