/* * 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 = {}));