130 lines
3.4 KiB
JavaScript
130 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 = {}));
|