Clearing a javascript timer when new starting point is input [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
yes I'm new, and don't worry about making me sound dumb. :) This is a web app written in Google Script and it's taking info from Google Sheets.
I adopted a code from a comment here: Javascript timer to use multiple times in a page . Current code is below. I'm running several different timers thru this whole thing and they all go down by 1 second every second - great. My question is because the value on the google sheet that the first code grabs, is changing every few minutes. I WANT it to be counting down from the most recent data, and make the "old" counter STOP. From reading several related threads I think I need another clearInterval somewhere but I can't figure out where to put it.
Google Apps Script that grabs Google sheets values and puts them into a variable. the "getdata" values are dates in the future, and then "data" is how many seconds until that date:
function getfivecoins(){
var livesheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Live");
var getdata = livesheet.getRange("D42").getValue().getTime();
var now = new Date().getTime();
var data = (getdata-now)/1000; // how many seconds in the future this date is
return data;
}
function gettencoins(){
var livesheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Live");
var getdata = livesheet.getRange("D43").getValue().getTime();
var now = new Date().getTime();
var data = (getdata-now)/1000; // how many seconds in the future this date is
return data;
}
then in html page:
<span id="timer1"></span>
<br>
<span id="timer2"></span>
<br>
lower down inside of script tags:
// every 10 seconds, gets new values from google sheets. Countdown should continue as normal (with some milliseconds difference, whatever) if values are the same, but trying to get countdown to start again if the value has changed. If its easier, clearing all the countdowns and then starting them again should have the same result.
document.addEventListener('DOMContentLoaded', function(){
setInterval(function(){
google.script.run.withSuccessHandler(generatefivecoins).getfivecoins();
google.script.run.withSuccessHandler(generatetencoins).gettencoins();
}, 10000);
});
const clearStack = []; //Added
function generatefivecoins(result) {
clearStack.forEach(clearInterval); //Added
var timer = document.getElementById('timer1');
var t = new timerObject();
clearStack.push(t.startTimer(result, timer)); //Modified
}
function generatetencoins(result){
clearStack.forEach(clearInterval); //Added
var timer = document.getElementById("timer2");
var t = new timerObject();
clearStack.push(t.startTimer(result, timer)); //Modified
}
var timerObject = function(){
return this;
};
timerObject.prototype.startTimer = function(duration, display) {
var me = this,
timer = duration,
STYLETEST, hours, minutes, seconds, ENDSTYLETEST;
var intervalLoop = setInterval(() => { // modified this line
//fancy colours and formatting stuff goes here
display.innerHTML = STYLETEST + " " + hours + ":" + minutes + ":" + seconds + ENDSTYLETEST;
if (--timer < 0) {
clearInterval(intervalLoop);
}
}, 1000);
return intervalLoop; //Added
};

Flow:
Instead of multiple functions, We use a single function with variable arguments.
coinBucket holds the different coins that can be generated
After generateCoins is called from server side, We use Map to store timerObject instance of class . During the next call, We use the same timerObject instead of creating a new one.
code.gs
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('index');
}
const getCoins = coin => {
const map = { 5: 'D42', 10: 'D43', 20: 'D48', 60: 'D49', 90: 'D50' };//fivecoin range = D42 and so on
var livesheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Live');
var getdata = livesheet
.getRange(map[coin])
.getValue()
.getTime();
var now = new Date().getTime();
var data = getdata - now; // how many milliseconds in the future this date is
return data;
};
Client side:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title></title>
</head>
<body>
<h1>Coins!!</h1>
<span id="timer1"></span>
<span id="timer2"></span>
<span id="timer3"></span>
<span id="timer4"></span>
<span id="timer5"></span>
</body>
<script>
const coinBucket = [5, 10, 20, 60, 90];//number of coin buckets
const GSR = (func, ...args) =>// google.script.run to promise
new Promise((resolve, reject) =>
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
[func](...args)
);
const clearStack = new Map();//map to store timerObjects
function generateCoins(result, i) {
const timer = clearStack.get(i) || new timerObject(i);
clearStack.set(i, timer);
timer.stopTimer();
timer.startTimer(result);
}
class timerObject {
constructor(id) {
this.elementId = 'timer' + (id + 1);
this.timerIds = [];
}
startTimer(duration) {
const display = document.getElementById(this.elementId);
this.timerIds.push(
setInterval(() => {
const hours = parseInt(duration / 1000 / 60 / 60, 10),
minutes = parseInt((duration / 1000 / 60) % 60, 10),
seconds = parseInt((duration / 1000) % 60, 10);
display.innerHTML = [hours, minutes, seconds]
.map((e) => ('0' + e).slice(-2))
.join(':');
duration = duration - 1000;
if (duration < 0) this.stopTimer();
}, 1000)
);
}
stopTimer() {
this.timerIds.forEach(clearInterval);
this.timerIds = [];
}
}
document.addEventListener('DOMContentLoaded', function () {
setInterval(function () {
Promise.all(
coinBucket.map((bucket) => GSR('getCoins', bucket))
).then((values) => values.forEach(generateCoins));
}, 10000);
});
</script>
</html>

