• Jump To … +
    chaplin.coffee application.coffee composer.coffee controller.coffee dispatcher.coffee composition.coffee event_broker.coffee history.coffee route.coffee router.coffee support.coffee sync_machine.coffee utils.coffee mediator.coffee collection.coffee model.coffee collection_view.coffee layout.coffee view.coffee
  • history.coffee

  • ¶
    'use strict'
    
    _ = require 'underscore'
    Backbone = require 'backbone'
  • ¶

    Cached regex for stripping a leading hash/slash and trailing space.

    routeStripper = /^[#\/]|\s+$/g
  • ¶

    Cached regex for stripping leading and trailing slashes.

    rootStripper = /^\/+|\/+$/g
  • ¶

    Cached regex for detecting MSIE.

    isExplorer = /msie [\w.]+/
  • ¶

    Cached regex for removing a trailing slash.

    trailingSlash = /\/$/
  • ¶

    Patched Backbone.History with a basic query strings support

    class History extends Backbone.History
  • ¶

    Get the cross-browser normalized URL fragment, either from the URL, the hash, or the override.

      getFragment: (fragment, forcePushState) ->
        if not fragment?
          if @_hasPushState or not @_wantsHashChange or forcePushState
  • ¶

    CHANGED: Make fragment include query string.

            fragment = @location.pathname + @location.search
            root = @root.replace trailingSlash, ''
            fragment = fragment.substr root.length unless fragment.indexOf root
          else
            fragment = @getHash()
    
        fragment.replace routeStripper, ''
  • ¶

    Start the hash change handling, returning true if the current URL matches an existing route, and false otherwise.

      start: (options) ->
        throw new Error 'Backbone.history has already been started' if Backbone.History.started
        Backbone.History.started = true
  • ¶

    Figure out the initial configuration. Do we need an iframe? Is pushState desired ... is it available?

        @options          = _.extend {}, {root: '/'}, @options, options
        @root             = @options.root
        @_wantsHashChange = @options.hashChange isnt false
        @_wantsPushState  = Boolean @options.pushState
        @_hasPushState    = Boolean (@options.pushState and @history and @history.pushState)
        fragment          = @getFragment()
  • ¶

    Normalize root to always include a leading and trailing slash.

        @root = ('/' + @root + '/').replace rootStripper, '/'
  • ¶

    Depending on whether we're using pushState or hashes, and whether 'onhashchange' is supported, determine how we check the URL state.

        if (@_hasPushState)
          Backbone.$(window).on 'popstate', @checkUrl
        else if @_wantsHashChange and 'onhashchange' of window
          Backbone.$(window).on 'hashchange', @checkUrl
        else if @_wantsHashChange
          @_checkUrlInterval = setInterval @checkUrl, @interval
  • ¶

    Determine if we need to change the base url, for a pushState link opened by a non-pushState browser.

        @fragment = fragment
        loc = @location
        atRoot = loc.pathname.replace(/[^\/]$/, '$&/') is @root
  • ¶

    If we've started off with a route from a pushState-enabled browser, but we're currently in a browser that doesn't support it...

        if @_wantsHashChange and @_wantsPushState and not @_hasPushState and not atRoot
  • ¶

    CHANGED: Prevent query string from being added before hash. So, it will appear only after #, as it has been already included into @fragment

          @fragment = @getFragment null, true
          @location.replace @root + '#' + @fragment
  • ¶

    Return immediately as browser will do redirect to new url

          return true
  • ¶

    Or if we've started out with a hash-based route, but we're currently in a browser where it could be pushState-based instead...

        else if @_wantsPushState and @_hasPushState and atRoot and loc.hash
          @fragment = @getHash().replace routeStripper, ''
  • ¶

    CHANGED: It's no longer needed to add loc.search at the end, as query params have been already included into @fragment

          @history.replaceState {}, document.title, @root + @fragment
    
        @loadUrl() if not @options.silent
    
      navigate: (fragment='', options) ->
        return false unless Backbone.History.started
    
        options = {trigger: options} if not options or options is true
    
        fragment = @getFragment fragment
        url = @root + fragment
  • ¶

    Remove fragment replace, coz query string different mean difference page Strip the fragment of the query and hash for matching. fragment = fragment.replace(pathStripper, '')

        return false if @fragment is fragment
        @fragment = fragment
  • ¶

    Don't include a trailing slash on the root.

        if fragment.length is 0 and url isnt '/'
          url = url.slice 0, -1
  • ¶

    If pushState is available, we use it to set the fragment as a real URL.

        if @_hasPushState
          historyMethod = if options.replace then 'replaceState' else 'pushState'
          @history[historyMethod] {}, document.title, url
  • ¶

    If hash changes haven't been explicitly disabled, update the hash fragment to store history.

        else if @_wantsHashChange
          @_updateHash @location, fragment, options.replace
  • ¶

    iframe

          isSameFragment = fragment isnt @getFragment @getHash(@iframe)
          if @iframe? and isSameFragment
  • ¶

    Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change. When replace is true, we don't

            @iframe.document.open().close() unless options.replace
            @_updateHash @iframe.location, fragment, options.replace
  • ¶

    If you've told us that you explicitly don't want fallback hashchange- based history, then navigate becomes a page refresh.

        else
          return @location.assign url
    
        if options.trigger
          return @loadUrl(fragment)
    
    module.exports = if Backbone.$ then History else Backbone.History