159 lines
4.5 KiB
JavaScript
159 lines
4.5 KiB
JavaScript
|
/*
|
||
|
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
|
||
|
* and controllable by a public API.
|
||
|
*
|
||
|
* */
|
||
|
|
||
|
(() => {
|
||
|
class SmartInterval {
|
||
|
/**
|
||
|
* @param { function } opts.callback Function to be called on each iteration (required)
|
||
|
* @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
|
||
|
* @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
|
||
|
* @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
|
||
|
* when the page is hidden
|
||
|
* @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
|
||
|
* @param { boolean } opts.lazyStart Configure if timer is initialized on
|
||
|
* instantiation or lazily
|
||
|
* @param { boolean } opts.immediateExecution Configure if callback should
|
||
|
* be executed before the first interval.
|
||
|
*/
|
||
|
constructor(opts = {}) {
|
||
|
this.cfg = {
|
||
|
callback: opts.callback,
|
||
|
startingInterval: opts.startingInterval,
|
||
|
maxInterval: opts.maxInterval,
|
||
|
hiddenInterval: opts.hiddenInterval,
|
||
|
incrementByFactorOf: opts.incrementByFactorOf,
|
||
|
lazyStart: opts.lazyStart,
|
||
|
immediateExecution: opts.immediateExecution,
|
||
|
};
|
||
|
|
||
|
this.state = {
|
||
|
intervalId: null,
|
||
|
currentInterval: this.cfg.startingInterval,
|
||
|
pageVisibility: 'visible',
|
||
|
};
|
||
|
|
||
|
this.initInterval();
|
||
|
}
|
||
|
/* public */
|
||
|
|
||
|
start() {
|
||
|
const cfg = this.cfg;
|
||
|
const state = this.state;
|
||
|
|
||
|
if (cfg.immediateExecution) {
|
||
|
cfg.immediateExecution = false;
|
||
|
cfg.callback();
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
onVisibilityHidden() {
|
||
|
if (this.cfg.hiddenInterval) {
|
||
|
this.setCurrentInterval(this.cfg.hiddenInterval);
|
||
|
this.resume();
|
||
|
} else {
|
||
|
this.cancel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// start a timer, using the existing interval
|
||
|
resume() {
|
||
|
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
|
||
|
this.start();
|
||
|
}
|
||
|
|
||
|
onVisibilityVisible() {
|
||
|
this.cancel();
|
||
|
this.start();
|
||
|
}
|
||
|
|
||
|
destroy() {
|
||
|
this.cancel();
|
||
|
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
|
||
|
$(document).off('visibilitychange').off('beforeunload');
|
||
|
}
|
||
|
|
||
|
/* 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.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
|
||
|
}
|
||
|
|
||
|
initPageUnloadHandling() {
|
||
|
// TODO: Consider refactoring in light of turbolinks removal.
|
||
|
// prevent interval continuing after page change, when kept in cache by Turbolinks
|
||
|
$(document).on('beforeunload', () => this.cancel());
|
||
|
}
|
||
|
|
||
|
handleVisibilityChange(e) {
|
||
|
this.state.pageVisibility = e.target.visibilityState;
|
||
|
const intervalAction = this.isPageVisible() ?
|
||
|
this.onVisibilityVisible :
|
||
|
this.onVisibilityHidden;
|
||
|
|
||
|
intervalAction.apply(this);
|
||
|
}
|
||
|
|
||
|
getCurrentInterval() {
|
||
|
return this.state.currentInterval;
|
||
|
}
|
||
|
|
||
|
setCurrentInterval(newInterval) {
|
||
|
this.state.currentInterval = newInterval;
|
||
|
}
|
||
|
|
||
|
incrementInterval() {
|
||
|
const cfg = this.cfg;
|
||
|
const currentInterval = this.getCurrentInterval();
|
||
|
if (cfg.hiddenInterval && !this.isPageVisible()) return;
|
||
|
let nextInterval = currentInterval * cfg.incrementByFactorOf;
|
||
|
|
||
|
if (nextInterval > cfg.maxInterval) {
|
||
|
nextInterval = cfg.maxInterval;
|
||
|
}
|
||
|
|
||
|
this.setCurrentInterval(nextInterval);
|
||
|
}
|
||
|
|
||
|
isPageVisible() { return this.state.pageVisibility === 'visible'; }
|
||
|
|
||
|
stopTimer() {
|
||
|
const state = this.state;
|
||
|
|
||
|
state.intervalId = window.clearInterval(state.intervalId);
|
||
|
}
|
||
|
}
|
||
|
gl.SmartInterval = SmartInterval;
|
||
|
})(window.gl || (window.gl = {}));
|