Stopwatch in TS returns NaN - javascript

I have a Stopwatch that has two problems.
In the code, there is a class with several methods, the currentWatch class is supposed to return a time (milliseconds, seconds, minutes).
The first is that there is a millisecond NaN and I don't know how to
fix it.
And the second I want when I click the start (after the stop) from
the continuation of that.
typescript code :
class StopWatch {
private duration: number;
private status: string;
private currentTime: any;
private domRef: HTMLElement | null;
private interval: any;
private milliSecond: number;
private second:number;
private minute: number;
constructor(wrapper: string) {
this.duration = 0;
this.milliSecond = 0;
this.second = 0;
this.minute = 0;
this.domRef = document.getElementById(wrapper);
this.status = 'stopped';
if (!this.domRef) throw new Error('Does not exist');
this.render();
}
private render() {
this.domRef!.append(
createBtn('start', () => this.start()),
createBtn('stop', () => this.stop()),
createBtn('reset', () => this.reset()),
)
}
private start() {
if(this.status === 'started') throw new Error('already started');
this.currentTime = Date.now();
this.status = 'started';
const p = document.createElement("p");
p.id = "p";
document.body.appendChild(p);
this.interval = setInterval(() => {
this.minute, this.second, this.milliSecond = Number(this.currentWatch());
document.getElementById("p")!.innerHTML = `${this.minute} : ${this.second} : ${this.milliSecond}`;
}, 100);
}
private currentWatch() {
this.duration = Date.now() - this.currentTime;
if(this.status === 'stopped') this.stop();
let minutes = Math.floor(this.duration / (1000 * 60));
let seconds = Math.floor((this.duration % (1000 * 60)) / 1000);
let milliSeconds = Math.floor(this.duration % 1000);
this.minute = minutes;
this.second = seconds;
this.milliSecond = milliSeconds;
return [this.minute, this.second, this.milliSecond];
}
stop() {
clearInterval(this.interval);
if(this.status === 'stopped') throw new Error('already stopped');
this.duration = Date.now() - this.currentTime + this.duration;
this.status = 'stopped';
return this.duration;
}
reset(){
if(this.status === 'started') this.stop();
this.duration = 0;
document.getElementById("p")!.innerHTML = this.duration.toString();
}
}
function createBtn(name: string, listener: () => void) {
const startBtn = document.createElement('button');
startBtn.innerText = name;
startBtn.addEventListener('click', listener);
return startBtn;
}
(function() {
const btns = document.getElementsByClassName('add-btn');
btns[0].addEventListener('click', () => {
new StopWatch(btns[0].getAttribute('data-idw') as string);
});
})();
I put the compiled code of TypeScript to show the result.
var StopWatch = /** #class */ (function () {
function StopWatch(wrapper) {
this.duration = 0;
this.milliSecond = 0;
this.second = 0;
this.minute = 0;
this.domRef = document.getElementById(wrapper);
this.status = 'stopped';
if (!this.domRef)
throw new Error('Does not exist');
this.render();
}
StopWatch.prototype.render = function () {
var _this = this;
this.domRef.append(createBtn('start', function () { return _this.start(); }), createBtn('stop', function () { return _this.stop(); }), createBtn('reset', function () { return _this.reset(); }));
};
StopWatch.prototype.start = function () {
var _this = this;
if (this.status === 'started')
throw new Error('already started');
this.currentTime = Date.now();
this.status = 'started';
var p = document.createElement("p");
p.id = "p";
document.body.appendChild(p);
this.interval = setInterval(function () {
_this.minute, _this.second, _this.milliSecond = Number(_this.currentWatch());
document.getElementById("p").innerHTML = "".concat(_this.minute, " : ").concat(_this.second, " : ").concat(_this.milliSecond);
}, 100);
};
StopWatch.prototype.currentWatch = function () {
this.duration = Date.now() - this.currentTime;
if (this.status === 'stopped')
this.stop();
var minutes = Math.floor(this.duration / (1000 * 60));
var seconds = Math.floor((this.duration % (1000 * 60)) / 1000);
var milliSeconds = Math.floor(this.duration % 1000);
this.minute = minutes;
this.second = seconds;
this.milliSecond = milliSeconds;
return [this.minute, this.second, this.milliSecond];
};
StopWatch.prototype.stop = function () {
clearInterval(this.interval);
if (this.status === 'stopped')
throw new Error('already stopped');
this.duration = Date.now() - this.currentTime + this.duration;
this.status = 'stopped';
return this.duration;
};
StopWatch.prototype.reset = function () {
if (this.status === 'started')
this.stop();
this.duration = 0;
document.getElementById("p").innerHTML = this.duration.toString();
};
return StopWatch;
}());
function createBtn(name, listener) {
var startBtn = document.createElement('button');
startBtn.innerText = name;
startBtn.addEventListener('click', listener);
return startBtn;
}
(function () {
var btns = document.getElementsByClassName('add-btn');
btns[0].addEventListener('click', function () {
new StopWatch(btns[0].getAttribute('data-idw'));
});
})();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>StopWatch</title>
</head>
<body>
<button class="add-btn" data-idw="qwe">Add new StopWatch</button>
<div id="qwe"></div>
<script src="main.js"></script>
</body>
</html>