Related

Countdown timer issue with multiple timers running unexpectedly resulting in overlapping digits

this may be a small problem, but it seems that I can't find a solution anywhere. I will try to explain the issue the best way I can.
I have a simple timer function:
let [milliseconds,seconds,minutes] = [0,0,0];
let int = null;
const start = () => {
if(int!==null){
clearInterval(int);
}
int = setInterval(displayTimer,10);
}
const reset = () => {
clearInterval(int);
[milliseconds,seconds,minutes] = [0,0,0];
document.querySelector('.timerDisplay').innerHTML = '00 : 00';
}
function displayTimer(){
milliseconds+=10;
if(milliseconds == 1000){
milliseconds = 0;
seconds++;
if(seconds == 60){
seconds = 0;
minutes++;
}
}
let m = minutes < 10 ? "0" + minutes : minutes;
let s = seconds < 10 ? "0" + seconds : seconds;
document.querySelector('.timerDisplay').innerHTML = `${m} : ${s}`;
}
And when start and reset functionalities are separated in two different buttons, they work like a charm.
<button onClick={reset} id="pauseTimer">Reset</button>
<button onClick={start} id="startTimer">Start</button>
I am encountering an issue when I try to put both of them in a single function, like this:
const newGame = () => {
reset()
//some other code
start()
}
So the newGame function should (in theory) reset the timer and start the new one. Instead, timer resets to zero for a split second, but the "old one" keeps counting, while the new one starts from 00:00, so the numbers overlap. The more I fire newGame, the more overlaps I get.
My question is, how do I prevent this?
I've tried using a useState to store the timer, still didn't work.
Thank you!
I cleaned up your code snippets a bit so I could run it on my machine and it appears to run as expected. If you're still seeing a problem on your end it may be in a piece of code that you didn't share that is somehow interfering with the code you shared.
That said, I saw that you mentioned the useState React hook so I'm assuming you're using React. If that is the case and all of this code is being run inside a React component, then it may be that something you're doing is triggering the component to re-render, which would re-declare new copies of all your local variables and functions. If that's the case, then the reason your old interval is still running is that after the re-render, your reset and start functions are only able to access and interact with the new copies of your local variables that store the current interval and time. I would suggest persisting the state of each of those variables by using the useState React hook
<html>
<head>
<script>
let [milliseconds, seconds, minutes] = [0, 0, 0];
let int = null;
const start = () => {
if (int !== null) {
clearInterval(int);
}
int = setInterval(displayTimer, 10);
}
const reset = () => {
clearInterval(int);
[milliseconds, seconds, minutes] = [0, 0, 0];
document.querySelector('.timerDisplay').innerHTML = '00 : 00';
}
function displayTimer() {
milliseconds += 10;
if (milliseconds == 1000) {
milliseconds = 0;
seconds++;
if (seconds == 60) {
seconds = 0;
minutes++;
}
}
let m = minutes < 10 ? "0" + minutes : minutes;
let s = seconds < 10 ? "0" + seconds : seconds;
document.querySelector('.timerDisplay').innerHTML = `${m} : ${s}`;
}
const newGame = () => {
reset()
//some other code
start()
}
</script>
</head>
<body>
<button onClick="reset()" id="pauseTimer">Reset</button>
<button onClick="start()" id="startTimer">Start</button>
<button onClick="newGame()" id="newGame">New Game</button>
<p class="timerDisplay">00 : 00</p>
</body>
</html>

How do I setup a "rolling window"?

