Coverage

100%
139
139
0

render.js

100%
95
95
0
LineHitsSource
1/*!
2 * connect-render
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
7/**
8 * Module dependencies.
9 */
10
113var fs = require('fs');
123var path = require('path');
133var http = require('http');
143var ejs = require('ejs');
153var filters = require('./filters');
16
173var settings = {
18 root: __dirname + '/views',
19 cache: true,
20 layout: 'layout.html',
21 viewExt: '', // view default extname
22 _filters: {},
23};
24
253for (var k in filters) {
2612 settings._filters[k] = filters[k];
27}
28
293var cache = {};
30
313function _render_tpl(fn, options, callback) {
3225 var str;
3325 try {
3425 str = fn.call(options.scope, options);
35 } catch (err) {
364 return callback(err);
37 }
3821 callback(null, str);
39}
40
413var reg_meta = /[\\\^$*+?{}.()|\[\]]/g;
423var open = ejs.open || "<%";
433var close = ejs.close || "%>";
443var PARTIAL_PATTERN_RE = new RegExp(open.replace(reg_meta, "\\$&") +
45 "[-=]\\s*partial\\((.+)\\)\\s*" + close.replace(reg_meta, "\\$&"), 'g');
46/**
47 * add support for <%- partial('view') %> function
48 * rather than realtime compiling, this implemention simply statically 'include' the partial view file
49 *
50 * @param {String} data
51 * @param {String} [viewname] view name for partial loop check.
52 * @return {String}
53 */
543function partial(data, viewname) {
5522 return data.replace(PARTIAL_PATTERN_RE, function (all, view) {
5610 view = view.match(/['"](.*)['"]/); // get the view name
5710 if (!view || view[1] === viewname) {
582 return "";
59 } else {
608 var name = view[1];
618 if (settings.viewExt) {
624 name += settings.viewExt;
63 }
648 var viewpath = path.join(settings.root, name);
658 var tpl = '';
668 try {
678 tpl = fs.readFileSync(viewpath, 'utf8');
68 } catch (e) {
692 console.error("[%s][connect-render] Error: cannot load view partial %s\n%s", new Date(), viewpath, e.stack);
702 return "";
71 }
726 return partial(tpl, view[1]);
73 }
74 });
75}
76
773function _render(view, options, callback) {
7827 if (settings.viewExt) {
7912 view += settings.viewExt;
80 }
8127 var viewpath = path.join(settings.root, view);
8227 var fn = settings.cache && cache[view];
8327 if (fn) {
849 return _render_tpl(fn, options, callback);
85 }
86 // read template data from view file
8718 fs.readFile(viewpath, 'utf8', function (err, data) {
8818 if (err) {
892 return callback(err);
90 }
9116 var tpl = partial(data);
9216 fn = ejs.compile(tpl, {filename: view});
9316 if (settings.cache) {
9416 cache[view] = fn;
95 }
9616 _render_tpl(fn, options, callback);
97 });
98}
99
1003function send(res, str) {
10115 var buf = new Buffer(str);
10215 res.charset = res.charset || 'utf-8';
10315 res.setHeader('Content-Type', 'text/html');
10415 res.setHeader('Content-Length', buf.length);
10515 res.end(buf);
106}
107
108/**
109 * Render the view fill with options
110 *
111 * @param {String} view, view name.
112 * @param {Object} [options=null]
113 * - {Boolean} layout, use layout or not, default is `true`.
114 * @return {HttpServerResponse} this
115 */
1163function render(view, options) {
11721 var self = this;
11821 options = options || {};
119
12021 for (var name in settings._filters) {
12184 options[name] = settings._filters[name];
122 }
123
12421 if (settings.helpers) {
12521 for (var k in settings.helpers) {
12675 var helper = settings.helpers[k];
12775 if (typeof helper === 'function') {
12821 helper = helper(self.req, self);
129 }
13075 if (!options.hasOwnProperty(k)) {
13174 options[k] = helper;
132 }
133 }
134 }
135
13621 if (settings.filters) {
13712 for (var name in settings.filters) {
13812 options[name] = settings.filters[name];
139 }
140 }
141
142 // add request to options
14321 if (!options.request) {
14421 options.request = self.req;
145 }
146 // render view template
14721 _render(view, options, function (err, str) {
14821 if (err) {
1494 return self.req.next(err);
150 }
15117 var layout = typeof options.layout === 'string' ? options.layout : settings.layout;
15217 if (options.layout === false || !layout) {
15311 return send(self, str);
154 }
155 // render layout template, add view str to layout's locals.body;
1566 options.body = str;
1576 _render(layout, options, function (err, str) {
1586 if (err) {
1592 return self.req.next(err);
160 }
1614 send(self, str);
162 });
163 });
16421 return this;
165}
166
167/**
168 * connect-render: Template Render helper for connect
169 *
170 * Use case:
171 *
172 * var render = require('connect-render');
173 * var connect = require('connect');
174 *
175 * connect(
176 * render({
177 * root: __dirname + '/views',
178 * cache: true, // must set `true` in production env
179 * layout: 'layout.html', // or false for no layout
180 * helpers: {
181 * config: config,
182 * sitename: 'NodeBlog Engine',
183 * _csrf: function (req, res) {
184 * return req.session ? req.session._csrf : "";
185 * },
186 * }
187 * });
188 * );
189 *
190 * res.render('index.html', { title: 'Index Page', items: items });
191 *
192 * // no layout
193 * res.render('blue.html', { items: items, layout: false });
194 *
195 * @param {Object} [options={}] render options.
196 * - {String} layout, layout name, default is `'layout.html'`.
197 * Set `layout=''` or `layout=false` meaning no layout.
198 * - {String} root, view files root dir.
199 * - {Boolean} cache, cache view content or not, default is `true`.
200 * Must set `cache = true` on production.
201 * - {String} viewExt, view file extname, default is `''`.
202 * - {Object} [filters={}]
203 * - {Object} [helpers={}]
204 * @return {Function} render middleware for `connect`
205 */
2063function middleware(options) {
2072 options = options || {};
2082 for (var k in options) {
20910 settings[k] = options[k];
210 }
2112 return function (req, res, next) {
21221 req.next = next;
21321 if (!res.req) {
21421 res.req = req;
215 }
21621 res.render = render;
21721 next();
218 };
219}
220
2213module.exports = middleware;

filters.js

100%
44
44
0
LineHitsSource
1/*!
2 * connect-render - lib/filters.js
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
71"use strict";
8
9/**
10 * Module dependencies.
11 */
12
131var moment = require('moment');
14
151var NO_ASCII_CHAR_RE = /[^\x00-\xff]/;
16
171var TRUNCATE_PEDDING = '…';
18
19/**
20 * truncatechars with max unicode length
21 * e.g.:
22 *
23 * ```js
24 * truncatechars('你好吗?', 3); // => '你好…'
25 * truncatechars('hello', 2); // => 'he…'
26 * ```
27 *
28 * @param {String} text
29 * @param {Number} max, max string length.
30 * @return {String}
31 * @public
32 */
331exports.truncatechars = function (text, max) {
3432 if (!max) {
355 throw new Error('`max` must be inter and above 0');
36 }
3727 if (text === null || text === undefined) {
382 return '';
39 }
4025 if (typeof text === 'string') {
4120 text = text.trim();
42 } else {
435 text = String(text);
44 }
4525 if (text === '') {
461 return text;
47 }
48
4924 var len = Math.round(text.replace(/[^\x00-\xff]/g, 'qq').length / 2);
5024 if (len > max) {
5114 var index = 0;
5214 for (var j = 0, l = max - 1; j < l; index++) {
5334 if (NO_ASCII_CHAR_RE.test(text[index])) {
5410 j += 1;
55 } else {
5624 j += 0.5;
57 }
58 }
5914 text = text.substring(0, index) + TRUNCATE_PEDDING;
60 }
6124 return text;
62};
63
64/**
65 * Show money format with decimal digits.
66 *
67 * e.g.:
68 *
69 * ```js
70 * fmoney("12345.675910", 3); // => 12,345.676
71 * ```
72 *
73 * @param {String|Number} s
74 * @param {Number} n, decimal digits.
75 * @return {String}
76 */
771exports.fmoney = function fmoney(s, n) {
7880 s = parseFloat((s + "").replace(/[^\d\.\-]/g, ""));
7980 if (isNaN(s)) {
806 return s;
81 }
8274 var lr = (s.toFixed(n || 0) + '').split('.');
8373 var l = lr[0].split('').reverse();
8473 var t = [];
8573 for (var i = 0, len = l.length, last = len - 1; i < len; i++) {
86172 t.push(l[i]);
87172 if ((i + 1) % 3 === 0 && i !== last) {
8823 t.push(',');
89 }
90 }
9173 t = t.reverse();
9273 if (lr[1]) {
9363 t.push('.');
9463 t.push(lr[1]);
95 }
9673 return t.join('');
97};
98
99/**
100 * Format DateTime to string.
101 *
102 * @see http://momentjs.com/docs/#/displaying/format/
103 * @param {Date} d
104 * @param {String} format, default is 'YYYY-MM-DD HH:mm:ss'.
105 * @return {String}
106 */
1071exports.dateformat = function (d, format) {
10810 var date = moment(d);
10910 if (!d || !date || !date.isValid()) {
1105 return '';
111 }
1125 return date.format(format || 'YYYY-MM-DD HH:mm:ss');
113};