How to handle non-root URLs in a singlepage app?
Asked Answered
P

3

6

I try to make a single page app with Rails 3.2 and Backbone.js with pushState option but faced with something that I do not understand.

If I load the root URL of the app (/), everything goes right: Rails return an HTML-layout with JS which bootstraps Backbone which makes some XHRs for JSON-entities and renders the content.

But if I start using app from non-root URL (e.g. by manually typing it in the browser's address bar) then Rails will try to handle this request using theirs routing rules from routes.rb - that's wrong, cause it's a "Backbone's" route. How do I load the page and bootstrap Backbone for handling this URL in that case?

Prevaricator answered 22/8, 2012 at 23:13 Comment(0)
P
14

Finally I found the solution.

I put the following code into my routes.rb

class XHRConstraint
  def matches?(request)
    !request.xhr? && !(request.url =~ /\.json$/ && ::Rails.env == 'development')
  end
end

match '(*url)' => 'home#index', :constraints => XHRConstraint.new

With this matcher all non-XHR requests are routed to HomeController which returns an HTML page. And XHR requests will be handled by other controllers which return JSON responses. Also I left requests ending with ".json" as valid in development environment for debugging.

Prevaricator answered 23/8, 2012 at 21:25 Comment(2)
I also found this great write-up by artsy describing how to create a global link handler with Backbone pushState to avoid page refreshes, which I feel is very much in conjunction with this answer and might help some people.Rennarennane
Thanks man! Had to changed the line to this to be able to work in Rails 4: get '*path' => 'home#index', :constraints => XHRConstraint.newBanket
H
1

This is a somewhat tricky issue, but basically in a nutshell, you need to respond to all valid (HTML) requests in rails with the same (root) page, from there backbone will take over and route to the correct route handler (in your bakckbone router).

I've discussed this issue in more detail here: rails and backbone working together

Basically what I do is to create actions for every page that I want to handle, and blank views. I use respond_with to return the page (which is the same in each case) and because I handle GET actions only for HTML requests, I add this line at the top of the controller:

respond_to :html, :only => [ :show, :new ]

JSON requests are handled with respond_with as well, but unlike the HTML requests actually return the requested resource (and perform the requested action in the case of PUT, POST and DELETE).

Hakan answered 22/8, 2012 at 23:19 Comment(6)
Updated my answer with a bit more info.Hakan
That will do the trick but it's not too elegant way cause all of Backbone's routes must be responsible by Rails. So you have to update server routes when you change client's routes. Is there really no way to delegate all html requests to the root controller?Prevaricator
I disagree: if you delegate all requests to the root controller then you are saying that all requests are valid, when in fact most are not. Rails needs to know which routes are valid and which are not in order to properly return status codes for incorrect routes, etc. This is duplication but it is important duplication. It's inevitable once you start using pushState.Hakan
Yes, if I delegate all html requests to the root controller, I assume that all html requests are valid because it is a singlepage app and any html request should return the root page. I do not agree that Rails need to know about client's routes because their management is Backbone's router business. Rails are stateless and there are no "pages" or "actions", only resources (which can be accessed only by XHR) and the root page.Prevaricator
Thank you :) In response to your last point, I would say: then why are you using pushState? It sounds to me like the normal hash/anchor approach is what you want. pushState is meant to help you make your pages accessible as HTML resources, which is why (I believe) rails should know about their routes, etc.Hakan
Because of History API and also because of hashed URLs are not so beautyful :)Prevaricator
N
1

Backbone will not be informed of your url change if you do it manually. This change will be catch by the browser and it will do its job sending the request to the server as usual.

Same if you click in a normal link, it will follow its href without inform Backbone.

If you want Backbone being in charge of a url change you have to do it through the Backbone tools you have available and this is the own Router.

So if you want to make an URL change in the Backbone way you have to do it explicitly, something like:

app.router.navigate("my/route", {trigger: true});
Numismatology answered 23/8, 2012 at 11:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.