summaryrefslogtreecommitdiff
path: root/app/node_modules/okview/index.js
blob: 5f3c65c787d1d811f556bceb67f5038a3eff0b32 (plain)
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;