-
Notifications
You must be signed in to change notification settings - Fork 412
/
route.js
216 lines (179 loc) · 5.98 KB
/
route.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
var Url = Iron.Url;
var MiddlewareStack = Iron.MiddlewareStack;
var assert = Iron.utils.assert;
/*****************************************************************************/
/* Both */
/*****************************************************************************/
Route = function (path, fn, options) {
var route = function (req, res, next) {
var controller = this;
controller.request = req;
controller.response = res;
route.dispatch(req.url, controller, next);
}
if (typeof fn === 'object') {
options = fn;
fn = options.action;
}
options = options || {};
if (typeof path === 'string' && path.charAt(0) !== '/') {
path = options.path ? options.path : '/' + path
}
// extend the route function with properties from this instance and its
// prototype.
_.extend(route, this.constructor.prototype);
// always good to have options
options = route.options = options || {};
// the main action function as well as any HTTP VERB action functions will go
// onto this stack.
route._actionStack = new MiddlewareStack;
// any before hooks will go onto this stack to make sure they get executed
// before the action stack.
route._beforeStack = new MiddlewareStack;
route._beforeStack.append(route.options.onBeforeAction);
route._beforeStack.append(route.options.before);
// after hooks get run after the action stack
route._afterStack = new MiddlewareStack;
route._afterStack.append(route.options.onAfterAction);
route._afterStack.append(route.options.after);
// track which methods this route uses
route._methods = {};
if (typeof fn === 'string') {
route._actionStack.push(path, _.extend(options, {
template: fn
}));
} else if (typeof fn === 'function' || typeof fn === 'object') {
route._actionStack.push(path, fn, options);
}
route._path = path;
return route;
};
/**
* The name of the route is actually stored on the handler since a route is a
* function that has an unassignable "name" property.
*/
Route.prototype.getName = function () {
return this.handler && this.handler.name;
};
/**
* Returns an appropriate RouteController constructor the this Route.
*
* There are three possibilities:
*
* 1. controller option provided as a string on the route
* 2. a controller in the global namespace with the converted name of the route
* 3. a default RouteController
*
*/
Route.prototype.findControllerConstructor = function () {
var self = this;
var resolve = function (name, opts) {
opts = opts || {};
var C = Iron.utils.resolve(name);
if (!C || !RouteController.prototype.isPrototypeOf(C.prototype)) {
if (opts.supressErrors !== true)
throw new Error("RouteController '" + name + "' is not defined.");
else
return undefined;
} else {
return C;
}
};
var convert = function (name) {
return self.router.toControllerName(name);
};
var result;
var name = this.getName();
// the controller was set directly
if (typeof this.options.controller === 'function')
return this.options.controller;
// was the controller specified precisely by name? then resolve to an actual
// javascript constructor value
else if (typeof this.options.controller === 'string')
return resolve(this.options.controller);
// is there a default route controller configured?
else if (this.router && this.router.options.controller) {
if (typeof this.router.options.controller === 'function')
return this.router.options.controller;
else if (typeof this.router.options.controller === 'string')
return resolve(this.router.options.controller);
}
// otherwise do we have a name? try to convert the name to a controller name
// and resolve it to a value
else if (name && (result = resolve(convert(name), {supressErrors: true})))
return result;
// otherwise just use an anonymous route controller
else
return RouteController;
};
/**
* Create a new controller for the route.
*/
Route.prototype.createController = function (options) {
options = options || {};
var C = this.findControllerConstructor();
options.route = this;
var instance = new C(options);
return instance;
};
Route.prototype.setControllerParams = function (controller, url) {
};
/**
* Dispatch into the route's middleware stack.
*/
Route.prototype.dispatch = function (url, context, done) {
// call runRoute on the controller which will behave similarly to the previous
// version of IR.
assert(context._runRoute, "context doesn't have a _runRoute method");
return context._runRoute(this, url, done);
};
/**
* Returns a relative path for the route.
*/
Route.prototype.path = function (params, options) {
return this.handler.resolve(params, options);
};
/**
* Return a fully qualified url for the route, given a set of parmeters and
* options like hash and query.
*/
Route.prototype.url = function (params, options) {
var path = this.path(params, options);
var host = (options && options.host) || Meteor.absoluteUrl();
if (host.charAt(host.length-1) === '/')
host = host.slice(0, host.length-1);
return host + path;
};
/**
* Return a params object for the route given a path.
*/
Route.prototype.params = function (path) {
return this.handler.params(path);
};
/**
* Add convenience methods for each HTTP verb.
*
* Example:
* var route = router.route('/item')
* .get(function () { })
* .post(function () { })
* .put(function () { })
*/
_.each(HTTP_METHODS, function (method) {
Route.prototype[method] = function (fn) {
// track the method being used for OPTIONS requests.
this._methods[method] = true;
this._actionStack.push(this._path, fn, {
// give each method a unique name so it doesn't clash with the route's
// name in the action stack
name: this.getName() + '_' + method.toLowerCase(),
method: method,
// for now just make the handler where the same as the route, presumably a
// server route.
where: this.handler.where,
mount: false
});
return this;
};
});
Iron.Route = Route;