I've built a series of timers that are designed to be started, paused and resumed on cue. Numbers update dynamically on my page when the timer ticks up. The issue I'm having is figuring out how to get the timer to start from where it left off before I paused it. I can get it to restart from scratch, but I unsure how to take the paused Date.now() value, and work it into the formula to display the correct value. It will be something stupid that I just cant figure out.
function ticking2(num) {
//IF TIMER IS PAUSED CANCEL THE ANIMATION FRAME
if (timerObjArray[num].paused == true) {
timerObjArray[num].restartTime = Date.now();
cancelAnimationFrame(id);
} else if (timerObjArray[num].paused == false) {
timerObjArray[num].initialTime = Date.now()
if (timerObjArray[num].restartTime != 0) {
//THIS IS THE LINE WHERE I AM HAVING TROUBLE
timerObjArray[num].milli = ((timerObjArray[num].initialTime - timerObjArray[num].initialDate) - (timerObjArray[num].initialTime - timerObjArray[num].restartTime)) / 1000;
} else {
timerObjArray[num].milli = ((timerObjArray[num].initialTime - timerObjArray[num].initialDate ) / 1000);
}
//THIS FUNCTION TAKES THE MS VALUE AND CONVERTS IT TO HH:MM:SS
convert(num, timerObjArray[num].milli * 1000);
id = requestAnimationFrame(function() {
ticking2(num);
});
}
}
Thanks for the help.
I don't have enough information so, I made a simple implementation. You can look at this to help determine what you're missing. You're welcome to use it.
Timer fiddle
Utility:
var timer = (function() {
let _timers = {};
let pub = {
start : function(id) {
if(!_timers[id]) _timers[id] = {on:true, intervals:[{start:new Date()}] };
else if(_timers[id].on) throw 'timer is already started: ' + id;
else {
_timers[id].on = true;
_timers[id].intervals.push({start:new Date()});
}
},
stop : function(id) {
if(!_timers[id]) throw 'timer does not exist, cannot be stopped: ' + id;
else if(!_timers[id].on) throw 'timer is already stopped: ' + id;
else {
_timers[id].on = false;
let interval = _timers[id].intervals[_timers[id].intervals.length -1];
interval.stop = new Date();
interval.total = interval.stop - interval.start;
}
},
read : function(id) {
if(!_timers[id]) throw 'timer does not exist, cannot be read: ' + id;
let total = 0;
for(let i=0; i<_timers[id].intervals.length; i++) {
if(_timers[id].intervals[i].total) total += _timers[id].intervals[i].total;
else total += (new Date()) - _timers[id].intervals[i].start;
}
return { intervals:_timers[id].intervals, total: total };
},
delete: function(id) {
if(!_timers[id]) throw 'timer does not exist, cannot be deleted: ' + id;
delete _timers[id];
}
};
return pub;
})();
Example usage:
$('.start').on('click', function(){
timer.start(123);
});
$('.stop').on('click', function(){
timer.stop(123);
});
$('.clear').on('click', function(){
timer.delete(123);
$('input').val('');
});
setInterval(function(){
let result = null;
try//obviously not a great pattern
{
result = timer.read(123);
} catch(ex){}
if(result) {
$('input').val(result.total);
}
}, 35);
Related
So basically when I switch tabs, the countdown timer on a specific page just stops counting down and resumes when you return to the tab. Is there anyway to mitigate that so that it counts in the background or it accounts for the time you spend on another tab?
This is basically what I have for js:
document.getElementById('timer').innerHTML =
05 + ":" + 01;
startTimer();
function startTimer() {
var presentTime = document.getElementById('timer').innerHTML;
var timeArray = presentTime.split(/[:]+/);
var m = timeArray[0];
var s = checkSecond((timeArray[1] - 1));
if(s==59){m=m-1}
if(m<0){
return
} else if (m == 0 && s == 0) {
location.reload();
}
document.getElementById('timer').innerHTML =
m + ":" + s;
setTimeout(startTimer, 1000);
}
function checkSecond(sec) {
if (sec < 10 && sec >= 0) {sec = "0" + sec};
if (sec < 0) {sec = "59"};
return sec;
}
Any ideas whether the time could be done server side or something so that it can't be modified client side? If not, then whatever, but mainly just want to figure out how to make the countdown still work (or account for the time spent) when on another tab.
We can store the variable m and s values either globally or use the local storage to set the values after setting the inner HTML and get the stored values back whenever tabs were switched as:
Set values:
window.localStorage.setItem('minutes', m.toString()); //same for the seconds
Get values:
window.localStorage.getItem('minutes'); //same for the seconds
Hope this answers your questions.
Just a simple solution:
Add this piece of code.
<html>
<head>
<script>
(function() {
var $momentum;
function createWorker() {
var containerFunction = function() {
var idMap = {};
self.onmessage = function(e) {
if (e.data.type === 'setInterval') {
idMap[e.data.id] = setInterval(function() {
self.postMessage({
type: 'fire',
id: e.data.id
});
}, e.data.delay);
} else if (e.data.type === 'clearInterval') {
clearInterval(idMap[e.data.id]);
delete idMap[e.data.id];
} else if (e.data.type === 'setTimeout') {
idMap[e.data.id] = setTimeout(function() {
self.postMessage({
type: 'fire',
id: e.data.id
});
// remove reference to this timeout after is finished
delete idMap[e.data.id];
}, e.data.delay);
} else if (e.data.type === 'clearCallback') {
clearTimeout(idMap[e.data.id]);
delete idMap[e.data.id];
}
};
};
return new Worker(URL.createObjectURL(new Blob([
'(',
containerFunction.toString(),
')();'
], {
type: 'application/javascript'
})));
}
$momentum = {
worker: createWorker(),
idToCallback: {},
currentId: 0
};
function generateId() {
return $momentum.currentId++;
}
function patchedSetInterval(callback, delay) {
var intervalId = generateId();
$momentum.idToCallback[intervalId] = callback;
$momentum.worker.postMessage({
type: 'setInterval',
delay: delay,
id: intervalId
});
return intervalId;
}
function patchedClearInterval(intervalId) {
$momentum.worker.postMessage({
type: 'clearInterval',
id: intervalId
});
delete $momentum.idToCallback[intervalId];
}
function patchedSetTimeout(callback, delay) {
var intervalId = generateId();
$momentum.idToCallback[intervalId] = function() {
callback();
delete $momentum.idToCallback[intervalId];
};
$momentum.worker.postMessage({
type: 'setTimeout',
delay: delay,
id: intervalId
});
return intervalId;
}
function patchedClearTimeout(intervalId) {
$momentum.worker.postMessage({
type: 'clearInterval',
id: intervalId
});
delete $momentum.idToCallback[intervalId];
}
$momentum.worker.onmessage = function(e) {
if (e.data.type === 'fire') {
$momentum.idToCallback[e.data.id]();
}
};
window.$momentum = $momentum;
window.setInterval = patchedSetInterval;
window.clearInterval = patchedClearInterval;
window.setTimeout = patchedSetTimeout;
window.clearTimeout = patchedClearTimeout;
})();
</script>
</head>
</html>
I have a submit function on a textbox with JavaScript. When the script fires, it checks a Kendo grid for a certain article and adds +1 to its quantity as well as opening the corresponding cell in editing mode. What I want to achieve is that on every submit the timer that starts grid.editCell() will reset its timer.
Currently, the event fires properly. However, the timer doesn't get reset, although the clearTimeout() does work if I just simply start the timer and then clear it right afterwards.
JavaScript:
$('#txtBarcode').submit(function (e) {
var grid = $("#PickListDetailGrid").data("kendoGrid");
var dataSource = $("#PickListDetailGrid").data("kendoGrid").dataSource;
var allData = grid.dataSource.data();
var code = this.value;
var notification = $("#notification").data("kendoNotification");
var timer = null;
clearTimeout(timer);
$.each(allData, function (index, item) {
if (item.ArticleID == code) {
clearTimeout(timer);
timer = null;
if (item.Quantity > item.PickedQuantity && item.PickedQuantity.toString().length < 4) {
var edit = function () {
if (item.PickedQuantity != item.Quantity && timer != null) {
grid.select("tr:eq(" + (index) + ")");
grid.editCell("tr:eq(" + (index + 1) + ") td:eq(" + (5) + ")");
} else {
//do nothing
}
}
item.PickedQuantity++;
item.dirty = true;
dataSource.sync();
if (item.PickedQuantity != item.Quantity) {
console.log("tik tok");
if (timer) {
clearTimeout(timer); //cancel the previous timer.
timer = null;
}
timer = setTimeout(edit, 3000);
} else {
clearTimeout(timer);
timer = null;
}
document.getElementById("txtBarcode").value = "";
} else {
if (item.PickedQuantity.toString().length > 4) {
notification.hide();
notification.show({
message: "Added item"
}, "upload-success");
} else {
notification.hide();
notification.show({
title: "Quantity Error",
message: "You already picked this item to the maximum"
}, "error");
document.getElementById("txtBarcode").value = "";
grid.select("tr:eq(" + (index) + ")");
grid.editCell("tr:eq(" + (index + 1) + ") td:eq(" + (5) + ")");
$('.focus :input').focus();
}
}
}
})
})
You can try delay function like this. The delay function should be outside of the each function.
var delay = (function() {
var timer = 0;
return function(callback, ms) {
clearTimeout(timer);
timer = setTimeout(callback, ms);
};
})();
delay(function() {
//do something
}, 3000);
The problem was that var timer = null; had to be outside of the submit function to be properly cleared and set to null before assigning a new setTimeout() to it.
I am making my own version of simon Game, but the callback function inside click is not existing after user press wrong input. As a result function handler.patternRepeatPlayer() is getting called recursively for each element of array pattern.I want a solution to break from the else after callinghandler.patternRepeatPlayer just once. The program is working fine in strict mode, but only in non-strict mode inside else , I am not able to break from else.
-- can access the project on Git.
https://github.com/santosh1357/simonGame.git
The flow is like from html -> Function simonnGame.PatternGen from PatternGen -> handler.PatternRepeatPlayer -> PatternRepPlayer -> PatternMatcher -> userInput(here if wrong user input in non-strict mode) -> patternRepeatPlayer
This case is failing as in this case else is not existing after calling the function only once.
//Problematic Function.
userInput: function(){
var userPattern = new Array();var id;
$('img').click(function(){
id = parseInt(this.id,10); userPattern.push(id);handler.effect(id);
if(userPattern.indexOf(id) !== simonGame.PATTERN.indexOf(id)){
if($('.chkStrict:checked').val() === "on"){
var audio = new Audio('sounds/wrong.mp3');
audio.play();
setTimeout(function(){window.location.reload(true)},1000);
} else {
var audio = new Audio('sounds/wrong.mp3');
audio.play();
userPattern.length = 0;
handler.repeatFlag = true;
handler.patternRepeatPlayer(); ****//this is getting called recursivelly rather than quiting after calling once****
return ;
}
}
//End Problematic Functiom
I think there is some misunderstanding on how click callback functions work.
//Fullcode
var simonGame = {
COUNT: 0,
PATTERN: [],
SOUND:[{file:'sounds/sa.mp3'},{file:'sounds/re.mp3'},{file:'sounds/ga.mp3'},{file:'sounds/ma.mp3'},{file:'sounds/pa.mp3'},{file:'sounds/dha.mp3'},{file:'sounds/nee.mp3'}],
patternGen: function(){
var randomId;
randomId = Math.floor(Math.random() * 7);
simonGame.PATTERN.push(randomId);
if(simonGame.COUNT > 20){
alert("You have won the game!!");
window.location.reload(true);
}
simonGame.COUNT += 1;
//debugger;
//console.log("increase count true calling count display " + simonGame.COUNT);
handler.countDisplay();
//console.log("count gen true calling patternPlayer with PATTERN " + simonGame.PATTERN );
handler.patternRepeatPlayer();
}, //close patternGen
patternMatcher: function(genPattern){
//console.log("inside patternMatch");
var genPattern = simonGame.patternGen;
//setTimeout(function(){
//console.log("PATEERN: " + simonGame.PATTERN + "COUNT " + simonGame.COUNT );
//calling user input
console.log("calling user Input");
handler.userInput();
setTimeout(function(){
if(handler.repeatFlag === false){ //execute count gen only if repeat flag is false inside user INPUT
genPattern();
}
},simonGame.COUNT*2000);
//console.log("pattern check true, calling pattern gen");
//},simonGame.COUNT*5000); //c`enter code here`lose setTimeout
}, //close patternMatcher
} //close simonGame
var handler = {
countRepPlayer: 0,
repeatFlag: false,
patternRepeatPlayer: function(){
var repeater = setInterval(function(){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
//setTimeout(function(){
simonGame.patternMatcher();
//},1000);
handler.countRepPlayer = 0;
}
},1000);//close sestInterval
}, //close patternRepeatPlayer
effect: function(id){
var img = document.getElementById(id);
if(img !== null && id !== undefined){
$( img ).fadeIn(100).fadeOut(100).fadeIn(100);//fadeOut(200).fadeIn(200);
//debugger;
var audio = new Audio(simonGame.SOUND[id].file);
audio.play();
//console.log("id inside effect " + id)
}
},//close effect
countDisplay: function(){
document.getElementById("count").innerHTML = "Count: " + simonGame.COUNT;
}, //close countIncrease
userInput: function(){
var userPattern = new Array();var id;
$('img').click(function(){
id = parseInt(this.id,10);
userPattern.push(id);
handler.effect(id);
console.log(" user " + userPattern);
console.log(" pattern " + simonGame.PATTERN);
if(userPattern.indexOf(id) !== simonGame.PATTERN.indexOf(id)){
console.log(" WRONG USER INPUT ");
if($('.chkStrict:checked').val() === "on"){
var audio = new Audio('sounds/wrong.mp3');
audio.play();
setTimeout(function(){window.location.reload(true)},1000);
} else {
console.log("inside else " );
var audio = new Audio('sounds/wrong.mp3');
audio.play();
userPattern.length = 0;
handler.repeatFlag = true;
handler.patternRepeatPlayer(); ****//this is getting called recursivelly rather than quiting after calling once**.**
return ;
}
}
//reset the userPattern Array
if(userPattern.length === simonGame.PATTERN.length){
userPattern.length = 0;
}
}); //close click.
}
} //close handler
Yes, It will be called recursively, because you set interval for it.
Here you can modify your code:
patternRepeatPlayer: function(){
var repeater = setInterval(function(){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
//setTimeout(function(){
simonGame.patternMatcher();
//},1000);
handler.countRepPlayer = 0;
}
},1000);//close sestInterval
}
To: (EDITED)
function myCallbackFunction(repeater){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
simonGame.patternMatcher();
handler.countRepPlayer = 0;
}
}
patternRepeatPlayer: function(){
var repeater = setInterval(function() {myCallbackFunction(repeater);}, 1000);
}
And where you need to call it once, just call myCallbackFunction(repeater)
I am ask to write a java script program that retrieve an api JSON record from and address and through websocket every single minute. The stream continues after 60 seconds. I am expected to return the respective stream retrieve and the stream from the previous retrieve . Below is my code
var obj=
{
seconds : 60,
priv : 0,
prevTick : '' ,
data : ''
}
function countTime()
{
obj.seconds --;
obj.priv ++;
var msg ;
if(obj.priv > 1)
{
obj.priv = 0;
obj.msg = null;
}
if(prop.seconds < 0)
{
msg = sock.open();
obj.msg = obj.msg + ", New Tick : " + msg.msg ;
setTimeout(countTime, 1000);
obj.seconds = 60;
}
}
var sock= new WebSocket('link');
sock.onopen = function(evt) {
ws.send(JSON.stringify({ticks:'string'}));
};
sock.onmessage = function(msg) {
var data = JSON.parse(msg.data);
return 'record update: %o'+ data ;
};
Please what is wrong with my code above ? It does not delay at all. The stream continues irrespective.
How about encapsulating the buffering behavior into a class?
function SocketBuffer(socket, delay, ontick) {
var messages = [], tickInterval;
socket.onmessage = function(msg) {
messages.push( JSON.parse(msg.data) );
};
function tick() {
if (typeof ontick !== "function") return;
ontick( messages.splice(0) );
}
this.pause = function () {
tickInterval = clearInterval(tickInterval);
};
this.run = function () {
if (tickInterval) return;
tickInterval = setInterval(tick, delay * 1000);
tick();
};
this.run();
}
Note that .splice(0) returns all elements from the array and empties the array in the same step.
Usage:
var link = new WebSocket('link');
link.onopen = function (evt) {
this.send( JSON.stringify({ticks:'string'}) );
};
var linkBuf = new SocketBuffer(link, 60, function (newMessages) {
console.log(newMessages);
});
// if needed, you can:
linkBuf.pause();
linkBuf.run();
Try this:
function countTime() {
var interval = 1000; // How long do you have to wait for next round
// setInterval will create infinite loop if it is not asked to terminate with clearInterval
var looper = setInterval(function () {
// Your code here
// Terminate the loop if required
clearInterval(looper);
}, interval);
}
If you use setTimeout() you don't need to count the seconds manually. Furthermore, if you need to perform the task periodically, you'd better use setInterval() as #RyanB said. setTimeout() is useful for tasks that need to be performed only once. You're also using prop.seconds but prop doesn't seem to be defined. Finally, you need to call countTime() somewhere or it will never be executed.
This might work better:
var obj=
{
seconds : 60,
priv : 0,
prevTick : '' ,
data : ''
}
function countTime()
{
obj.seconds --;
obj.priv ++; //I don't understand this, it will always be set to zero 3 lines below
var msg ;
if(obj.priv > 1)
{
obj.priv = 0;
obj.msg = null;
}
msg = sock.open();
obj.msg = obj.msg + ", New Tick : " + msg.msg;
obj.seconds = 60;
//Maybe you should do sock.close() here
}
var sock= new WebSocket('link');
sock.onopen = function(evt) {
ws.send(JSON.stringify({ticks:'string'}));
};
sock.onmessage = function(msg) {
var data = JSON.parse(msg.data);
return 'record update: %o'+ data ;
};
var interval = setInterval(countTime, 1000);
EDIT: finally, when you're done, just do
clearInterval(interval);
to stop the execution.
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.