I'm trying to figure out the way I can set up a rolling window for a variable.
The variable will record a number increasing # amount of times from the minute before.
My basic interval timer
var kp123 = Number('1');
var myInt = setInterval(function () {
kp123 = 1;
}, 60000);
Whenever a specific command is sent, kp123 gets increased by 1:
kp123++;
This variable may increase 20 times every second, or only 2-3 times every second.
Right now how the system is set up, it records the variable # every minute, however, the data gets reset when the interval timer reaches one minute.
var kp123 = Number('1');
var kp123History = []; // The history of kp123 is stored in this variable each minute
var myInt = setInterval(function () {
kp123History.push(kp123);
kp123 = 1;
console.log(kp123History); // You can see the results in the console each minute like this
}, 60000);
or if you only want the previous value, and not the full history, try this
var kp123 = Number('1');
var prevKp123 = null; // The previous value of kp123 is stored in this variable each minute
var myInt = setInterval(function () {
prevKp123 = kp123;
kp123 = 1;
}, 60000);
It sounds like (per the comments) you want a rolling average (wiki). You don't really show enough of your code for me to give a specific answer to you, but in general, you can't deduce a rolling average from just averages, you'll need to know actual values and their timestamps. I.e. you can't summarize your data and throw away the timestmaps.
class RollingAverage {
constructor(windowMs = 1000) {
this.windowMs_ = windowMs;
this.values_ = [];
}
addValue(value = 1) {
let time = Date.now();
this.values_.push({value, time});
}
get average() {
let now = Date.now();
this.values_ = this.values_.filter(({time}) => now - time <= this.windowMs_ * 1000);
return this.values_
.map(({value}) => value)
.reduce((a, b) => a + b)
/ this.values_.length;
}
}
let test = async () => {
let sleep = ms => new Promise(r => setTimeout(r, ms));
let avg = new RollingAverage();
avg.addValue(1);
console.log(avg.average); // 1
await sleep(600);
console.log(avg.average); // 1
avg.addValue(3);
console.log(avg.average); // 2
await sleep(600);
console.log(avg.average); // 3
avg.addValue(5);
console.log(avg.average); // 4
}
test();

Increment variable Javascript since a date

Hi I want to use 3 different counters that show me 3 different increasing numbers starting from a certain date. I've tryed this:
<script>
var START_DATE_1 = new Date("July 18, 2016 10:30:00"); // put in the starting date here
var INTERVAL_1 = 5; // in seconds
var INCREMENT_1 = 1; // increase per tick
var START_VALUE_1 = 0; // initial value when it's the start date
var count_1 = 0;
window.onload = function()
{
var msInterval_1 = INTERVAL_1 * 1000;
var now_1 = new Date();
count_1 = parseInt((now_1 - START_DATE_1)/msInterval_1) * INCREMENT_1 + START_VALUE_1;
document.getElementById('counter_1').innerHTML = count_1;
setInterval("count_1 += INCREMENT_1; document.getElementById('counter_1').innerHTML = count_1;", msInterval_1);
}
</script>
<script>
var START_DATE_2 = new Date("July 18, 2016 10:30:00"); // put in the starting date here
var INTERVAL_2 = 5; // in seconds
var INCREMENT_2 = 1; // increase per tick
var START_VALUE_2 = 0; // initial value when it's the start date
var count_2 = 0;
window.onload = function()
{
var msInterval_2 = INTERVAL_2 * 1000;
var now_2 = new Date();
count_2 = parseInt((now_2 - START_DATE_2)/msInterval_2) * INCREMENT_2 + START_VALUE_2;
document.getElementById('counter_2').innerHTML = count_2;
setInterval("count_2 += INCREMENT_2; document.getElementById('counter_2').innerHTML = count_2;", msInterval_2);
}
</script>
<script>
var START_DATE_3 = new Date("July 18, 2016 10:30:00"); // put in the starting date here
var INTERVAL_3 = 5; // in seconds
var INCREMENT_3 = 1; // increase per tick
var START_VALUE_3 = 0; // initial value when it's the start date
var count_3 = 0;
window.onload = function()
{
var msInterval_3 = INTERVAL_3 * 1000;
var now_3 = new Date();
count_2 = parseInt((now_3 - START_DATE_3)/msInterval_3) * INCREMENT_3 + START_VALUE_3;
document.getElementById('counter_3').innerHTML = count_2;
setInterval("count_3 += INCREMENT_3; document.getElementById('counter_3').innerHTML = count_3;", msInterval_3);
}
</script>
<div id="counter_1"></div>
<div id="counter_2"></div>
<div id="counter_3"></div>
This doesn't work as expected and only the 3td div is populated but now working as expected too (as soon as I load the page it show me a number then after few seconds restart from 1).
What's wrong? How should I do it without using JQuery also?
Thanks in advance.
I don't do a code analysis - this would be something for SE-CodeReview (highly recommended to post there too once you got it working), but your error is, that you are assigning something to a function more than once - hence, only the last assignment is actually called onload.
So -- you are assigning window.onload = function() in your first <script> tag, then you are overriding it in your second; and you're overriding the second assignment again in your third <script> tag.
You can't do that if you want to assign all 3 functions to a single (onload) event.
If you want to add multiple listeners for a single event, use
document.addEventListener("DOMContentLoaded", function() {});
Now inside that function, you can add all your code that you want to have executed onload of your page; in addition to the code you wanted to have executed onload in another function!
Here's a simple demonstration using the same techniques as described above, but with click listeners. Notice the first function will never be called.
document.getElementById("a").onclick = function() {
alert("I will never alert, because of the next function");
}
document.getElementById("a").onclick = function() {
alert("I will alert, because I'm not overriden");
}
document.getElementById("a").addEventListener('click', function() {
alert("I will alert too");
});
document.getElementById("a").addEventListener('click', function() {
alert("And a third alert. Disturbing");
});
<div id="a">Click me!</div>

