summaryrefslogtreecommitdiff
path: root/node_modules/express/lib/view.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/express/lib/view.js')
-rw-r--r--node_modules/express/lib/view.js460
1 files changed, 460 insertions, 0 deletions
diff --git a/node_modules/express/lib/view.js b/node_modules/express/lib/view.js
new file mode 100644
index 0000000..5258249
--- /dev/null
+++ b/node_modules/express/lib/view.js
@@ -0,0 +1,460 @@
+
+/*!
+ * Express - view
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var path = require('path')
+ , extname = path.extname
+ , dirname = path.dirname
+ , basename = path.basename
+ , utils = require('connect').utils
+ , View = require('./view/view')
+ , partial = require('./view/partial')
+ , union = require('./utils').union
+ , merge = utils.merge
+ , http = require('http')
+ , res = http.ServerResponse.prototype;
+
+/**
+ * Expose constructors.
+ */
+
+exports = module.exports = View;
+
+/**
+ * Export template engine registrar.
+ */
+
+exports.register = View.register;
+
+/**
+ * Lookup and compile `view` with cache support by supplying
+ * both the `cache` object and `cid` string,
+ * followed by `options` passed to `exports.lookup()`.
+ *
+ * @param {String} view
+ * @param {Object} cache
+ * @param {Object} cid
+ * @param {Object} options
+ * @return {View}
+ * @api private
+ */
+
+exports.compile = function(view, cache, cid, options){
+ if (cache && cid && cache[cid]){
+ options.filename = cache[cid].path;
+ return cache[cid];
+ }
+
+ // lookup
+ view = exports.lookup(view, options);
+
+ // hints
+ if (!view.exists) {
+ if (options.hint) hintAtViewPaths(view.original, options);
+ var err = new Error('failed to locate view "' + view.original.view + '"');
+ err.view = view.original;
+ throw err;
+ }
+
+ // compile
+ options.filename = view.path;
+ view.fn = view.templateEngine.compile(view.contents, options);
+ cache[cid] = view;
+
+ return view;
+};
+
+/**
+ * Lookup `view`, returning an instanceof `View`.
+ *
+ * Options:
+ *
+ * - `root` root directory path
+ * - `defaultEngine` default template engine
+ * - `parentView` parent `View` object
+ * - `cache` cache object
+ * - `cacheid` optional cache id
+ *
+ * Lookup:
+ *
+ * - partial `_<name>`
+ * - any `<name>/index`
+ * - non-layout `../<name>/index`
+ * - any `<root>/<name>`
+ * - partial `<root>/_<name>`
+ *
+ * @param {String} view
+ * @param {Object} options
+ * @return {View}
+ * @api private
+ */
+
+exports.lookup = function(view, options){
+ var orig = view = new View(view, options)
+ , partial = options.isPartial
+ , layout = options.isLayout;
+
+ // Try _ prefix ex: ./views/_<name>.jade
+ // taking precedence over the direct path
+ if (partial) {
+ view = new View(orig.prefixPath, options);
+ if (!view.exists) view = orig;
+ }
+
+ // Try index ex: ./views/user/index.jade
+ if (!layout && !view.exists) view = new View(orig.indexPath, options);
+
+ // Try ../<name>/index ex: ../user/index.jade
+ // when calling partial('user') within the same dir
+ if (!layout && !view.exists) view = new View(orig.upIndexPath, options);
+
+ // Try root ex: <root>/user.jade
+ if (!view.exists) view = new View(orig.rootPath, options);
+
+ // Try root _ prefix ex: <root>/_user.jade
+ if (!view.exists && partial) view = new View(view.prefixPath, options);
+
+ view.original = orig;
+ return view;
+};
+
+/**
+ * Partial render helper.
+ *
+ * @api private
+ */
+
+function renderPartial(res, view, options, parentLocals, parent){
+ var collection, object, locals;
+
+ if (options) {
+ // collection
+ if (options.collection) {
+ collection = options.collection;
+ delete options.collection;
+ } else if ('length' in options) {
+ collection = options;
+ options = {};
+ }
+
+ // locals
+ if (options.locals) {
+ locals = options.locals;
+ delete options.locals;
+ }
+
+ // object
+ if ('Object' != options.constructor.name) {
+ object = options;
+ options = {};
+ } else if (undefined != options.object) {
+ object = options.object;
+ delete options.object;
+ }
+ } else {
+ options = {};
+ }
+
+ // Inherit locals from parent
+ union(options, parentLocals);
+
+ // Merge locals
+ if (locals) merge(options, locals);
+
+ // Partials dont need layouts
+ options.isPartial = true;
+ options.layout = false;
+
+ // Deduce name from view path
+ var name = options.as || partial.resolveObjectName(view);
+
+ // Render partial
+ function render(){
+ if (object) {
+ if ('string' == typeof name) {
+ options[name] = object;
+ } else if (name === global) {
+ merge(options, object);
+ }
+ }
+ return res.render(view, options, null, parent, true);
+ }
+
+ // Collection support
+ if (collection) {
+ var len = collection.length
+ , buf = ''
+ , keys
+ , key
+ , val;
+
+ options.collectionLength = len;
+
+ if ('number' == typeof len || Array.isArray(collection)) {
+ for (var i = 0; i < len; ++i) {
+ val = collection[i];
+ options.firstInCollection = i == 0;
+ options.indexInCollection = i;
+ options.lastInCollection = i == len - 1;
+ object = val;
+ buf += render();
+ }
+ } else {
+ keys = Object.keys(collection);
+ len = keys.length;
+ options.collectionLength = len;
+ options.collectionKeys = keys;
+ for (var i = 0; i < len; ++i) {
+ key = keys[i];
+ val = collection[key];
+ options.keyInCollection = key;
+ options.firstInCollection = i == 0;
+ options.indexInCollection = i;
+ options.lastInCollection = i == len - 1;
+ object = val;
+ buf += render();
+ }
+ }
+
+ return buf;
+ } else {
+ return render();
+ }
+};
+
+/**
+ * Render `view` partial with the given `options`. Optionally a
+ * callback `fn(err, str)` may be passed instead of writing to
+ * the socket.
+ *
+ * Options:
+ *
+ * - `object` Single object with name derived from the view (unless `as` is present)
+ *
+ * - `as` Variable name for each `collection` value, defaults to the view name.
+ * * as: 'something' will add the `something` local variable
+ * * as: this will use the collection value as the template context
+ * * as: global will merge the collection value's properties with `locals`
+ *
+ * - `collection` Array of objects, the name is derived from the view name itself.
+ * For example _video.html_ will have a object _video_ available to it.
+ *
+ * @param {String} view
+ * @param {Object|Array|Function} options, collection, callback, or object
+ * @param {Function} fn
+ * @return {String}
+ * @api public
+ */
+
+res.partial = function(view, options, fn){
+ var app = this.app
+ , options = options || {}
+ , viewEngine = app.set('view engine')
+ , parent = {};
+
+ // accept callback as second argument
+ if ('function' == typeof options) {
+ fn = options;
+ options = {};
+ }
+
+ // root "views" option
+ parent.dirname = app.set('views') || process.cwd() + '/views';
+
+ // utilize "view engine" option
+ if (viewEngine) parent.engine = viewEngine;
+
+ // render the partial
+ try {
+ var str = renderPartial(this, view, options, null, parent);
+ } catch (err) {
+ if (fn) {
+ fn(err);
+ } else {
+ this.req.next(err);
+ }
+ return;
+ }
+
+ // callback or transfer
+ if (fn) {
+ fn(null, str);
+ } else {
+ this.send(str);
+ }
+};
+
+/**
+ * Render `view` with the given `options` and optional callback `fn`.
+ * When a callback function is given a response will _not_ be made
+ * automatically, however otherwise a response of _200_ and _text/html_ is given.
+ *
+ * Options:
+ *
+ * - `scope` Template evaluation context (the value of `this`)
+ * - `debug` Output debugging information
+ * - `status` Response status code
+ *
+ * @param {String} view
+ * @param {Object|Function} options or callback function
+ * @param {Function} fn
+ * @api public
+ */
+
+res.render = function(view, opts, fn, parent, sub){
+ // support callback function as second arg
+ if ('function' == typeof opts) {
+ fn = opts, opts = null;
+ }
+
+ try {
+ return this._render(view, opts, fn, parent, sub);
+ } catch (err) {
+ // callback given
+ if (fn) {
+ fn(err);
+ // unwind to root call to prevent multiple callbacks
+ } else if (sub) {
+ throw err;
+ // root template, next(err)
+ } else {
+ this.req.next(err);
+ }
+ }
+};
+
+// private render()
+
+res._render = function(view, opts, fn, parent, sub){
+ var options = {}
+ , self = this
+ , app = this.app
+ , helpers = app._locals
+ , dynamicHelpers = app.dynamicViewHelpers
+ , viewOptions = app.set('view options')
+ , root = app.set('views') || process.cwd() + '/views';
+
+ // cache id
+ var cid = app.enabled('view cache')
+ ? view + (parent ? ':' + parent.path : '')
+ : false;
+
+ // merge "view options"
+ if (viewOptions) merge(options, viewOptions);
+
+ // merge res._locals
+ if (this._locals) merge(options, this._locals);
+
+ // merge render() options
+ if (opts) merge(options, opts);
+
+ // merge render() .locals
+ if (opts && opts.locals) merge(options, opts.locals);
+
+ // status support
+ if (options.status) this.statusCode = options.status;
+
+ // capture attempts
+ options.attempts = [];
+
+ var partial = options.isPartial
+ , layout = options.layout;
+
+ // Layout support
+ if (true === layout || undefined === layout) {
+ layout = 'layout';
+ }
+
+ // Default execution scope to a plain object
+ options.scope = options.scope || {};
+
+ // Populate view
+ options.parentView = parent;
+
+ // "views" setting
+ options.root = root;
+
+ // "view engine" setting
+ options.defaultEngine = app.set('view engine');
+
+ // charset option
+ if (options.charset) this.charset = options.charset;
+
+ // Dynamic helper support
+ if (false !== options.dynamicHelpers) {
+ // cache
+ if (!this.__dynamicHelpers) {
+ this.__dynamicHelpers = {};
+ for (var key in dynamicHelpers) {
+ this.__dynamicHelpers[key] = dynamicHelpers[key].call(
+ this.app
+ , this.req
+ , this);
+ }
+ }
+
+ // apply
+ merge(options, this.__dynamicHelpers);
+ }
+
+ // Merge view helpers
+ union(options, helpers);
+
+ // Always expose partial() as a local
+ options.partial = function(path, opts){
+ return renderPartial(self, path, opts, options, view);
+ };
+
+ // View lookup
+ options.hint = app.enabled('hints');
+ view = exports.compile(view, app.cache, cid, options);
+
+ // layout helper
+ options.layout = function(path){
+ layout = path;
+ };
+
+ // render
+ var str = view.fn.call(options.scope, options);
+
+ // layout expected
+ if (layout) {
+ options.isLayout = true;
+ options.layout = false;
+ options.body = str;
+ this.render(layout, options, fn, view, true);
+ // partial return
+ } else if (partial) {
+ return str;
+ // render complete, and
+ // callback given
+ } else if (fn) {
+ fn(null, str);
+ // respond
+ } else {
+ this.send(str);
+ }
+}
+
+/**
+ * Hint at view path resolution, outputting the
+ * paths that Express has tried.
+ *
+ * @api private
+ */
+
+function hintAtViewPaths(view, options) {
+ console.error();
+ console.error('failed to locate view "' + view.view + '", tried:');
+ options.attempts.forEach(function(path){
+ console.error(' - %s', path);
+ });
+ console.error();
+}