Skip to content

Instantly share code, notes, and snippets.

@mrjjwright
Created March 1, 2011 16:55
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mrjjwright/849441 to your computer and use it in GitHub Desktop.
Save mrjjwright/849441 to your computer and use it in GitHub Desktop.
Add pretty html5 history support to Backbone
#
# Replaces Backbone.History with support for HTML5 history API if browser supports it.
# To use, setup your controllers as usual and try it with a browser that supports the HTML5 history API.
# For browsers that don't support the HTML5 API, this will fall back to using the default Backbone hash routing.
#
# I have only tested this on my project in Firefox, Safari and Chrome so let me know.
#
pushSupport = window.history? and window.history.pushState?
if pushSupport and Backbone? and Backbone.History?
rootUrl = document.location.protocol+'//'+(document.location.host or document.location.hostname)
_.extend Backbone.History.prototype,
# Simply return the browser's whole path
getFragment: ->
return window.location.pathname.substring(1)
# Set all hrefs to be saved in the browser history and then routed through your controllers.
# Handy for single page apps with no server side templating.
# You can remove this and the call to it in start if you don't want it.
# In that case, call Backbone.history.saveLocation and Backbone.history.loadUrl directly
ajaxifyInternalLinks: ->
self = this
$('a[href^=/]').live 'click', (event) ->
href = $(this).attr('href')
self.saveLocation(href)
self.loadUrl(href.substring(1))
event.preventDefault()
return false
# Overwriting this function basically replaces Backbone's history mechanism with everything in this module.
start: ->
_.bindAll(this, 'loadUrl')
# Remove this call if you don't all links to be routed through your controller automatically,
@ajaxifyInternalLinks()
# Route the initial browser url from page load
@loadUrl()
# Handle history navigation, such as browser back/forward clicks by listening for popstate
# We don't use JQuery event binding here because the popstate event isn't passed properly.
# If there is no event.state then it must be a page load event, ignore it.
self = this
window.onpopstate = (event) -> if event.state? then self.loadUrl()
# This has been modified to allow the fragment to be passed in directly.
# If fragment is not passed it will be loaded from getFragment as usual.
loadUrl: (fragment) ->
# Such a nice assignment/check in CoffeeScript...
fragment = @fragment = fragment ? @getFragment()
# We skip all the other stuff Backbone does for hashes and go straight to the routes
_.each @handlers, (handler) ->
if handler.route.test(fragment) then handler.callback(fragment)
# This has been modified to allow the browser history to be replaced by passing true for replace.
# This is handy for times you need to patch the history from inside the controller routes or somewhere
# else in your app.
saveLocation: (fragment, replace) ->
return if not fragment?
return if @fragment is fragment
@fragment = fragment
loc = window.location
url = loc.protocol + "//" + loc.host + fragment
state = {ts: new Date()}
if replace? then window.history.replaceState(state, document.title, url)
else window.history.pushState(state, document.title, url)
@mataspetrikas
Copy link

you might want to use live/delegate instead of bind if you call the ajaxifyInternalLinks only once.
otherwise it won't work after the first page redraw

@mrjjwright
Copy link
Author

Thanks, noticed that myself when I went to use it today. :)

@rstacruz
Copy link

Nice. :) I've done something similar and I've made exclusions to what gets affected ajaxifyInternalLinks.

Something like:

$('a[href^=/]').live 'click', (event) ->
   # Skip shift-clicks
   return true  if event.shiftKey? or event.metaKey? or event.ctrlKey?
   # Skip those that link to another window
   return true  if $(this).attr('target')
   # etc...

@sinefunc
Copy link

Also, your loadUrl will match multiple routes (unlike the original loadUrl). It should use _.any instead of _.each:

    # We skip all the other stuff Backbone does for hashes and go straight to the routes
  _.any @handlers, (handler) ->
    if handler.route.test(fragment)
      handler.callback(fragment)
      return true

@d4tocchini
Copy link

shouldn't your jQuery selector be:

$('a[href^="/"]')

@mrjjwright
Copy link
Author

Yes it should, thanks d3tocchnini!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment