-
Notifications
You must be signed in to change notification settings - Fork 16
/
ampersand-router.js
126 lines (111 loc) · 4.54 KB
/
ampersand-router.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/*$AMPERSAND_VERSION*/
var classExtend = require('ampersand-class-extend');
var Events = require('ampersand-events');
var extend = require('lodash/assign');
var isRegExp = require('lodash/isRegExp');
var isFunction = require('lodash/isFunction');
var result = require('lodash/result');
var ampHistory = require('./ampersand-history');
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
var Router = module.exports = function (options) {
options || (options = {});
this.history = options.history || ampHistory;
if (options.routes) this.routes = options.routes;
this._bindRoutes();
this.initialize.apply(this, arguments);
};
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var optionalParam = /\((.*?)\)/g;
var namedParam = /(\(\?)?:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods.
extend(Router.prototype, Events, {
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function () {},
// Manually bind a single named route to a callback. For example:
//
// this.route('search/:query/p:num', 'search', function (query, num) {
// ...
// });
//
route: function (route, name, callback) {
if (!isRegExp(route)) route = this._routeToRegExp(route);
if (isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
var router = this;
this.history.route(route, function (fragment) {
var args = router._extractParameters(route, fragment);
if (router.execute(callback, args, name) !== false) {
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
router.history.trigger('route', router, name, args);
}
});
return this;
},
// Execute a route handler with the provided parameters. This is an
// excellent place to do pre-route setup or post-route cleanup.
execute: function (callback, args, name) {
if (callback) callback.apply(this, args);
},
// Simple proxy to `ampHistory` to save a fragment into the history.
navigate: function (fragment, options) {
this.history.navigate(fragment, options);
return this;
},
// Reload the current route as if it was navigated to from somewhere
// else
reload: function () {
this.history.loadUrl(this.history.fragment);
return this;
},
// Helper for doing `internal` redirects without adding to history
// and thereby breaking backbutton functionality.
redirectTo: function (newUrl) {
this.navigate(newUrl, {replace: true});
},
// Bind all defined routes to `history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
_bindRoutes: function () {
if (!this.routes) return;
this.routes = result(this, 'routes');
var route, routes = Object.keys(this.routes);
while ((route = routes.pop()) != null) {
this.route(route, this.routes[route]);
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp: function (route) {
route = route
.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, function (match, optional) {
return optional ? match : '([^/?]+)';
})
.replace(splatParam, '([^?]*?)');
return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted decoded parameters. Empty or unmatched parameters will be
// treated as `null` to normalize cross-browser behavior.
_extractParameters: function (route, fragment) {
var encodedParams = route.exec(fragment).slice(1);
var searchParm = encodedParams.pop();
function decodeOrNull(p) {
return p ? decodeURIComponent(p) : null;
}
var params = encodedParams.map(decodeOrNull);
searchParm && params.push(searchParm);
return params;
}
});
Router.extend = classExtend;