Backbone.Manager is a state-based routing/control manager for Backbone. It removes direct dependency on the router, and instead provides a standard control mechanism for url updates and state-change handling. It can be used for large state changes that involve url updates and moving between major view controllers, or for small state changes to do things like flash div content.
- Intuitive state change
- Differentiate between pageload and triggered changes
- Remove temptation of view<->router relationships
- Automatic state change from clicked anchors
- Programmatic state change ability
This:
UsersRouter = Backbone.Router.extend
routes:
'users/:id': 'showUser'
initialize: (options) ->
_.bindAll @, 'switchToUser'
@listenTo Backbone, 'showUser', @switchToUser
is now organized into this:
UsersManager = Backbone.Manager.extend
states:
'users.detail':
url: 'users/:id'
loadMethod: 'showUser'
transitionMethod: 'switchToUser'
A Backbone.Manager instance is created by providing a router (required) to the constructor: new Backbone.Manager(router)
. If you're creating multiple Manager instances, it's recommended to just share a single instance of a router between them.
####Example
UsersManager = Backbone.Manager.extend
states:
users:
url: 'users'
loadMethod: 'showUsers'
transitionMethod: 'switchToUsers'
'users.detail'
url: 'users/:id'
loadMethod: 'showUser'
transitionMethod: 'switchToUser'
events:
'loadSuccess:users.detail': 'prepareUser'
'transitionStart': 'logToAnalytics'
initialize: ->
# ...
showUsers: ->
# ...
switchToUsers: (searchString, options) ->
# ...
showUser: (id) ->
# ...
switchToUser: (id, searchString, options) ->
# ...
prepareUser: (id) ->
# ...
logToAnalytics: ->
# ...
The states
definition is the foundation of the Manager. It consists of state names paired with definitions for that state. States basically fall into one of two categories:
Which category a state falls under is controlled by the state being provided with an url definition:
states:
urlState:
url: '/states/:id'
# etc
nonUrlState:
# etc
These are able to be triggered via:
- Initial Pageload
- Window.popstate of '/users/1'
Backbone.Manager.go('users.detail',[1])
Backbone.Manager.goByUrl('/users/1')
- Direct
data-bb-state
trigger:<a data-bb-state="users.detail([1])">
- Inferred
data-bb-state
trigger:<a data-bb-state href="/users/1">
These are able to be triggered via:
- Programmatic:
Backbone.Manager.go('users.detail',[1])
data-bb-state
definition:<a data-bb-state="users.detail([1])">
states:
'users.detail':
url: 'users/:id'
loadMethod: 'callback' # String representing method name for callback
Callback used immediately upon load of the page, when the page url matches defined url (User navigates directly). Url must be defined to activate.
# Arguments are built from url params, passed straight from the Router
callback: (id, searchString) ->
Callback used when any non-loadMethod related state change occurs. Functionality can be affected by passing transitionOptions.
states:
'users.detail.books.detail':
url: 'users/:a/books/:b'
transitionMethod: 'callback' # String representing method name for callback
Before the transitionMethod
is triggered, A router.navigate
will occur with the state's url. Note: currently anything inside of and including the optional matcher (()
's) in the state url are removed first.
Callback method takes the params in order from the url, then provides the searchString (provided because of the router, usually null), and finally an options object containing the populated url. So:
trigger | callback method |
---|---|
Backbone.Manager.go('users.detail.books.detail',[1,2]) |
callback(1,2,null,{url: 'users/1/books/2'}) |
Backbone.Manager.go('users.detail.books.detail',{b:2,a:1}) (args order not important) |
callback(1,2,null,{url: 'users/1/books/2'}) |
<a data-bb-state="users.detail.books.detail([1,2])"> |
callback(1,2,null,{url: 'users/1/books/2'}) |
<a data-bb-state="users.detail.books.detail({b:2,a:1})"> (args order not important) |
callback(1,2,null,{url: 'users/1/books/2'}) |
Backbone.Manager.goByUrl('/users/1/books/2') |
callback(1,2,null,{url: 'users/1/books/2'}) |
<a data-bb-state href="/users/1/books/2"> |
callback(1,2,null,{url: 'users/1/books/2'}) |
states:
'users.detail.books.detail':
transitionMethod: 'callback' # String representing method name for callback
Callback method takes the params in order as passed. Order is important, even when an object is used for the args. So:
trigger | callback method |
---|---|
Backbone.Manager.go('users.detail.books.detail',[1,2]) |
callback(1,2) |
Backbone.Manager.go('users.detail.books.detail',{b:2,a:1}) (args order important) |
callback(2,1) values taken in order |
<a data-bb-state="users.detail.books.detail([1,2])"> |
callback(1,2) |
<a data-bb-state="users.detail.books.detail({b:2,a:1})"> (args order important) |
callback(2,1) values taken in order |
Functionality of how the transition will occur can be controlled by passing in transitionOptions. These options can be passed directly using go
or goByUrl
, or also by setting data-bb-options on an anchor.
Currently supported options:
name | description |
---|---|
navigate | (boolean, default: true) If set to false, will not update the url when the transition is occurs, even if the url is provided for the state. |
The '*'
is reserved as a final matcher for states. When the data-bb-state
watcher attempts to perform a state transition for a state that hasn't been defined, it will fallback to a '*'
state definition. Here is an example of how to use it:
Backbone.Manager.extend
states:
'*':
url: '*url'
transitionMethod: 'defaultTransition'
defaultTransition: (url) ->
# ...
Important: If this is declared, it should be done within the very first manager created, and as the very first state definition. This is so that it ends up being placed in the bottom of the handlers stack within Backbone.History. When a Manager is created, Backbone.Manager inserts each url handler into the shared router as it progresses through the States definition... from the top down. The Router then works from the top down in its handlers when it's searching for a match. This is a Backbone.Router limitation.
Backbone.Manager will trigger state specific and general events as the transition and load methods are being processed. These are async calls, so the callbacks aren't guaranteed to have completed before the post
events are triggered. Here are the following events that are triggered:
event | description |
---|---|
loadStart | incoming page load call for any state |
loadStart:[state] (args) | incoming page load call for the [state] (args) are the url params provided by the router |
loadSuccess | loadMethod callback completed |
loadSuccess:[state] (args) | loadMethod callback completed for the [state] (args) are the url params provided by the router |
loadError (error) | loadMethod callback failed |
loadError:[state] (args, error) | loadMethod callback failed for the [state] (args) are the url params provided by the router (error) is the thrown error |
transitionStart | incoming transition call for any state |
transitionStart:[state] | incoming transition call for the [state] |
transitionSuccess | transitionMethod callback completed |
transitionSuccess:[state] | transitionMethod callback completed for the [state] |
transitionError (error) | transitionMethod callback failed (error) is the thrown error |
transitionError:[state] (error) | transitionMethod callback failed for the [state] (error) is the thrown error |
exit | state is transitioning out of the current Manager, into a different one |
navigate | Router's navigate function has been triggered and the url has changed |
There are four different ways to trigger a state change within Backbone.Manager:
Triggered immediately upon load of the page, when the page url matches defined url (User navigates directly). Url must be defined to activate. This will trigger the loadMethod associated with the url.
Typically occurs when the user uses the back button. This will trigger the transitionMethod associated with the url.
The programmatic way of triggering state changes. Example Usage:
events:
'click dd': 'showUser'
showUser: ->
Backbone.Manager.go('users.detail', {id:1}, {navigate: true})
params:
- stateName
- args: [] or {}
- see transitionMethod for details on what happens with the args
- (optional) transitionOptions (Object containing the options defined in transitionOptions)
The programmatic way of triggering state changes via url matching. Example Usage:
events:
'click dd': 'showUser'
showUser: ->
Backbone.Manager.goByUrl('/users/1', {navigate: true})
params:
- url (tested against url matchers defined in states, will use * state if none are found)
- (optional) transitionOptions (Object containing the options defined in transitionOptions)
The data-bb-state
attribute is watched for by Backbone.Manager on all anchor tag clicks that bubble up to document. If event propagation is disabled or preventDefault gets set on that event, then Backbone.Manager will not trigger.
Example Usages:
<a data-bb-state='users.detail([1])'/>
<a data-bb-state='users.detail({id:1})'/>
<a data-bb-state href='/users/1'/>
The format for the data-bb-state
value is 'statename([args]or{args})'
, where the args are passed to the callback as described in transitionMethod.
The first two examples are explicit state calls, but the third uses the url to infer the state (it actually calls goByUrl
). The explicit triggers in the examples above will trigger the users.detail.transitionMethod
callback. To use the inferred trigger, data-bb-state
must be defined on the anchor and it must have an href
url defined.
You can add the data-bb-options
attribute to your anchor to allow passing of the transitionOptions in the form of valid JSON.
Example Usage:
<a data-bb-state='users.detail([1])' data-bb-options='{"navigate": false}'/>
##Contributors
- PR's should only contain changes to .coffee files, the release js will be built later
- Run
gulp
to autocompile coffeescript (both src and test/src) into /out for testing - Open
test/test-runner.html
to run the in-browser test suite, or runnpm test
for headless.
- Added new navigate event when a
router.navigate
is performed
- Bugfix:
goByUrl
still usesgo
and gained a null in the params
- Catch up deps
- Bugfix: When calling
go
with an array of params, an odd search string gets appended to the url
- Breaking: Add [transition/load] Success and Error events. Rename old bare events to [transition/load]Start
- Bugfix: Transition is matching urls in a different priority than load
- Bugfix: queryParams defined in state.url are dropped from the _urlAsTemplate
- Bugfix: queryParams are getting dropped when transition navigate occurs
- Add transitionOptions to
go
andgoByUrl
... supportsnavigate
currently to allow pushState bypass - Support
data-bb-options
attribute to specify transitionOptions from anchor tags
- Bugfix:
go
still requires args of some sort even if url contains no params - Bugfix:
go
breaks in ie8 if no arguments are provided
- Bugfix:
goByUrl
dies if there is a state that doesn't have a url defined
- Bugfix: Managers in wrong order after
goByUrl
runs
- Bugfix: Managers are being traversed in the wrong order for
goByUrl
- Breaking: Move from conventional
<a href>
-to-state translation to direct url matching. This means that old conventionaldata-bb-state
triggers for states without url definitions will no longer work. - Added
Backbone.Manager.goByUrl
to backdata-bb-state
href functionality
- Breaking: Removed pre/post events... there was no guarantee of pre since it was async
- Bugfix: Param matcher isn't excluding
(
- Bugfix: Use currentTarget instead of target for anchor state changes - @jmcnevin