core/origin.js

define([
  'underscore', 
  'backbone', 
  'core/l10n',
  'core/router',
  'core/browserStorage',
  'core/models/sessionModel'
], function(_, Backbone, l10n, Router, BrowserStorage, SessionModel) {
  var initialized = false;
  var eventTaps = [];
  var $loading;
  /**
   * Global singleton class
   * @class Origin
   */
  var Origin = _.extend({}, Backbone.Events, {
    /**
     * Flag used to determine if debug features should be enabled
     * @member Origin#debug
     * @type {Boolean}
     */
    debug: false,
    /**
     * Performs setup of the application, will only run once
     * @function Origin#initialize
     */
    initialize: _.once(function(callback) {
      listenToWindowEvents();
      new Router(this);
      initialized = true;
      this.trigger('origin:dataReady');
    }),
    /**
     * Initialises the application utilities
     * @function Origin#loadUtilities
     */
    loadUtilities: _.once(async function(callback) {
      try {
        /**
         * Reference to the constant attributes
         * @member Origin#constants
         * @type {Object}
         */
        this.constants = await $.get('/api/config');
        /**
         * Global reference to the l10n translation utilities
         * @member Origin#l10n
         * @type {l10n}
         */
        this.l10n = new l10n(this);
        /**
         * Global reference to the l10n translation utilities
         * @member Origin#l10n
         * @type {l10n}
         */
        this.browserStorage = new BrowserStorage(this);

        await this.l10n.load();
        callback();
      } catch(e) {
        console.error(e.message);
      }
    }),
    /**
     * Initialises the current session and caches for use
     * @function Origin#
     */
    startSession: _.once(function(callback) {
      initLoading();
      this.loadUtilities((function() {
        /**
         * Current session instance
         * @member Origin#sessionModel
         * @type {SessionModel}
         */
        Origin.sessionModel = new SessionModel(this);
        Origin.sessionModel.fetch({
          success: () => callback(),
          error: (m, jqXhr) => callback(new Error(jqXhr.responseJSON.message))
        });
      }).bind(this));
    }),
    /**
     * Whether the Origin object has loaded
     * @function Origin#hasInitialized
     * @return {Boolean}
     */
    hasInitialized: function() {
      return initialized;
    },
    /**
     * Overrides Backbone.Events#trigger to allow for tapping and debug logging
     * @function Origin#
     */
    trigger: function(eventName, data) {
      var args = arguments;
      callTaps(eventName, function() {
        if(Origin.debug){
          console.log('Origin.trigger:', eventName, (data ? data : ''));
        }
        Backbone.Events.trigger.apply(Origin, args);
      });
    },
    /**
     * Register a function to tap into a certain event before it fires
     * @function Origin#tap
     */
    tap: function(event, callback) {
      eventTaps.push({ event: event, callback: callback });
    },
    /**
     * Tells views to clean themselves up
     * @function Origin#removeViews
     */
    removeViews: function() {
      Origin.trigger('remove:views');
    }
  });

  // Private functions

  function initLoading() {
    $loading = $('.loading');
    hideLoading();

    Origin.on('origin:hideLoading', hideLoading, Origin);
    Origin.on('origin:showLoading', showLoading, Origin);
  }

  // abstracted window events
  function listenToWindowEvents() {
    $(document).on('keydown', onKeyDown);
    $(window).on('resize', onResize);
    $(window).on('blur focus', onFocusBlur);
  }

  function showLoading(shouldHideTopBar) {
    $loading
      .removeClass('display-none fade-out')
      .toggleClass('cover-top-bar', shouldHideTopBar);
  }

  function hideLoading() {
    $loading.addClass('fade-out');
    _.delay(_.bind(function() {
      $loading
        .addClass('display-none')
        .removeClass('cover-top-bar');
    }, this), 300);
  }

  // Calls all 'tapped' functions before continuing
  function callTaps(event, callback) {
    var taps = _.where(eventTaps, { event: event });
    // recurse
    function callTap() {
      var tap = taps.pop();
      if(!tap) return callback();
      tap.callback.call(Origin, callTap);
    }
    callTap();
  }

  // Event handling

  function onKeyDown(event) {
    if($(event.target).is('input,textarea,.ck')) return;
    Origin.trigger('key:down', event);
  }

  function onResize(event) {
    var $window = $(this);
    var windowWidth = $window.width();
    var windowHeight = $window.height();
    Origin.trigger('window:resize', windowWidth, windowHeight);
  }

  function onFocusBlur(event) {
    var $win = $('window');
    var prevType = $win.data("prevType");
    // prevent double-firing
    if(prevType === event.type) return;
    // send out Origin events
    var eventName = (event.type === 'focus') ? 'active' : 'inactive';
    Origin.trigger('window:' + eventName, event);
  }

  return Origin;
});