I'm building a Rails 4 application and I'm trying to combine location.hash Backbone History with turbolinks push state. The application is broken up into multiple smaller, SPA like pages.
The problem I'm having is that when I do Backbone.Router.navigate()
, within
a page, it doesn't register anything with turbolinks. Here's a hypothetical
example to demonstrate the problem:
/one
@router.navigate 'page-one'
/one#page-one
@router.navigate 'page-two'
/one#page-two
/two
/two
/two
/two
/one
I guessed that what's probably happening is that when navigating via backbone,
the browser is logging the change but turbolinks isn't. I tried to expose
Turbolinks.reflectNewUrl
:
@Turbolinks = { visit, pagesCached, reflectNewUrl }
And then modifying Backbone.Router.navigate
so that it registers with
turbolinks every time we navigate:
navigate = Backbone.Router.prototype.navigate
Backbone.Router.prototype.navigate = (page, args...) ->
navigate page, args...
window?.Turbolinks?.reflectNewUrl "##{page}"
That sort of worked, Step 8 above is no longer stuck on /two
, but
click on the back button again and we get this error:
Uncaught TypeError: Cannot call method 'getElementsByTagName' of null turbolinks.js:142
removeNoscriptTags turbolinks.js:142
changePage turbolinks.js:111
fetchHistory turbolinks.js:69
(anonymous function) turbolinks.js:390
So something is still wrong.
Now I'm hoping there's a kind soul out there to help me out and point out my perhaps obvious blunder :)
I disagree about not mixing Turbolinks and Backbone. They fulfil different roles and there are occasions where you might like to use both.
In fact, I understand Basecamp uses both.
You can use this (now infamous) post as a starting point: http://www.goddamnyouryan.com/blog/rails-4-turbolinks-and-backbone
However I still had the problem you're having. Here's how I solved it (provisionally, I haven't tested this in the wild):
window.MyApp =
Models: {}
Collections: {}
Views: {}
Routers: {}
initialize: ->
# Build it up
@router = new MyApp.Routers.Pages(data)
Backbone.history.start()
close: ->
# Tear it down
@router.close(false)
@router = undefined
# Turbolinks exposes the before-change event when a page change is initiated
$(document).on 'page:before-change', ->
# If the app has been initialized
if MyApp.router?
# Stop the history and clean up the app
Backbone.history.stop()
MyApp.close()
# We're leaving the domain of the backbone app, check for Turbolinks
if Turbolinks?.supported
# Push the last known state of the app to the Turbolinks history
window.history.replaceState {turbolinks: true, url: window.location.href}, window.title, window.location.href
$(document).on 'page:change', ->
# If it's the page with our backbone app
if $('#pages').size() > 0
MyApp.initialize()