chaplinjs / chaplin

HTML5 application architecture using Backbone.js
http://chaplinjs.org
Other
2.85k stars 232 forks source link

controller.historyURL is useless? #51

Closed paulmillr closed 12 years ago

paulmillr commented 12 years ago

I'm not sure but it seems that it's useless.

For example:

# Routes
match 'feeds/favorite', 'feeds#show_favorite'
match 'feeds/popular', 'feeds#show_popular'

# Controller
class FeedsController
  historyURL: 'feeds'
  # ...

What's its purpose in this case? User will be redirected to feeds/ if he'll navigate to / etc.

molily commented 12 years ago

If a route matches, the Router adds a path to the params. If this is present, it is used as URL, see ApplicationController:

    adjustURL: (controller, params) ->
      if params.path
        # Just use the matched path
        url = params.path

      else if typeof controller.historyURL is 'function'
        # Use controller.historyURL to get the URL
        # If the property is a function, call it
        url = controller.historyURL params

The question is, when is path not available and when comes historyURL into play? In short, every time a controller is not invoked by a route. If you already have a model and want to pass it to a controller, you can start the controller directly with some params:

mediator.publish '!startupController', 'feeds', 'show',
  feed: someExistingModel
  some: 'options'
  foo: bar

This is especially useful if there is a Collection at feeds#index which already has the a full Feed model and wants to call feeds#show for it. This could be done via routing to /feeds/123, but then the model would not be available instantly.

On moviepilot, our controllers have a conventional way to get the model data:

# Get the model data from JSON configuration or from params
#
# 1. Try to get the page data from configuration
# 2. Try to get an object from params
# 3. Fallback to a stub with an ID from params.id

getModelData: (params, key, idKey = 'id') ->
  # Shortcut
  config = MovieExplorerConfig
  fromConfig = @getModelDataFromConfig key
  if fromConfig
    data = fromConfig
  else if params[key]
    data = params[key]
    # If it’s already a model, unbox the attributes
    if data instanceof MovieExplorer.Model
      data = data.toJSON()
  else if params[idKey]
    data = id: params[idKey]
  else
    return null

In the specific controller, there’s getFeedData which curries getModelData so we can write:

@feed = new Feed @getFeedData(params)

If you go to http://moviepilot.com/movies/9040 directly, the model data is already present in the HTML as JSON, stored in the MovieExplorerConfig object.

If we want to link to /movies/9040 from inside the application, most of the time we already have the movie model. So we just pass it via publish '!startupController', 'movies', 'show', movie: movieModel. In this case, historyURL is called to determine the right URL.

If we just got the ID from a matched route, the model will automatically fetch itself from the server.

I agree historyURL stuff may be confusing since it’s not used when routes match, which is the normal case. The question is however how to build a proper URL when starting controllers directly. I find this feature very useful in a fully-encapsulated system since it allows controlled data sharing.

Of course, there could be other solutions to solve this, like a central factory which would return the model for a given ID. But by explicitly passing data to other controllers, we are able to dispose all other data immediately. And we don’t have to rely on central stores.

molily commented 12 years ago

Closing this, but we should include this description in the Controller documentation.