forked from AmpersandJS/ampersand-router
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ampersand-router.js
130 lines (114 loc) · 4.73 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
127
128
129
130
/*$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;
},
// Match a URL fragment with the routes, returning an array of results
// for the route. Return will include all the variables in the route.
match: function(fragment) {
return this.history.match(fragment);
},
// 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 params = route.exec(fragment).slice(1);
return params.map(function (param, i) {
// Don't decode the search params.
if (i === params.length - 1) return param || null;
return param ? decodeURIComponent(param) : null;
});
}
});
Router.extend = classExtend;