It looks like your currentWatch function is returning an array.
In your start function, you create a new Number with the value returned from currentWatch.
Number accepts only a single input.
Perhaps, refactor your start method to something like this
this.interval = setInterval(() => {
this.currentWatch();
document.getElementById("p")!.innerHTML = `${this.minute} : ${this.second} : ${this.milliSecond}`;
}, 100);

You have to set the variables from the array in start like this:
[_this.minute, _this.second, _this.milliSecond] = _this.currentWatch();
Since currentWatch() returns an array.
Of course there are also other ways to do that, but this one might be closest to your original code.

Related

I can't call a class function inside another class function in Javascript

I want to call the function "displayTime" in "startTimer" but for some reason I get "Uncaught TypeError: this.displayTime is not a function" in the console.
let endTimer = "0";
let isRunning = false;
//! CLASS
class Timer {
constructor(endTimer, isRunning) {
this.endTimer = endTimer;
this.isRunning = isRunning;
}
startTimer() {
if (this.endTimer === "0") {
this.endTimer = new Date().getTime() + 1500000;
}
displayTime();
if (this.isRunning === true) {
this.pauseTimer();
this.isRunning
}
this.isRunning = true;
}
displayTime() {
let now = new Date().getTime();
let remainingTime = this.endTimer - now;
let minutes = Math.floor(remainingTime / 1000 / 60);
let seconds = Math.floor((remainingTime / 1000) % 60);
if (seconds < 10) {
seconds = "0" + seconds;
}
timer.innerHTML = `<h1>${minutes}:${seconds}</h1>`;
start.textContent = "STOP"
this.update = setInterval(this.displayTime, 100);
}
}
let newTimer = new Timer(endTimer, isRunning);
//! EVENT LISTENERS
start.addEventListener("click", newTimer.startTimer);
I think that I'm missing something obvious, but I don't understand what...
start.addEventListener("click", newTimer.startTimer.bind(newTimer));
Calling displayTime(); without the prepended keyword 'this' is the main issue (line 16 below) as mentioned by GenericUser and Heretic Monkey in the comments above.
You probably already know this but you'll want to define a pauseTimer() method/function as well.
let endTimer = "0";
let isRunning = false;
//! CLASS
class Timer {
constructor(endTimer, isRunning) {
this.endTimer = endTimer;
this.isRunning = isRunning;
}
}
Timer.prototype.startTimer = function() {
if (this.endTimer === "0") {
this.endTimer = new Date().getTime() + 1500000;
}
this.displayTime(); // this is the important line
if (this.isRunning === true) {
this.pauseTimer();
this.isRunning
}
this.isRunning = true;
}
Timer.prototype.displayTime = function() {
let now = new Date().getTime();
let remainingTime = this.endTimer - now;
let minutes = Math.floor(remainingTime / 1000 / 60);
let seconds = Math.floor((remainingTime / 1000) % 60);
if (seconds < 10) {
seconds = "0" + seconds;
}
//timer.innerHTML = `<h1>${minutes}:${seconds}</h1>`;
//start.textContent = "STOP"
this.update = setInterval(this.displayTime, 100);
}
Timer.prototype.pauseTimer = function() {
this.isRunning = false;
}
let newTimer = new Timer("0", true);
newTimer.startTimer();
//! EVENT LISTENERS
//start.addEventListener("click", newTimer.startTimer);
class Timer {
startTimer() {
// You forgot this keyword
this.displayTime();
}
displayTime() {
console.log('do something');
}
}
let newTimer = new Timer();
// addEventListener changes the context of this.
// Using an arrow function will keep 'this' keyword in tact inside startTimer method
start.addEventListener('click', () => newTimer.startTimer)

how can I re-call this function? not experienced with ES6

