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
|
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;
var meta = this._meta = options.meta;
var queries = this._queries = options.queries || [];
// Whether this is a view for a specific resource or its
// resource will be resolved later
// TODO This bound / unbound thing can probably be expressed in a
// less convoluted way.
var unbound = this.unbound = !!UNBOUND_ROUTE_PATTERN.exec(this.route);
this._middleware = createMiddleware(this);
this._fetchTemplateData = unbound ?
fetchResourceTemplateData : fetchCollectionTemplateData;
function fetchResourceTemplateData(id) {
// Bound views only have a single query
// TODO This is super convoluted
return fetchTemplateData(meta, [queries[0].get(id)]);
}
function fetchCollectionTemplateData() {
return fetchTemplateData(meta, queries);
}
}
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.fetchTemplateData = function() {
return this._fetchTemplateData.apply(this, arguments);
};
/**
* Unbound views need different middleware to resolve requests
*/
function createMiddleware(view) {
if (view.unbound) {
return unboundMiddleware(view);
} else {
return boundMiddleware(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 unboundMiddleware(view) {
var paramName = getParamName(view.route);
return function(req, res, next) {
var id = req.params[paramName];
view.fetchTemplateData(id).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 boundMiddleware(view) {
return function(req, res, next) {
view.fetchTemplateData().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;
|