Router Creation and Configuration¶
A router
(as it is referred to in this sub-section) is a more formal expression of a standard view model. It allows you to hook into the various lifecycle events Footwork provides as well as enabling other capabilities.
In its essense a router
can be thought of as a container that houses the logic and data your template/HTML/DOM binds to. They are used to 'wrap' your HTML/markup, having the bindings (behavior) you declare in the HTML applied to the logic you define in the router
.
Creation¶
A router
is a view model object that has been bootstrapped with fw.router.boot()
by passing it the instance along with an optional configuration:
function MyRouter () { var self = fw.router.boot(this, { // see Configuration below }); self.someValue = fw.observable(); }
In the code above you see the instance bootstrapped as a router
, the boot method will return a reference to the instance.
Configuration¶
When you call fw.router.boot()
you pass it two parameters, the view model instance/object and an optional configuration object. The configuration for a router
has the following options:
function MyRouter () { // For convenience the boot method returns a reference to the instance. var self = fw.router.boot(this, { namespace: /* see below */, afterRender: /* see below */, afterResolve: /* see below */, onDispose: /* see below */, sequence: /* see below */, baseRoute: /* see below */, activate: /* see below */, predicate: /* see below */, outlet: /* see below */ }); }
All of these options, are optional...you only have to provide the values required for your applications needs.
- namespace (string)
- afterRender (callback)
- afterResolve (callback)
- onDispose (callback)
- sequence (integer | callback)
- routes (array)
- baseRoute (string)
- activate (boolean)
- predicate (callback)
- outlet (object)
Callback Context
All callback functions execute with the context of the router
instance.
Each of these options and their use is described below:
namespace (string)¶
Footwork provides an easy way to logically separate your modules using string-based identifiers. This configuration value is used to set the router
namespace:
namespace: 'MyRouter'
Once your router
is bootstrapped its namespace is made available as $namespace
on the object instance:
function MyRouter () { fw.router.boot(this, { namespace: 'MyRouter' }); this.$namespace.subscribe('someEvent', function () { // do something... }); }
Namespaces in Footwork are a mechanism used to help keep your application loosely coupled yet highly cohesive. Among other things, namespacing provides hooks for pub/sub communication, as well as broadcastables / receivables. For more information see namespaces.
afterRender (callback)¶
This callback is triggered after binding and rendering the router
with the DOM (but before afterResolve()
and also before any nested elements are bound/resolved).
It is passed one parameter, the parent DOM element the router
is bound against.
afterRender: function (element) { console.info('My element is', element); }
Prospectively you might use this as a way to startup various 3rd party plugins such as those that work with jQuery.
afterResolve (callback)¶
This callback is triggered after binding the router
with the DOM and all nested components/etc have been fully resolved as well.
If you are animating a router
into place then that will only occur once the instance has been resolved. Note that the resolution of your router
only affects when it is animated into place, it does not affect when it is bound or rendered into the DOM (that occurs as soon as possible). If you are not animating your instances into place then you do not need to worry about when it is resolved. For information on how to use the animation features, see animating routers.
The afterResolve
callback is passed one parameter, a resolve function. Using the resolve() function you tell Footwork when your instance has been resolved. It can be called in three different ways to specify resolution, depending on your needs:
-
You can call it nothing to mark the current instance as resolved immediately:
afterResolve: function (resolve) { resolve(); // now marked as resolved }
-
You can pass it a promise which Footwork then waits to be fulfulled or rejected:
afterResolve: function (resolve) { var myRequest = fetch(/* ... */); resolve(myRequest); // marked resolved once myRequest resolves/rejects }
-
You can pass it an array of promises which Footwork then waits to be fulfulled or rejected:
afterResolve: function (resolve) { var requests = [ fetch(/* ... */), fetch(/* ... */) ]; resolve(requests); // marked resolved once all of the requests resolve/reject }
-
The resolve callback itself returns a promise which resolves once all promises you pass in have resolved:
afterResolve: function (resolve) { var requests = [ fetch(/* ... */), fetch(/* ... */) ]; resolve(requests).then(function () { console.info('all requests have completed'); }); }
Once your instance has been marked as resolved (along with any siblings that may exist), its parent is then informed (if there is one) by calling its resolve
callback. This continues up the context chain until there are no more left.
Note
Each instance, once marked resolved, will be animated into place if configured to do so (see: animating routers).
onDispose (callback)¶
This callback is triggered anytime the router
has its dispose()
method called. It is passed one parameter, the parent DOM element the router
is bound against.
onDispose: function (element) { // custom disposal logic }
Just as with afterRender
this might be used as an API hook for 3rd party plugins. You would do whatever custom disposal logic you might need here.
Note
Footwork will trigger dispose()
automatically if:
- The
router
was bound with a component that was removed from the DOM - The element a
router
was bound against was removed from the DOM
sequence (integer | callback)¶
The value provided will cause Footwork to sequence the animations on all router
instantiated with the same namespace as this one. Essentially this means that if you instantiate a bunch of the same routers
then their animations will all be sequenced with a delay between them being the value provided here. This enables you to easily animate in elements in a pleasing way.
Integer Value¶
sequence: 100 // 100 msec between
Callback Function¶
This callback is triggered with each new instance:
sequence: function () { return 100; // 100 msec between }
For more information see animating routers and more specifically sequencing animations.
baseRoute (string)¶
Beginning portion of a url which is excluded when searching for a matching route configuration. This is for use in cases where your application lives at a url which is not at the root of the domain (ie: www.domain.com/sub/folder
vs www.domain.com
).
If you do not specify a baseRoute
when your application is not at the root of its domain, then you would have to prepend that path to each route as well.
baseRoute: '/sub/folder'
routes (array)¶
The routes your application has defined.
routes: [ { route: '/', controller: function () { /* ... */ } }, { route: '/dashboard', controller: function () { /* ... */ } } ]
See the route configuration documentation for full information on how to configure and use routes.
activate (boolean)¶
This flag indicates whether or not the router instance should be activated automatically once it is bound against the DOM. By default this value is true
.
activate: true
If you specify false
then you will have to activate the router explicitly yourself by setting the activated observable property to true
:
router.activated(true);
Router Deactivation
You can disable/deactivate a router at anytime by setting the activated
observable to false:
router.activated(false);
predicate (callback)¶
If provided this callback will evaluated prior to a route change, its result (true/false) will determine whether or not the routing is allowed to take place.
predicate: function (url) { if (url.match(/^\/profile/) && !loggedIn) { return false; } return true; }
Individual Route Predicate
The predicate shown above is called for all routes. Each individual route configuration also has an option for a predicate
callback. If provided, either/both callbacks will be evaluated and both must return true for the route to change.
outlet (object)¶
When performing an outlet change you can pass it several options. This object provides defaults used for those outlet changes at a router level (all outlet changes on that router are supplied them):
outlet: { loading: 'loading-display', transition: 300, onComplete: function (outletElement) { // do something } }
The options you provide here are the same as you would provide directly to an outlet when manipulating it (see: outlet options documentation), however there are a few differences to take into consideration when supplying outlet options on the router itself:
-
You can provide a callback function for the
loading
option.This is to facilitate being able to programmatically provide different loading displays from the router itself. The callback you provide is given the name of the outlet being changed and the name of the component that is loading (its context is the router). The callback should return the name of the component to use as the loading display for the outlet.
An example usage of this callback:
outlet: { loading: function (outletName, displayComponent) { return 'loading-display'; } }
-
You can provide a callback function for the
transition
option.Triggered just like the
loading
option, the callback here is provided the name of the outlet in question as well as thedisplayComponent
that is being transitioned to. You should return the integer value representing the minimum transition time required for the switch:outlet: { transition: function (outletName, displayComponent) { return 300; } }
-
All of these outlet options are overridden by local options provided on an explicit outlet change.
The one exception is the
onComplete
callback, which will be called in addition to anonComplete
callback provided explicitly for any outlet change.