I don't know how to call counter() a second time after it runs on page load
I made a new "instance" of the counter, I think. And this does work but there must be a correct way. I need to call it in replay()
/**
* #fileoverview demo
*/
class ReVideo {
constructor() {
this.time = 5;
...
this.timer = document.getElementById("update");
this.counter = setInterval(() => this.countdown(), 1000);
this.fader = fader();
this.counter2 = "";
...
this.retry.addEventListener('click', () => this.replay());
...
}
countdown() {
this.time--;
if (this.time > -1) {
this.timer.innerHTML = "seconds remaining: " + this.time;
} else {
this.timer.innerHTML = "You Lose!";
this.watch.style.display = "block";
this.retry.style.display = "block";
clearInterval(this.counter);
clearInterval(this.counter2);
this.coins++;
this.coinCount.innerHTML = "Coins: " + this.coins;
}
this.notice.style.visibility = "visible";
this.notice.innerHTML = "Loaded";
}
...
replay() {
this.time = 5;
this.watch.style.display = "none";
this.notice.style.visibility = "hidden";
fader("Loaded");
this.retry.style.display = "none";
this.counter2 = setInterval(() => this.countdown(), 1000);
this.counter2;
}
...
}
new ReVideo();
It does not run if i say counter();
You could have a helper counter function to return the intervalID like:
counter () {
const interval = setInterval(() => this.countdown(), 1000);
return interval;
}
and in your constructor, you could assign the intervalID to some variable like:
this.interval = counter();
Then you can stop the counter by passing the variable holding the intervalID like clearInterval(this.interval);

How to decrement given time

I am getting an output of 00:20:00 which is correct but my problem now is its not decrementing even when I have subtracted it am i missing something?
$duration=0;
$startime=date('Y-m-d H:i:s');
$end_time=$end_time=date('Y-m-d H:i:s',
strtotime('+'.$duration.'minutes',strtotime($startime)));
$timefirst=strtotime($startime);
$timesecond=strtotime($end_time);
$differenceinseconds=$timesecond-$timefirst;
echo gmdate("H:i:s", $differenceinseconds);
my script
<div id='response'></div>
<script type="text/javascript">
setInterval(function(){
var xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET",'responseTime.php',false);
xmlhttp.send(null);
document.getElementById('response').innerHTML=xmlhttp.responseText;
},1000);
</script>
As RiggsFolly mentioned why waste servers time running a timer.
Here is what you can do in javascript,
<div id="stopwatch"></div>
<script>
var Stopwatch = function (elem, target, options) {
var timer = createTimer(),
offset,
clock,
interval;
// default options
options = options || {};
options.delay = options.delay || 1;
// append elements
elem.appendChild(timer);
// initialize
reset();
// private functions
function createTimer() {
var interval = 20 ; // 20 seconds
var element = document.createElement("progress");
element.setAttribute("max",interval);
return element;
}
function start() {
if (!interval) {
offset = Date.now();
interval = setInterval(update, options.delay);
}
}
function stop() {
if (interval) {
clearInterval(interval);
interval = null;
}
}
function reset() {
clock = 0;
render();
}
function update() {
clock += delta();
render();
}
function render() {
timer.value = parseInt(clock / 1000);
if(timer.value==interval){
// This is the point where timer ends, put your further code in here.
}
}
function delta() {
var now = Date.now(),
d = now - offset;
offset = now;
return d;
}
// public API
this.start = start;
this.stop = stop;
this.reset = reset;
};
var elem = document.getElementById("stopwatch");
var timer = new Stopwatch(elem, {delay: 10});
timer.start();
</script>

Javascript not able to make common functions for a prototype

