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
|
var fetchTemplateData = require('okutil').fetchTemplateData;
var OKResource = require('okresource');
// Routes for views over collections have a special pattern
// containing a free variable e.g. :id
var UNBOUND_ROUTE_PATTERN = /:([a-zA-Z\-_]+)/;
/**
* OKView!
* Is supplied DB queries and a template and is responsible
* for resolving the queries, throwing the data into the templates,
* and sending the response.
*/
function OKView(options) {
if (!(this instanceof OKView)) return new OKView(options);
options = options || {};
if (!options.template) throw new Error('No template provided to view.');
if (!options.meta) throw new Error('No meta resource provided to view');
if (!options.route) throw new Error('No route provided to view');
this.route = options.route;
this._template = options.template;
this._meta = options.meta;
this._queries = options.queries || [];
// Whether this is a view for a specific resource or its
// resource will be resolved later
this.unbound = !!UNBOUND_ROUTE_PATTERN.exec(this.route);
this._middleware = createMiddleware(this);
}
OKView.prototype.middleware = function() {
return this._middleware;
};
OKView.prototype.render = function(req, res, data) {
this._template.render(data).then(function(html) {
res.send(html);
}, errorHandler(req, res, data));
};
OKView.prototype.getTemplateData = function(options) {
return fetchTemplateData(this._meta, this._queries, options);
};
/**
* Unbound views need different middleware to resolve requests
*/
function createMiddleware(view) {
if (view.unbound) {
return collectionMiddleware(view);
} else {
return singleMiddleware(view);
}
}
// Note that these middleware do not call next
// and should thus always be added at the end of the
// middleware chain.
/**
* Creates middleware for a view which does not
* yet have a resource id associated with it
*/
function collectionMiddleware(view) {
var paramName = getParamName(view.route);
return function(req, res, next) {
view.getTemplateData({
id: req.params[paramName]
}).then(function(data) {
view.render(req, res, data);
}, errorHandler(req, res, next));
};
}
/**
* Creates middleware for a view which already
* has a resource id associated with it
*/
function singleMiddleware(view) {
return function(req, res, next) {
view.getTemplateData().then(function(data) {
view.render(req, res, data);
}, errorHandler(req, res, next));
};
}
/**
* TODO BS error handling for now
*/
function errorHandler(req, res, next) {
return function(err) {
res.send(err.stack);
}
}
/**
* Given a route with a free variable, return the
* name of the variable, e.g. :id returns id
*/
function getParamName(route) {
route = route || '';
var matches = UNBOUND_ROUTE_PATTERN.exec(route) || [];
return matches[1];
}
module.exports = OKView;
|