%# http://api.rubyonrails.org/classes/ActionDispatch/Routing.html#module-ActionDispatch::Routing-label-Reloading+routes %> <%# Ensure routes are loaded during precompile. %> <% Rails.application.reload_routes! %> Rev.registerComponent 'Router', ### CLASS METHODS ### statics: # Dump of the route table from the rails application routesFromRails: <%= Rails.application.routes.routes.select{ |r| r.verb.match('GET') }.map { |x| [x.path.ast.to_s.gsub(/\A\//,''), x.defaults] }.to_json %> # Convert 'defaults' from Rails route, which looks like # { controller: 'foo', action: 'new' } into a path as array like this: # ['foo', 'new'] routeInfoParts: (info)-> parts = info.controller.split('/') parts.push(info.action) parts # Take the route path from routeInfoParts and turn it into a slash-delimited # string like this 'foo/new' routeFunctionName: (info)-> Rev.Components.Router.routeInfoParts(info).join('/') # Create the config hash which we pass to the backbone router, which looks # like this: # routes: { # "profiles(.:format)": "profiles/show", # "profiles/:id(.:format)": "profiles/show", # "foo(.:format)": "foo/show" # } routerConfig: -> routerConfig = routes: {} _(Rev.Components.Router.routesFromRails).each (pair)=> [key, value] = pair return unless value.controller && value.action routerConfig.routes[key] ?= Rev.Components.Router.routeFunctionName(value) routerConfig ### INSTANCE METHODS ### mixins: [Backbone.Events] getInitialState: -> bbRouter: new Backbone.Router(Rev.Components.Router.routerConfig()) # Seed the path and options (which we pass down to child components) from # the initial props we received. Updates will come from the AJAX prop fetch. path: @props.path options: @props.options componentDidMount: -> if App.router? && Rev.env == 'development' console.error 'You attempted to mount two routers at the same time.' + 'Only one router may be mounted at a time.' else App.router = @ # Start history to pay attention to pushState and hashChange events # Only start history client side. It uses APIs which are not available on # the server-- plus we don't need to catch route changes on during the # server-side render! if Rev.isClientContext() and not Backbone.History.started Backbone.history.start pushState: true # Listen for any route matches and handle @listenTo @state.bbRouter, 'route', @routeChangeHandler componentWillUnmount: -> if App.router == @ App.router = null @stopListening() locationURL: -> [baseUrl, query] = Backbone.history.fragment.split '?' "/#{baseUrl}?#{if query? and query.length then '&' else ''}#{encodeURIComponent '__props'}" # Fall back to full page navigation request onPropsFetchFailure: -> window.location = @locationURL() # Make a handler for successful props fetching of a given templatePath # Close over the templatePath so when Backbone calls the callback we have it makePropsFetchSuccessHandler: (templatePath)-> return (data, textStatus, xhr)=> try @setState path: templatePath, options: JSON.parse(data) window.scrollTo 0, 0 catch err @onPropsFetchFailure() routeChangeHandler: (templatePath, params)-> # Request the same props from the server that we would get for a server-side # render. If we get them back successfully, swap the page. If we fail, fall # back to server side render. Backbone.ajax url: @locationURL() beforeSend: (xhr) -> xhr.setRequestHeader "Content-Type", "text/reactprops" xhr.setRequestHeader "Accept", "text/reactprops" success: @makePropsFetchSuccessHandler(templatePath) error: @onPropsFetchFailure true navigateTo: (url, options)-> if options?.templatePath? Backbone.history.navigate(url) @routeChangeHandler(options.templatePath) else Backbone.history.navigate(url, trigger: true) render: ->