UNPKG

broadway-handlebars

Version:

Plugin for flatiron/broadway for rendering with the handlebars view engine.

225 lines (183 loc) 6.26 kB
// var _ = { // each: require('lodash.each'), // defaults: require('lodash.defaults'), // extend: require('lodash.extend'), // clone: require('lodash.clone'), // range: require('lodash.range') // }, var _ = require('lodash'); var Minimize = require('minimize') var minimize = new Minimize(); var handlebars = require('handlebars'); // parens will prevent browserify from packaging. var defaultViewResolver = (typeof(window) === 'undefined') ? require('./viewresolver') : null; var bwHandlebars = function() { }; /** * Attaches and initializes the plugin. * @params options.helpers {Object} key/value dictionary of handlerbar helpers. key is the name and val is the function. * @params options.optimize {Boolean} Compresses and removes whitespace from markup. * @params options.development {Boolean} If true, then templates are fetched and compiled every time they are rendered. * @params options.viewResolver {Object} if absent, then a file system view resolver is used to get the markup from the file system. **/ bwHandlebars.prototype.attach = function (options) { // support the factory if exists - https://github.com/tommydudebreaux/handlebars.js // otherwise, use the default singleton approach here. var Handlebars = handlebars.create ? handlebars.create() : handlebars; _.defaults(options, { helpers: {}, optimize: false, development: true, view: { base: (typeof(window) === 'undefined') ? process.cwd() : null, ext: "html" } }); var viewResolver = options.viewResolver || new defaultViewResolver(options.view), templateCache = {}; // Override the default version of each. Handlebars.registerHelper('each', function(context, options) { var buffer = [], that = this; _.each(context, function(v, k) { try { buffer.push(options.fn(_.extend(_.clone(that), { key: k, value: v }))); } catch (e) { buffer.push(e); } }); return buffer.join(''); }); // Create a range function that will iterate numbers from 0 -> max // This is mirrors the underscore _.range function. // http://documentcloud.github.com/underscore/#range Handlebars.registerHelper('range', function(expression, options) { var buffer = [], stop = 0, that = this; if (typeof(expression) == 'string') { with(this) { stop = eval(expression); } } else { stop = expression; } _.each(_.range((options.hash.start || 0), stop, (options.hash.step || 1)), function(v, k) { try { buffer.push(options.fn( _.extend(_.clone(that), { key: k, value: v }))); } catch (e) { buffer.push(e); } }); return buffer.join(''); }); // if with evaluation. Handlebars.registerHelper('ifeval', function(expression, options) { var conditional = false; with(this) { conditional = eval(expression); } return conditional ? options.fn(this) : options.inverse(this); }); // Similar to standard print {{value}}, but with an optional fallback value. // Ex: {{def foo def="goo"}} will print the value of foo if exists, otherwise it will print "goo" // If def is omitted, then an empty string is printed. Handlebars.registerHelper('def', function(expression, options) { var val = null, def = options.hash.def || ''; try { with(this) { val = eval(expression); } } catch (e) { val = expression; } return val ? val : def; }); // Allowing dynamic loading of partials based on value of variable in context // Call is made like {{{partial "partial.root" partial_name ctx}}} or {{{partial full_partial_string ctx}}} Handlebars.registerHelper('partial', function(root, name, ctx, hash) { var ps = Handlebars.partials, path; if (typeof name === 'object') { var tmp = name; name = ''; hash = ctx; ctx = tmp; } else if (typeof name === 'undefined') { name = ''; }; path = root + ((name !== '') ? ('.' + name) : name); if(typeof ps[path] != 'function') ps[path] = Handlebars.compile(ps[path]); return ps[path](ctx, hash); }); // Inject external handlebar helpers. _.each(options.helpers, function(fn, name) { Handlebars.registerHelper(name, fn); }); /** * Attaches to a Broadway app and exposes the render function * @params view {String} The name of the view to render. This will be used by the view resolver to pull markup from file system (node.js) or other (client js) * @params data {Object} The view model to use when rendering. * @params callback {Function} Notifies when render is complete. callback(err, data) **/ this.render = function (view, data, callback) { var template = templateCache[view]; // are we in debug or was it already compiled if (!options.development && template) { _render(view, data, callback); } else { // otherwise get and compile. viewResolver.all(function(err, dict) { if (err) { callback(err); } else { // register each partial in case it is needed by view. _.each(dict, function(markup, key) { Handlebars.registerPartial(key.replace(/\//g, '.'), markup); }); // Compile and render the top level view. _compile(view, dict[view]); _render(view, data, callback); } }); } }; // expose the instance of hb so that helpers or other things can be attached async. this.render.instance = Handlebars; var _compile = function(view, markup) { // remove whitespace if html compression is on. if (markup && options.optimize) { markup = markup.replace(/\s+/gi, ' '); } templateCache[view] = markup ? Handlebars.compile(markup) : function() { throw new Error("View Not Found - " + view); }; return templateCache[view]; }; var _render = function(view, data, callback) { var template = templateCache[view], html = null, err = null; // render try { html = template(data); } catch (e) { err = e; } if (options.minify) { minimize.parse(html, function (error, data) { return typeof(callback) === 'function' ? callback(error, data) : null; }); } else { return typeof(callback) === 'function' ? callback(err, html) : null; } }; this.templates = function(callback) { viewResolver.all(callback); }; }; module.exports = bwHandlebars;