BigW Consortium Gitlab

smart_interval.js.es6 4.45 KB
Newer Older
1 2 3 4 5 6 7 8 9
/*
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
*
* */

(() => {
  class SmartInterval {
    /**
10 11 12 13 14 15 16 17 18 19
      * @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.
20
      */
21
    constructor(opts = {}) {
22
      this.cfg = {
23 24 25 26 27 28 29
        callback: opts.callback,
        startingInterval: opts.startingInterval,
        maxInterval: opts.maxInterval,
        hiddenInterval: opts.hiddenInterval,
        incrementByFactorOf: opts.incrementByFactorOf,
        lazyStart: opts.lazyStart,
        immediateExecution: opts.immediateExecution,
30 31 32 33
      };

      this.state = {
        intervalId: null,
34
        currentInterval: this.cfg.startingInterval,
35 36 37 38 39 40 41 42 43 44 45
        pageVisibility: 'visible',
      };

      this.initInterval();
    }
    /* public */

    start() {
      const cfg = this.cfg;
      const state = this.state;

46 47 48 49 50
      if (cfg.immediateExecution) {
        cfg.immediateExecution = false;
        cfg.callback();
      }

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
      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();
    }

69 70 71 72 73 74 75 76 77
    onVisibilityHidden() {
      if (this.cfg.hiddenInterval) {
        this.setCurrentInterval(this.cfg.hiddenInterval);
        this.resume();
      } else {
        this.cancel();
      }
    }

78 79 80 81 82 83
    // start a timer, using the existing interval
    resume() {
      this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
      this.start();
    }

84 85 86 87 88
    onVisibilityVisible() {
      this.cancel();
      this.start();
    }

89 90
    destroy() {
      this.cancel();
91
      document.removeEventListener('visibilitychange', this.handleVisibilityChange);
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
      $(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)
110
      document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
111 112 113 114 115 116 117
    }

    initPageUnloadHandling() {
      // prevent interval continuing after page change, when kept in cache by Turbolinks
      $(document).on('page:before-unload', () => this.cancel());
    }

118 119 120 121 122
    handleVisibilityChange(e) {
      this.state.pageVisibility = e.target.visibilityState;
      const intervalAction = this.isPageVisible() ?
        this.onVisibilityVisible :
        this.onVisibilityHidden;
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137

      intervalAction.apply(this);
    }

    getCurrentInterval() {
      return this.state.currentInterval;
    }

    setCurrentInterval(newInterval) {
      this.state.currentInterval = newInterval;
    }

    incrementInterval() {
      const cfg = this.cfg;
      const currentInterval = this.getCurrentInterval();
138
      if (cfg.hiddenInterval && !this.isPageVisible()) return;
139 140 141 142 143 144 145 146 147
      let nextInterval = currentInterval * cfg.incrementByFactorOf;

      if (nextInterval > cfg.maxInterval) {
        nextInterval = cfg.maxInterval;
      }

      this.setCurrentInterval(nextInterval);
    }

148 149
    isPageVisible() { return this.state.pageVisibility === 'visible'; }

150 151 152 153 154 155 156 157
    stopTimer() {
      const state = this.state;

      state.intervalId = window.clearInterval(state.intervalId);
    }
  }
  gl.SmartInterval = SmartInterval;
})(window.gl || (window.gl = {}));