router.js revision 8868b979badbb9aeed3c32f3fb02eafead9aedfe
a75ebc38c1de401b679953a9b87bd323f0f48d02TrippProvides URL-based routing using HTML5 `pushState()` or the location hash.
f69d245bb21be88752420e834a6b6be37e9b525fTripp@submodule router
f69d245bb21be88752420e834a6b6be37e9b525fTripp@since 3.4.0
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp origin = location.origin || (location.protocol + '//' + location.host),
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // We have to queue up pushState calls to avoid race conditions, since the
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // popstate event doesn't actually provide any info on what URL it's
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // associated with.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Fired when the router is ready to begin dispatching to route handlers.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp You shouldn't need to wait for this event unless you plan to implement some
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp kind of custom dispatching logic. It's used internally in order to avoid
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp dispatching to an initial route if a browser history change occurs first.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp @event ready
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp @param {Boolean} dispatched `true` if routes have already been dispatched
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp (most likely due to a history change).
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTrippProvides URL-based routing using HTML5 `pushState()` or the location hash.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTrippThis makes it easy to wire up route handlers for different application states
a12380a54ea6e3ec3f16a090eee8ec5bf93aed83Trippwhile providing full back/forward navigation support and bookmarkable, shareable
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp@class Router
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp@param {Object} [config] Config properties.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp @param {Boolean} [config.html5] Overrides the default capability detection
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp and forces this router to use (`true`) or not use (`false`) HTML5
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp @param {String} [config.root=''] Root path from which all routes should be
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp @param {Array} [config.routes=[]] Array of route definition objects.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp@constructor
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp@extends Base
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp@since 3.4.0
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTrippfunction Router() {
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp // -- Protected Properties -------------------------------------------------
422668e1d4513bb870b8b576fd9d828c8872f074Tripp Whether or not `_dispatch()` has been called since this router was
422668e1d4513bb870b8b576fd9d828c8872f074Tripp instantiated.
8209f3939e32e0e5bde64192267fdaf9db6f4fbcTripp @property _dispatched
422668e1d4513bb870b8b576fd9d828c8872f074Tripp @type Boolean
422668e1d4513bb870b8b576fd9d828c8872f074Tripp @default undefined
422668e1d4513bb870b8b576fd9d828c8872f074Tripp Whether or not we're currently in the process of dispatching to routes.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @property _dispatching
422668e1d4513bb870b8b576fd9d828c8872f074Tripp @type Boolean
422668e1d4513bb870b8b576fd9d828c8872f074Tripp @default undefined
422668e1d4513bb870b8b576fd9d828c8872f074Tripp Cached copy of the `html5` attribute for internal use.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp @property _html5
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @type Boolean
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp Whether or not the `ready` event has fired yet.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp @property _ready
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @type Boolean
422668e1d4513bb870b8b576fd9d828c8872f074Tripp @default undefined
422668e1d4513bb870b8b576fd9d828c8872f074Tripp Regex used to match parameter placeholders in route paths.
422668e1d4513bb870b8b576fd9d828c8872f074Tripp Subpattern captures:
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp 1. Parameter prefix character. Either a `:` for subpath parameters that
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp should only match a single level of a path, or `*` for splat parameters
422668e1d4513bb870b8b576fd9d828c8872f074Tripp that should match any number of path levels.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp 2. Parameter name.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @property _regexPathParam
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @type RegExp
422668e1d4513bb870b8b576fd9d828c8872f074Tripp _regexPathParam: /([:*])([\w\-]+)/g,
422668e1d4513bb870b8b576fd9d828c8872f074Tripp Regex that matches and captures the query portion of a URL, minus the
422668e1d4513bb870b8b576fd9d828c8872f074Tripp preceding `?` character, and discarding the hash portion of the URL if any.
422668e1d4513bb870b8b576fd9d828c8872f074Tripp @property _regexUrlQuery
422668e1d4513bb870b8b576fd9d828c8872f074Tripp @type RegExp
422668e1d4513bb870b8b576fd9d828c8872f074Tripp _regexUrlQuery: /\?([^#]*).*$/,
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Regex that matches everything before the path portion of a URL (the origin).
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp This will be used to strip this part of the URL from a string when we
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp only want the path.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @property _regexUrlOrigin
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @type RegExp
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp _regexUrlOrigin: /^(?:[^\/#?:]+:\/\/|\/\/)[^\/]*/,
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp // -- Lifecycle Methods ----------------------------------------------------
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp var self = this;
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp // Necessary because setters don't run on init.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp this._setRoutes(config && config.routes ? config.routes :
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // Set up a history instance or hashchange listener.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Y.after('history:change', self._afterHistoryChange, self);
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp Y.on('hashchange', self._afterHistoryChange, win, self);
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // Fire a 'ready' event once we're ready to route. We wait first for all
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp // subclass initializers to finish, then for window.onload, and then an
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp // additional 20ms to allow the browser to fire a useless initial
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // `popstate` event if it wants to (and Chrome always wants to).
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp setTimeout(function () {
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp self.fire(EVT_READY, {dispatched: !!self._dispatched});
422668e1d4513bb870b8b576fd9d828c8872f074Tripp destructor: function () {
422668e1d4513bb870b8b576fd9d828c8872f074Tripp if (this._html5) {
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Y.detach('history:change', this._afterHistoryChange, this);
422668e1d4513bb870b8b576fd9d828c8872f074Tripp Y.detach('hashchange', this._afterHistoryChange, win);
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // -- Public Methods -------------------------------------------------------
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Dispatches to the first route handler that matches the current URL, if any.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp If `dispatch()` is called before the `ready` event has fired, it will
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp automatically wait for the `ready` event before dispatching. Otherwise it
422668e1d4513bb870b8b576fd9d828c8872f074Tripp will dispatch immediately.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @method dispatch
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp dispatch: function () {
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp this._ready = true;
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp return this;
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp Gets the current route path, relative to the `root` (if any).
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp @method getPath
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @return {String} Current route path.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp getPath: function () {
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp return this._getPath();
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Returns `true` if this router has at least one route that matches the
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp specified URL, `false` otherwise.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp This method enforces the same-origin security constraint on the specified
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp `url`; any URL which is not from the same origin as the current URL will
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp always return `false`.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @method hasRoute
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @param {String} url URL to match.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @return {Boolean} `true` if there's at least one matching route, `false`
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp return false;
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Returns an array of route objects that match the specified URL path.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp This method is called internally to determine which routes match the current
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp path whenever the URL changes. You may override it if you want to customize
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp the route matching logic, although this usually shouldn't be necessary.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp Each returned route object has the following properties:
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp * `callback`: A function or a string representing the name of a function
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp this router that should be executed when the route is triggered.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp * `keys`: An array of strings representing the named parameters defined in
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp the route's path specification, if any.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp * `path`: The route's path specification, which may be either a string or
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp * `regex`: A regular expression version of the route's path specification.
cc21b565833307c2b0b06deb4e3ab22c2a94be3eTripp This regex is used to determine whether the route matches a given path.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp router.route('/foo', function () {});
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp router.match('/foo');
8209f3939e32e0e5bde64192267fdaf9db6f4fbcTripp // => [{callback: ..., keys: [], path: '/foo', regex: ...}]
8209f3939e32e0e5bde64192267fdaf9db6f4fbcTripp @method match
4288e1894e685f74123435d45db06e5cef146e7fTripp @param {String} path URL path to match.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @return {Object[]} Array of route objects that match the specified path.
422668e1d4513bb870b8b576fd9d828c8872f074Tripp Removes the `root` URL from the front of _url_ (if it's there) and returns
f69d245bb21be88752420e834a6b6be37e9b525fTripp the result. The returned path will always have a leading `/`.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @method removeRoot
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @param {String} url URL.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @return {String} Rootless path.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // Strip out the non-path part of the URL, if any (e.g.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // "http://foo.com"), so that we're left with just the path.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Replaces the current browser history entry with a new one, and dispatches to
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp the first matching route handler, if any.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Behind the scenes, this method uses HTML5 `pushState()` in browsers that
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp support it (or the location hash in older browsers and IE) to change the
422668e1d4513bb870b8b576fd9d828c8872f074Tripp The specified URL must share the same origin (i.e., protocol, host, and
422668e1d4513bb870b8b576fd9d828c8872f074Tripp port) as the current page, or an error will occur.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp // Starting URL: http://example.com/
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp router.replace('/path/');
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp router.replace('/path?foo=bar');
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp router.replace('/');
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @method replace
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @param {String} [url] URL to set. This URL needs to be of the same origin as
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp the current URL. This can be a URL relative to the router's `root`
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp attribute. If no URL is specified, the page's current URL will be used.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @see save()
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Adds a route handler for the specified URL _path_.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp The _path_ parameter may be either a string or a regular expression. If it's
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp a string, it may contain named parameters: `:param` will match any single
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp part of a URL path (not including `/` characters), and `*param` will match
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp any number of parts of a URL path (including `/` characters). These named
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp parameters will be made available as keys on the `req.params` object that's
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp passed to route handlers.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp If the _path_ parameter is a regex, all pattern matches will be made
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp available as numbered keys on `req.params`, starting with `0` for the full
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp match, then `1` for the first subpattern match, and so on.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp Here's a set of sample routes along with URL paths that they match:
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp * Route: `/photos/:tag/:page`
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp * URL: `/photos/kittens/1`, params: `{tag: 'kittens', page: '1'}`
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp * URL: `/photos/puppies/2`, params: `{tag: 'puppies', page: '2'}`
f69d245bb21be88752420e834a6b6be37e9b525fTripp * Route: `/file/*path`
8209f3939e32e0e5bde64192267fdaf9db6f4fbcTripp * URL: `/file/foo/bar/baz.txt`, params: `{path: 'foo/bar/baz.txt'}`
8209f3939e32e0e5bde64192267fdaf9db6f4fbcTripp * URL: `/file/foo`, params: `{path: 'foo'}`
8209f3939e32e0e5bde64192267fdaf9db6f4fbcTripp If multiple route handlers match a given URL, they will be executed in the
f69d245bb21be88752420e834a6b6be37e9b525fTripp order they were added. The first route that was added will be the first to
f69d245bb21be88752420e834a6b6be37e9b525fTripp be executed.
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp router.route('/photos/:tag/:page', function (req, res, next) {
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @method route
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp @param {String|RegExp} path Path to match. May be a string or a regular
e393eced613f9b4a5fb6bdd461d0e0bf5064d5ecTripp expression.
var keys = [];
// Starting URL: http://example.com/
// New URL: http://example.com/path/
// New URL: http://example.com/path?foo=bar
// New URL: http://example.com/
upgrade: function () {
if (!this._html5) {
_dequeue: function () {
var self = this,
fn;
var self = this,
return self;
if (err) {
_getHashPath: function () {
_getOrigin: function () {
return origin;
_getPath: function () {
_getQuery: function () {
if (this._html5) {
if (path instanceof RegExp) {
return path;
var res = function () {
return res;
_getRoutes: function () {
_getURL: function () {
result = {},
for (; i < len; ++i) {
return result;
_queue: function () {
self = this;
setTimeout(function () {
return self;
this._ready = true;
if (this._html5) {
this._routes = [];
_afterHistoryChange: function (e) {
var self = this,
_defReadyFn: function (e) {
this._ready = true;
ATTRS: {
html5: {
`http://example.com/myapp/` and you add a route with the path `/`, your
when the user browses to `http://example.com/myapp/`.
root: {
routes: {
value : [],