131 lines
3.4 KiB
JavaScript
131 lines
3.4 KiB
JavaScript
![]() |
/*
|
||
|
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
|
||
|
* and controllable by a public API.
|
||
|
*
|
||
|
* */
|
||
|
|
||
|
(() => {
|
||
|
class SmartInterval {
|
||
|
/**
|
||
|
* @param { function } callback Function to be called on each iteration (required)
|
||
|
* @param { milliseconds } startingInterval `currentInterval` is set to this initially
|
||
|
* @param { milliseconds } maxInterval `currentInterval` will be incremented to this
|
||
|
* @param { integer } incrementByFactorOf `currentInterval` is incremented by this factor
|
||
|
* @param { boolean } lazyStart Configure if timer is initialized on instantiation or lazily
|
||
|
*/
|
||
|
constructor({ callback, startingInterval, maxInterval, incrementByFactorOf, lazyStart }) {
|
||
|
this.cfg = {
|
||
|
callback,
|
||
|
startingInterval,
|
||
|
maxInterval,
|
||
|
incrementByFactorOf,
|
||
|
lazyStart,
|
||
|
};
|
||
|
|
||
|
this.state = {
|
||
|
intervalId: null,
|
||
|
currentInterval: startingInterval,
|
||
|
pageVisibility: 'visible',
|
||
|
};
|
||
|
|
||
|
this.initInterval();
|
||
|
}
|
||
|
/* public */
|
||
|
|
||
|
start() {
|
||
|
const cfg = this.cfg;
|
||
|
const state = this.state;
|
||
|
|
||
|
state.intervalId = window.setInterval(() => {
|
||
|
cfg.callback();
|
||
|
|
||
|
if (this.getCurrentInterval() === cfg.maxInterval) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.incrementInterval();
|
||
|
this.resume();
|
||
|
}, this.getCurrentInterval());
|
||
|
}
|
||
|
|
||
|
// cancel the existing timer, setting the currentInterval back to startingInterval
|
||
|
cancel() {
|
||
|
this.setCurrentInterval(this.cfg.startingInterval);
|
||
|
this.stopTimer();
|
||
|
}
|
||
|
|
||
|
// start a timer, using the existing interval
|
||
|
resume() {
|
||
|
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
|
||
|
this.start();
|
||
|
}
|
||
|
|
||
|
destroy() {
|
||
|
this.cancel();
|
||
|
$(document).off('visibilitychange').off('page:before-unload');
|
||
|
}
|
||
|
|
||
|
/* private */
|
||
|
|
||
|
initInterval() {
|
||
|
const cfg = this.cfg;
|
||
|
|
||
|
if (!cfg.lazyStart) {
|
||
|
this.start();
|
||
|
}
|
||
|
|
||
|
this.initVisibilityChangeHandling();
|
||
|
this.initPageUnloadHandling();
|
||
|
}
|
||
|
|
||
|
initVisibilityChangeHandling() {
|
||
|
// cancel interval when tab no longer shown (prevents cached pages from polling)
|
||
|
$(document)
|
||
|
.off('visibilitychange').on('visibilitychange', (e) => {
|
||
|
this.state.pageVisibility = e.target.visibilityState;
|
||
|
this.handleVisibilityChange();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
initPageUnloadHandling() {
|
||
|
// prevent interval continuing after page change, when kept in cache by Turbolinks
|
||
|
$(document).on('page:before-unload', () => this.cancel());
|
||
|
}
|
||
|
|
||
|
handleVisibilityChange() {
|
||
|
const state = this.state;
|
||
|
|
||
|
const intervalAction = state.pageVisibility === 'hidden' ? this.cancel : this.resume;
|
||
|
|
||
|
intervalAction.apply(this);
|
||
|
}
|
||
|
|
||
|
getCurrentInterval() {
|
||
|
return this.state.currentInterval;
|
||
|
}
|
||
|
|
||
|
setCurrentInterval(newInterval) {
|
||
|
this.state.currentInterval = newInterval;
|
||
|
}
|
||
|
|
||
|
incrementInterval() {
|
||
|
const cfg = this.cfg;
|
||
|
const currentInterval = this.getCurrentInterval();
|
||
|
let nextInterval = currentInterval * cfg.incrementByFactorOf;
|
||
|
|
||
|
if (nextInterval > cfg.maxInterval) {
|
||
|
nextInterval = cfg.maxInterval;
|
||
|
}
|
||
|
|
||
|
this.setCurrentInterval(nextInterval);
|
||
|
}
|
||
|
|
||
|
stopTimer() {
|
||
|
const state = this.state;
|
||
|
|
||
|
state.intervalId = window.clearInterval(state.intervalId);
|
||
|
}
|
||
|
}
|
||
|
gl.SmartInterval = SmartInterval;
|
||
|
})(window.gl || (window.gl = {}));
|