I am trying to make timer in javascript using a prototype. Each time a new timer is created, a object of prototype is created. There are methods to increase time and print each second. The whole code snippet is as follows:
function Timer(elem) {
this.interval = null;
this.currentTime = {
sec: 0,
min: 0,
hr: 0
};
this.elem = elem;
};
Timer.prototype.start = function() {
var self = this;
if (!self.interval) {
self.interval = setInterval(update, 1000);
}
function update() {
incrementTime();
render();
}
function render() {
self.elem.innerText = getPrintableTime();
}
function incrementTime() {
self.currentTime["min"] += Math.floor((++self.currentTime["sec"]) / 60);
self.currentTime["hr"] += Math.floor(self.currentTime["min"] / 60);
self.currentTime["sec"] = self.currentTime["sec"] % 60;
self.currentTime["min"] = self.currentTime["min"] % 60;
}
function getPrintableTime() {
var text = getTwoDigitNumber(self.currentTime["hr"]) + ":" + getTwoDigitNumber(self.currentTime["min"]) + ":" + getTwoDigitNumber(self.currentTime["sec"]);
return text;
}
function getTwoDigitNumber(number) {
if (number > 9) {
return "" + number;
} else {
return "0" + number;
}
}
};
module.exports = Timer;
I have all methods in start function. The problem is that for each new object of Timer, new space for each method will be used which is very inefficient. But when I try to put methods outside of start function, they lose access to self variable. You can see that there is setInterval function used which will be calling these methods per second. I cannot use this also as this will be instance of Window in subsequent calls.
How can I solve this situation by only keeping one instance of all the interior methods?
You don't need to have all methods in the start function. Yes, for each new Timer instance, new space for each function will be used, but that is necessary when you want to work with setInterval as you need a function which closes over the instance. However, you need only one such closure, the other methods can be standard prototype methods.
function getTwoDigitNumber(number) {
return (number > 9 ? "" : "0") + number;
}
function Timer(elem) {
this.interval = null;
this.currentTime = {
sec: 0,
min: 0,
hr: 0
};
this.elem = elem;
};
Timer.prototype.start = function() {
var self = this;
if (!this.interval) {
this.interval = setInterval(function update() {
self.incrementTime();
self.render();
}, 1000);
}
};
Timer.prototype.render() {
this.elem.innerText = this.getPrintableTime();
};
Timer.prototype.incrementTime = function() {
this.currentTime.sec += 1;
this.currentTime.min += Math.floor(this.currentTime.sec / 60);
this.currentTime.hr += Math.floor(this.currentTime.min / 60);
this.currentTime.sec = this.currentTime.sec % 60;
this.currentTime.min = this.currentTime.min % 60;
};
Timer.prototype.getPrintableTime = function() {
var text = getTwoDigitNumber(this.currentTime.hr) + ":"
+ getTwoDigitNumber(this.currentTime.min) + ":"
+ getTwoDigitNumber(self.currentTime.sec);
return text;
};
module.exports = Timer;
Btw, regarding your incrementTime pattern, you should have a look at How to create an accurate timer in javascript?.
You can use apply to use functions defined outside of prototype with correct this context.
function Timer(elem) {
this.interval = null;
this.currentTime = {
sec: 0,
min: 0,
hr: 0
};
this.elem = elem;
};
function update() {
incrementTime.apply(this);
render.apply(this);
}
function render() {
this.elem.innerText = getPrintableTime.apply(this);
}
function incrementTime() {
this.currentTime["min"] += Math.floor((++this.currentTime["sec"]) / 60);
this.currentTime["hr"] += Math.floor(this.currentTime["min"] / 60);
this.currentTime["sec"] = this.currentTime["sec"] % 60;
this.currentTime["min"] = this.currentTime["min"] % 60;
}
function getPrintableTime() {
var text = getTwoDigitNumber(this.currentTime["hr"]) + ":" + getTwoDigitNumber(this.currentTime["min"]) + ":" + getTwoDigitNumber(this.currentTime["sec"]);
return text;
}
function getTwoDigitNumber(number) {
if (number > 9) {
return "" + number;
} else {
return "0" + number;
}
}
Timer.prototype.start = function() {
var self = this;
if (!self.interval) {
self.interval = setInterval(function() {
update.apply(self);
}, 1000);
}
};
document.addEventListener('DOMContentLoaded', function() {
var timer = new Timer(document.getElementById('timer'));
timer.start();
}, false);
<div id="timer"></div>
If I understand correctly, you're wanting to only create one interval.
One possible solution would be to create a static method and variable to manage the setInterval. I would note that while this may be more performance friendly, the timers will always start and run on the same count...not from the moment each timer is created. (See example)
Of course, you could capture the current timestamp and calculate the elapsed time from there. But, that's another thread ;)
function Timer(elem) {
this.interval = null;
this.currentTime = {
sec: 0,
min: 0,
hr: 0
};
this.elem = elem;
};
Timer.subscribe = function(timer) {
Timer.subscribers = Timer.subscribers || [];
if (Timer.subscribers.indexOf(timer) === -1) {
Timer.subscribers.push(timer);
timer.update.call(timer);
}
Timer.checkInterval();
};
Timer.unsubscribe = function(timer) {
Timer.subscribers = Timer.subscribers || [];
if (Timer.subscribers.indexOf(timer) !== -1) {
Timer.subscribers.splice(Timer.subscribers.indexOf(timer), 1);
}
Timer.checkInterval();
};
Timer.checkInterval = function() {
if (!Timer.interval && Timer.subscribers.length > 0) {
Timer.interval = setInterval(function() {
Timer.subscribers.forEach(function(item) {
item.update.call(item);
});
}, 1000);
} else if (Timer.interval && Timer.subscribers.length === 0) {
clearInterval(Timer.interval);
Timer.interval = null;
}
};
Timer.prototype = {
start: function() {
Timer.subscribe(this);
},
stop: function() {
Timer.unsubscribe(this);
},
update: function() {
this.incrementTime();
this.render();
},
incrementTime: function() {
this.currentTime["min"] += Math.floor((++this.currentTime["sec"]) / 60);
this.currentTime["hr"] += Math.floor(this.currentTime["min"] / 60);
this.currentTime["sec"] = this.currentTime["sec"] % 60;
this.currentTime["min"] = this.currentTime["min"] % 60;
},
render: function() {
var self = this;
function getPrintableTime() {
var text = getTwoDigitNumber(self.currentTime["hr"]) + ":" + getTwoDigitNumber(self.currentTime["min"]) + ":" + getTwoDigitNumber(self.currentTime["sec"]);
return text;
}
function getTwoDigitNumber(number) {
if (number > 9) {
return "" + number;
} else {
return "0" + number;
}
}
this.elem.innerText = getPrintableTime();
}
};
/**
*
*/
var timers = document.getElementById('timers');
function addTimer() {
var el = document.createElement('div');
var tmr = document.createElement('span');
var btn = document.createElement('button');
var t = new Timer(tmr);
btn.innerText = 'Stop';
btn.onclick = function() {
t.stop();
};
el.appendChild(tmr);
el.appendChild(btn);
timers.appendChild(el);
t.start();
};
<div id="timers"></div>
<button onclick="addTimer()">Add Timer</button>

How to Pause the timer on window blur and resume the timer on window focus event?

Thanks for seeing my question.
I am using wp-pro-quiz plugin for quiz. I want to know that how can I pause the timer if the window is not in focus or is blur and resume it when it is back to focus.?
My code:
I get reset when it get focused
var timelimit = (function () {
var _counter = config.timelimit;
var _intervalId = 0;
var instance = {};
instance.stop = function () {
if (_counter) {
window.clearInterval(_intervalId);
globalElements.timelimit.hide();
}
};
instance.start = function () {
var x;
var beforeTime;
if (!_counter)
return;
var $timeText = globalElements.timelimit.find('span').text(plugin.methode.parseTime(_counter));
var $timeDiv = globalElements.timelimit.find('.wpProQuiz_progress');
globalElements.timelimit.show();
$.winFocus(function (event) {
console.log("Blur\t\t", event);
},
function (event) {
console.log("Focus\t\t", event);
x = _counter * 1000;
beforeTime = +new Date();
});
_intervalId = window.setInterval(function () {
var diff = (+new Date() - beforeTime);
var elapsedTime = x - diff;
if (diff >= 500) {
$timeText.text(plugin.methode.parseTime(Math.ceil(elapsedTime / 1000)));
}
$timeDiv.css('width', (elapsedTime / x * 100) + '%');
if (elapsedTime <= 0) {
instance.stop();
plugin.methode.finishQuiz(true);
}
}, 16);
};
return instance;
})();
Use this wrapper function to pause, resume your timeout.
var Timer;
Timer = function(callback, delay) {
var remaining, start, timerId;
timerId = void 0;
start = void 0;
remaining = delay;
this.pause = function() {
window.clearTimeout(timerId);
remaining -= new Date - start;
};
this.resume = function() {
start = new Date;
window.clearTimeout(timerId);
timerId = window.setTimeout(callback, remaining);
};
this.resume();
};
Intialize it like this, timer = new Timer("callback_function_here", 45000)
In this case total time is 45 seconds for the callback and upon event triggers(blur or focus in your case) it will pause or resume the timer accordingly.
timer.pause() //pause the timer
timer.resume() //resume the timer
P.S - Use this function as per the logic of your code. You will have to make the timer calls accordingly in your code
I did it this way:
var time0 ; var setTimeout_Int; var focused = true; var resume_Fun ;
var addTime =0; var addTimeDiff =0;
window.onfocus = function() {
focused = true;
var d = new Date();
addTimeDiff = addTimeDiff +( d.getTime() - addTime );
resume_Fun();
};
window.onblur = function()
{
focused = false;
};
function init()
{
var d = new Date();
time0 = d.getTime();
setTimeout_Int = setTimeout(update, 1000 )
}
function update()
{
clearTimeout(setTimeout_Int);
var d = new Date();
if(focused)
{
if(d.getTime() -(time0+addTimeDiff) < 20000)
{
setTimeout_Int= setTimeout(update, 1000 )
}
}
else
{
addTime = d.getTime();
resume_Fun = update;
}
}
init();

Categories

Resources