#

API Docs for: v2.11.1
Show: Inherited Protected Private Deprecated

# File: packages/ember-routing/lib/location/history_location.js

    import {
      get,
      set
    } from 'ember-metal';

    import { Object as EmberObject } from 'ember-runtime';
    import EmberLocation from './api';

    /**
    @module ember
    @submodule ember-routing
    */

    let popstateFired = false;

    /**
      Ember.HistoryLocation implements the location API using the browser's
      history.pushState API.

      @class HistoryLocation
      @namespace Ember
      @extends Ember.Object
      @private
    */
    export default EmberObject.extend({
      implementation: 'history',

      init() {
        this._super(...arguments);

        let base = document.querySelector('base');
        let baseURL = base ? base.getAttribute('href') : '';

        set(this, 'baseURL', baseURL);
        set(this, 'location', get(this, 'location') || window.location);

        this._popstateHandler = undefined;
      },

      /**
        Used to set state on first call to setURL

        @private
        @method initState
      */
      initState() {
        let history = get(this, 'history') || window.history;
        set(this, 'history', history);

        if (history && 'state' in history) {
          this.supportsHistory = true;
        }

        this.replaceState(this.formatURL(this.getURL()));
      },

      /**
        Will be pre-pended to path upon state change

        @property rootURL
        @default '/'
        @private
      */
      rootURL: '/',

      /**
        Returns the current `location.pathname` without `rootURL` or `baseURL`

        @private
        @method getURL
        @return url {String}
      */
      getURL() {
        let location = get(this, 'location');
        let path = location.pathname;

        let rootURL = get(this, 'rootURL');
        let baseURL = get(this, 'baseURL');

        // remove trailing slashes if they exists
        rootURL = rootURL.replace(/\/$/, '');
        baseURL = baseURL.replace(/\/$/, '');

        // remove baseURL and rootURL from start of path
        let url = path
          .replace(new RegExp('^' + baseURL + '(?=/|$)'), '')
          .replace(new RegExp('^' + rootURL + '(?=/|$)'), '');

        let search = location.search || '';
        url += search;
        url += this.getHash();

        return url;
      },

      /**
        Uses `history.pushState` to update the url without a page reload.

        @private
        @method setURL
        @param path {String}
      */
      setURL(path) {
        let state = this.getState();
        path = this.formatURL(path);

        if (!state || state.path !== path) {
          this.pushState(path);
        }
      },

      /**
        Uses `history.replaceState` to update the url without a page reload
        or history modification.

        @private
        @method replaceURL
        @param path {String}
      */
      replaceURL(path) {
        let state = this.getState();
        path = this.formatURL(path);

        if (!state || state.path !== path) {
          this.replaceState(path);
        }
      },

      /**
        Get the current `history.state`. Checks for if a polyfill is
        required and if so fetches this._historyState. The state returned
        from getState may be null if an iframe has changed a window's
        history.

        @private
        @method getState
        @return state {Object}
      */
      getState() {
        if (this.supportsHistory) {
          return get(this, 'history').state;
        }

        return this._historyState;
      },

      /**
       Pushes a new state.

       @private
       @method pushState
       @param path {String}
      */
      pushState(path) {
        let state = { path: path };

        get(this, 'history').pushState(state, null, path);

        this._historyState = state;

        // used for webkit workaround
        this._previousURL = this.getURL();
      },

      /**
       Replaces the current state.

       @private
       @method replaceState
       @param path {String}
      */
      replaceState(path) {
        let state = { path: path };
        get(this, 'history').replaceState(state, null, path);

        this._historyState = state;

        // used for webkit workaround
        this._previousURL = this.getURL();
      },

      /**
        Register a callback to be invoked whenever the browser
        history changes, including using forward and back buttons.

        @private
        @method onUpdateURL
        @param callback {Function}
      */
      onUpdateURL(callback) {
        this._removeEventListener();

        this._popstateHandler = () => {
          // Ignore initial page load popstate event in Chrome
          if (!popstateFired) {
            popstateFired = true;
            if (this.getURL() === this._previousURL) { return; }
          }
          callback(this.getURL());
        };

        window.addEventListener('popstate', this._popstateHandler);
      },

      /**
        Used when using `{{action}}` helper.  The url is always appended to the rootURL.

        @private
        @method formatURL
        @param url {String}
        @return formatted url {String}
      */
      formatURL(url) {
        let rootURL = get(this, 'rootURL');
        let baseURL = get(this, 'baseURL');

        if (url !== '') {
          // remove trailing slashes if they exists
          rootURL = rootURL.replace(/\/$/, '');
          baseURL = baseURL.replace(/\/$/, '');
        } else if (baseURL.match(/^\//) && rootURL.match(/^\//)) {
          // if baseURL and rootURL both start with a slash
          // ... remove trailing slash from baseURL if it exists
          baseURL = baseURL.replace(/\/$/, '');
        }

        return baseURL + rootURL + url;
      },

      /**
        Cleans up the HistoryLocation event listener.

        @private
        @method willDestroy
      */
      willDestroy() {
        this._removeEventListener();
      },

      /**
        @private

        Returns normalized location.hash

        @method getHash
      */
      getHash: EmberLocation._getHash,

      _removeEventListener() {
        if (this._popstateHandler) {
          window.removeEventListener('popstate', this._popstateHandler);
        }
      }
    });