Displaying one JS countdown after another

I'm trying to display a second countdown after the first one finishes. I'm using meteor. This is the timer:
sec = 5
#timer = setInterval((->
$('#timer').text sec--
if sec == -1
$('#timer').fadeOut 'fast'
sec=
timer
return
), 1000)
This is how I call it
When the template is rendered I call a setTimeout and a countdown displays
Template.selector.rendered = ->
window.setTimeout(startGame, 5000)
timer
When game starts I need a second countdown. I managed it like this:
sec = 5
sw = 0
#timer = setInterval((->
$('#timer').text sec--
if sec == -1
if sw == 0
sw = 1
sec = 20
else if sw == 1
clearInterval timer
return
), 1000)
But there has to be a better way.
If you plan to use many timers, you could make an object to achieve that. Here is an example taken from here You could adapt it to your case using custom events:
ReactiveTimer = (function () {
// Constructor
function ReactiveTimer(interval) {
this._dependency = new Tracker.Dependency;
this._intervalId = null;
if(_.isFinite(interval))
this.start(interval);
};
ReactiveTimer.prototype.start = function(interval){
var _this = this;
this._intervalId = Meteor.setInterval(function(){
// rerun every "interval"
_this._dependency.changed();
}, 1000 * interval);
};
ReactiveTimer.prototype.stop = function(){
Meteor.clearInterval(this._intervalId);
this._intervalId = null;
};
ReactiveTimer.prototype.tick = function(){
this._dependency.depend();
};
return ReactiveTimer;
})();

Simple jQuery Count Up timer help please

Trying to make a simple count up timer in jQuery... this sort of works but is adding the numbers to the end of '0000' and I want it to go '0001' '0002' '0003' etc...
This is all happening in the jQuery onReady scope.
var i = '0000'
var timer = function doSomething ( )
{
i = i+= 1
$('.counter').text(i);
console.log(i);
}
setInterval (timer, 1000 );
Your "i" variable needs to be an integer. You can format it how you like when you want to print it somewhere.
$(document).ready(function() {
var i = 0;
var target = $('.counter');
var timer = function doSomething ( )
{
i++;
var output = pad(i,4);
target.text(output);
console.log(output);
}
setInterval (timer, 1000 );
});
function pad(number, length) {
var str = '' + number;
while (str.length < length) {
str = '0' + str;
}
return str;
}​
Your current code is appending to a string, not addition to a number. It essentially looks like
i = '0000' + 1, i = '00001' + 1, i = '000011' + 1 ...
and so on. You'll need to keep it integer based to continue adding to the number. Here's an example with the formatting it looks like you wanted.
var pad = function(n) { return (''+n).length<4?pad('0'+n):n; };
jQuery.fn.timer = function() {
var t = this, i = 0;
setInterval(function() {
t.text(pad(i++));
}, 1000);
};
$('#timer').timer();
http://jsfiddle.net/jDaTK/
I would do something more like this:
// Make sure Date.now exists in your environment
Date.now = Date.now || function () { return Number(new Date()); };
var time = 0,
start = Date.now(),
intervalId;
intervalId = setInterval(function () {
var seconds, display;
// get the exact time since the timer was started
time = Date.now() - start;
// get the number or seconds, rounded down
seconds = Math.floor(time / 1000);
display = '' + seconds;
// pad the beginning of your display string with zeros
while (display.length < 4) {
display = "0" + display;
}
console.log(display);
}, 100);
setInterval is not exact. This code ensures that while the display could be up to nearly a second off (in theory), the actual time you are tracking is always the exact amount of time that has elapsed since you started the timer. But this code would update the display about once every tenth of a second, so it's not likely to ever be off more than a few milliseconds.
From here you can figure out smarter ways to update the display to ensure you have the level of accuracy you need. If this needs to be pretty accurate, then you could make sure you are displaying to the tenth of the second.
I really recommend the jQuery CountUp plugin. I tried a number of Javascript counters and this was one of the easiest to implement and came with lots of goodies:
<script type="text/javascript">
$(document).ready(function(){
$('#counter').countUp({
'lang':'en', 'format':'full', 'sinceDate': '22/07/2008-00::00';
});
});
</script>
<div id="counter"></div>

Categories