Permalink
Browse files

Merge pull request #842 from caridy/partials

Adding support for handlebar partials:
- a partial view (e.g. views/partials/foo.hb.html) can be used by any view within the same context/mojit.
- global partial views are also supported.
  • Loading branch information...
2 parents 5b6e80f + 8e8b777 commit d7c0e8b99df9842b384a5a8f6a1eda9d682497bd @caridy caridy committed Jan 29, 2013
View
7 README.md
@@ -71,10 +71,3 @@ http://developer.yahoo.com/forum/Yahoo-Mojito
Mojito is licensed under a [BSD license](https://github.com/yahoo/mojito/blob/master/LICENSE.txt). To contribute to the Mojito project, please see [Contributing](https://github.com/yahoo/mojito/wiki/Contributing-Code-to-Mojito).
The Mojito project is a [meritocratic, consensus-based community project](https://github.com/yahoo/mojito/wiki/Governance-Model) which allows anyone to contribute and gain additional responsibilities.
-
-## Third-party libraries
-
-Mojito includes the Mulib software available here:
-
-https://github.com/raycmorgan/Mu
-
View
366 examples/sandbox/bindertime/assets/js/mu.client.js
@@ -1,366 +0,0 @@
-/*
- * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved.
- * Copyrights licensed under the New BSD License.
- * See the accompanying LICENSE file for terms.
- */
-
-YUI.add('mojito-mu', function(Y){
-
- function MuAdapter() {}
-
- MuAdapter.prototype.render = function(data, tmpl, adapter, meta, more) {
- var uuid = Y.guid();
-
- Y.on('io:complete', function(id, o, UUID){
- if(uuid !== UUID){
- return;
- }
-
- adapter.flush(Mustache.to_html(o.responseText, data), meta);
-
- if(!more){
- adapter.done('', meta);
- }
- }, Y, uuid);
-
- Y.on('io:failure', function(id, o, UUID){
- if(uuid !== UUID){
- return;
- }
- adapter.done('Mu Error.', meta);
- }, Y, uuid);
-
- Y.io(tmpl);
- }
-
- Y.mojito.addons.viewEngines.mu = MuAdapter;
-
- /*
- Taken from https://github.com/janl/mustache.js
-
- mustache.js — Logic-less templates in JavaScript
-
- See http://mustache.github.com/ for more info.
- */
-
- var Mustache = function() {
- var Renderer = function() {};
-
- Renderer.prototype = {
- otag: "{{",
- ctag: "}}",
- pragmas: {},
- buffer: [],
- pragmas_implemented: {
- "IMPLICIT-ITERATOR": true
- },
- context: {},
-
- render: function(template, context, partials, in_recursion) {
- // reset buffer & set context
- if(!in_recursion) {
- this.context = context;
- this.buffer = []; // TODO: [bug 4647795] make this non-lazy
- }
-
- // fail fast
- if(!this.includes("", template)) {
- if(in_recursion) {
- return template;
- } else {
- this.send(template);
- return;
- }
- }
-
- template = this.render_pragmas(template);
- var html = this.render_section(template, context, partials);
- if(in_recursion) {
- return this.render_tags(html, context, partials, in_recursion);
- }
-
- this.render_tags(html, context, partials, in_recursion);
- },
-
- /*
- Sends parsed lines
- */
- send: function(line) {
- if(line != "") {
- this.buffer.push(line);
- }
- },
-
- /*
- Looks for %PRAGMAS
- */
- render_pragmas: function(template) {
- // no pragmas
- if(!this.includes("%", template)) {
- return template;
- }
-
- var that = this;
- var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
- this.ctag);
- return template.replace(regex, function(match, pragma, options) {
- if(!that.pragmas_implemented[pragma]) {
- throw({message:
- "This implementation of mustache doesn't understand the '" +
- pragma + "' pragma"});
- }
- that.pragmas[pragma] = {};
- if(options) {
- var opts = options.split("=");
- that.pragmas[pragma][opts[0]] = opts[1];
- }
- return "";
- // ignore unknown pragmas silently
- });
- },
-
- /*
- Tries to find a partial in the curent scope and render it
- */
- render_partial: function(name, context, partials) {
- name = this.trim(name);
- if(!partials || partials[name] === undefined) {
- throw({message: "unknown_partial '" + name + "'"});
- }
- if(typeof(context[name]) != "object") {
- return this.render(partials[name], context, partials, true);
- }
- return this.render(partials[name], context[name], partials, true);
- },
-
- /*
- Renders inverted (^) and normal (#) sections
- */
- render_section: function(template, context, partials) {
- if(!this.includes("#", template) && !this.includes("^", template)) {
- return template;
- }
-
- var that = this;
- // CSW - Added "+?" so it finds the tighest bound, not the widest
- var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
- "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
- "\\s*", "mg");
-
- // for each {{#foo}}{{/foo}} section do...
- return template.replace(regex, function(match, type, name, content) {
- var value = that.find(name, context);
- if(type == "^") { // inverted section
- if(!value || that.is_array(value) && value.length === 0) {
- // false or empty list, render it
- return that.render(content, context, partials, true);
- } else {
- return "";
- }
- } else if(type == "#") { // normal section
- if(that.is_array(value)) { // Enumerable, Let's loop!
- return that.map(value, function(row) {
- return that.render(content, that.create_context(row),
- partials, true);
- }).join("");
- } else if(that.is_object(value)) { // Object, Use it as subcontext!
- return that.render(content, that.create_context(value),
- partials, true);
- } else if(typeof value === "function") {
- // higher order section
- return value.call(context, content, function(text) {
- return that.render(text, context, partials, true);
- });
- } else if(value) { // boolean section
- return that.render(content, context, partials, true);
- } else {
- return "";
- }
- }
- });
- },
-
- /*
- Replace {{foo}} and friends with values from our view
- */
- render_tags: function(template, context, partials, in_recursion) {
- // tit for tat
- var that = this;
-
- var new_regex = function() {
- return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
- that.ctag + "+", "g");
- };
-
- var regex = new_regex();
- var tag_replace_callback = function(match, operator, name) {
- switch(operator) {
- case "!": // ignore comments
- return "";
- case "=": // set new delimiters, rebuild the replace regexp
- that.set_delimiters(name);
- regex = new_regex();
- return "";
- case ">": // render partial
- return that.render_partial(name, context, partials);
- case "{": // the triple mustache is unescaped
- return that.find(name, context);
- default: // escape the value
- return that.escape(that.find(name, context));
- }
- };
- var lines = template.split("\n");
- for(var i = 0; i < lines.length; i++) {
- lines[i] = lines[i].replace(regex, tag_replace_callback, this);
- if(!in_recursion) {
- this.send(lines[i]);
- }
- }
-
- if(in_recursion) {
- return lines.join("\n");
- }
- },
-
- set_delimiters: function(delimiters) {
- var dels = delimiters.split(" ");
- this.otag = this.escape_regex(dels[0]);
- this.ctag = this.escape_regex(dels[1]);
- },
-
- escape_regex: function(text) {
- // thank you Simon Willison
- if(!arguments.callee.sRE) {
- var specials = [
- '/', '.', '*', '+', '?', '|',
- '(', ')', '[', ']', '{', '}', '\\'
- ];
- arguments.callee.sRE = new RegExp(
- '(\\' + specials.join('|\\') + ')', 'g'
- );
- }
- return text.replace(arguments.callee.sRE, '\\$1');
- },
-
- /*
- find `name` in current `context`. That is find me a value
- from the view object
- */
- find: function(name, context) {
- name = this.trim(name);
-
- // Checks whether a value is thruthy or false or 0
- function is_kinda_truthy(bool) {
- return bool === false || bool === 0 || bool;
- }
-
- var value;
- if(is_kinda_truthy(context[name])) {
- value = context[name];
- } else if(is_kinda_truthy(this.context[name])) {
- value = this.context[name];
- }
-
- if(typeof value === "function") {
- return value.apply(context);
- }
- if(value !== undefined) {
- return value;
- }
- // silently ignore unkown variables
- return "";
- },
-
- // Utility methods
-
- /* includes tag */
- includes: function(needle, haystack) {
- return haystack.indexOf(this.otag + needle) != -1;
- },
-
- /*
- Does away with nasty characters
- */
- escape: function(s) {
- s = String(s === null ? "" : s);
- return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
- switch(s) {
- case "&": return "&amp;";
- case "\\": return "\\\\";
- case '"': return '&quot;';
- case "'": return '&#39;';
- case "<": return "&lt;";
- case ">": return "&gt;";
- default: return s;
- }
- });
- },
-
- // by @langalex, support for arrays of strings
- create_context: function(_context) {
- if(this.is_object(_context)) {
- return _context;
- } else {
- var iterator = ".";
- if(this.pragmas["IMPLICIT-ITERATOR"]) {
- iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
- }
- var ctx = {};
- ctx[iterator] = _context;
- return ctx;
- }
- },
-
- is_object: function(a) {
- return a && typeof a == "object";
- },
-
- is_array: function(a) {
- return Object.prototype.toString.call(a) === '[object Array]';
- },
-
- /*
- Gets rid of leading and trailing whitespace
- */
- trim: function(s) {
- return s.replace(/^\s*|\s*$/g, "");
- },
-
- /*
- Why, why, why? Because IE. Cry, cry cry.
- */
- map: function(array, fn) {
- if (typeof array.map == "function") {
- return array.map(fn);
- } else {
- var r = [];
- var l = array.length;
- for(var i = 0; i < l; i++) {
- r.push(fn(array[i]));
- }
- return r;
- }
- }
- };
-
- return({
- name: "mustache.js",
- version: "0.3.1-dev",
-
- /*
- Turns a template and view into HTML
- */
- to_html: function(template, view, partials, send_fun) {
- var renderer = new Renderer();
- if(send_fun) {
- renderer.send = send_fun;
- }
- renderer.render(template, view, partials);
- if(!send_fun) {
- return renderer.buffer.join("\n");
- }
- }
- });
- }();
-
-}, '0.0.1', {requires: ['mojito', 'io']});
View
3 lib/app/addons/ac/partial.common.js
@@ -54,7 +54,8 @@ YUI.add('mojito-partial-addon', function(Y, NAME) {
mojitView = instance.views[view];
data = data || {}; // default null data to empty view template
- renderer = new Y.mojito.ViewRenderer(mojitView.engine);
+ renderer = new Y.mojito.ViewRenderer(mojitView.engine,
+ this.ac.staticAppConfig.viewEngine);
Y.log('Rendering "' + view + '" view for "' + (instance.id || '@' +
instance.type) + '"', 'debug', NAME);
View
124 lib/app/addons/view-engines/hb.client.js
@@ -16,25 +16,30 @@ YUI.add('mojito-hb', function(Y, NAME) {
cache = Y.namespace('Env.Mojito.Handlebars');
/**
- * Class text.
- * @class HandleBarsAdapterServer
+ * HandlerBars Adapter for the client runtime.
+ * @class HandleBarsAdapterClient
+ * @constructor
+ * @param {object} options View engine configuration.
* @private
*/
- function HandleBarsAdapter() {}
+ function HandleBarsAdapter(options) {
+ this.options = options || {};
+ }
HandleBarsAdapter.prototype = {
/**
* Renders the handlebars template using the data provided.
* @param {object} data The data to render.
- * @param {string} mojitType The name of the mojit type.
- * @param {string} tmpl The name of the template to render.
+ * @param {object} instance The expanded mojit instance.
+ * @param {object} template The view object from RS to render with format:
+ * {'content-path': 'path to view', content: 'cached string'}.
* @param {object} adapter The output adapter to use.
* @param {object} meta Optional metadata.
* @param {boolean} more Whether there is more to be rendered
*/
- render: function (data, mojitType, tmpl, adapter, meta, more) {
- var cacheTemplates = meta && meta.view && meta.view.cacheTemplates,
+ render: function (data, instance, template, adapter, meta, more) {
+ var cacheTemplates = (this.options.cacheTemplates === false ? false : true),
handler = function (err, obj) {
var output;
@@ -43,44 +48,106 @@ YUI.add('mojito-hb', function(Y, NAME) {
return;
}
- output = obj.compiled(data);
+ output = obj.compiled(data, {
+ partials: obj.partials
+ });
if (more) {
adapter.flush(output, meta);
} else {
adapter.done(output, meta);
}
+ },
+ stack,
+ cacheKey,
+ fn,
+ partial,
+ partials;
+
+ // support for legacy url instead of a view object
+ if (Y.Lang.isString(template)) {
+ Y.log('[view] argument in [render] method should be an object', 'warn', NAME);
+ template = {
+ 'content-path': template
};
+ }
- this._getTemplateObj(tmpl, !cacheTemplates, handler);
- },
+ cacheKey = template['content-path'];
- /**
- * Cache the reference to a compiled handlebar template, plus
- * a raw string representation of the template.
- * @private
- * @param {string} tmpl The name of the template to render.
- * @param {boolean} bypassCache Whether or not we should rely on the cached content.
- * @param {function} callback The function that is called with the compiled template
- * @return {object} literal object with the "raw" and "template" references.
- */
- _getTemplateObj: function (tmpl, bypassCache, callback) {
- if (cache[tmpl] && !bypassCache) {
- callback(null, cache[tmpl]);
+ if (cacheTemplates && cache[cacheKey]) {
+ handler(null, cache[cacheKey]);
return;
}
- this._loadTemplate(tmpl, function (err, str) {
+ stack = new Y.Parallel();
+ partials = {};
+
+ // first item in the asyc queue is the actual view
+ this._getTemplateObj(template, stack.add(function (err, obj) {
if (err) {
- callback(err);
+ Y.log('Error trying to compile view ' + cacheKey, 'error', NAME);
+ Y.log(err, 'error', NAME);
return;
}
- cache[tmpl] = {
- raw: str,
- compiled: HB.compile(str)
+ cache[cacheKey] = obj;
+ }));
+
+ // after the first item, we just add any partial
+ if (instance && instance.partials && Y.Object.keys(instance.partials).length > 0) {
+ fn = function (partial, err, obj) {
+ if (err) {
+ Y.log('Error trying to compile partial [' + partial + '] on view ' +
+ cacheKey, 'error', NAME);
+ Y.log(err, 'error', NAME);
+ return;
+ }
+ partials[partial] = obj.compiled;
};
- callback(null, cache[tmpl]);
+ for (partial in instance.partials) {
+ if (instance.partials.hasOwnProperty(partial)) {
+ this._getTemplateObj(instance.partials[partial],
+ stack.add(Y.bind(fn, this, partial)));
+ }
+ }
+ }
+
+ // finally, let's just put the compiled view and partials together
+ stack.done(function () {
+ if (!cache[cacheKey]) {
+ handler(new Error("Error trying to render view " + cacheKey));
+ return;
+ }
+ cache[cacheKey].partials = partials;
+ handler(null, cache[cacheKey]);
});
+
+ },
+
+ /**
+ * Build a compiled handlebar template, plus
+ * a raw string representation of the template.
+ * @private
+ * @param {object} template The view object from RS to render with format:
+ * {'content-path': 'path to view', content: 'cached string'}.
+ * @param {function} callback The function that is called with the compiled template
+ * @return {object} literal object with the "raw" and "template" references.
+ */
+ _getTemplateObj: function (template, callback) {
+ var fn = function (err, str) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ callback(null, {
+ raw: str,
+ compiled: HB.compile(str)
+ });
+ };
+ if (template.content) {
+ fn(null, template.content);
+ } else {
+ this._loadTemplate(template['content-path'], fn);
+ }
},
/**
@@ -110,5 +177,6 @@ YUI.add('mojito-hb', function(Y, NAME) {
}, '0.1.0', {requires: [
'io-base',
+ 'parallel',
'handlebars'
]});
View
136 lib/app/addons/view-engines/hb.server.js
@@ -9,7 +9,7 @@
/*global YUI*/
-YUI.add('mojito-hb', function(Y, NAME) {
+YUI.add('mojito-hb', function (Y, NAME) {
'use strict';
@@ -18,27 +18,30 @@ YUI.add('mojito-hb', function(Y, NAME) {
cache = YUI.namespace('Env.Mojito.Handlebars');
/**
- * Class text.
+ * HandlerBars Adapter for the server runtime.
* @class HandleBarsAdapterServer
+ * @constructor
+ * @param {object} options View engine configuration.
* @private
*/
- function HandleBarsAdapter(viewId) {
- this.viewId = viewId;
+ function HandleBarsAdapter(options) {
+ this.options = options || {};
}
HandleBarsAdapter.prototype = {
/**
* Renders the handlebars template using the data provided.
* @param {object} data The data to render.
- * @param {string} mojitType The name of the mojit type.
- * @param {string} tmpl The name of the template to render.
+ * @param {object} instance The expanded mojit instance.
+ * @param {object} template The view object from RS to render with format:
+ * {'content-path': 'path to view', content: 'cached string'}.
* @param {object} adapter The output adapter to use.
* @param {object} meta Optional metadata.
* @param {boolean} more Whether there is more to be rendered
*/
- render: function (data, mojitType, tmpl, adapter, meta, more) {
- var cacheTemplates = meta && meta.view && meta.view.cacheTemplates,
+ render: function (data, instance, template, adapter, meta, more) {
+ var cacheTemplates = (this.options.cacheTemplates === false ? false : true),
handler = function (err, obj) {
var output;
@@ -47,24 +50,87 @@ YUI.add('mojito-hb', function(Y, NAME) {
return;
}
- output = obj.compiled(data);
+ output = obj.compiled(data, {
+ partials: obj.partials
+ });
// HookSystem::StartBlock
- Y.mojito.hooks.hook('hb', adapter.hook, 'end', tmpl);
+ Y.mojito.hooks.hook('hb', adapter.hook, 'end', template);
// HookSystem::EndBlock
if (more) {
adapter.flush(output, meta);
} else {
adapter.done(output, meta);
}
+ },
+ stack,
+ cacheKey,
+ fn,
+ partial,
+ partials;
+
+ // support for legacy url instead of a view object
+ if (Y.Lang.isString(template)) {
+ Y.log('[view] argument in [render] method should be an object', 'warn', NAME);
+ template = {
+ 'content-path': template
};
+ }
+
+ cacheKey = template['content-path'];
+
+ if (cacheTemplates && cache[cacheKey]) {
+ handler(null, cache[cacheKey]);
+ return;
+ }
// HookSystem::StartBlock
- Y.mojito.hooks.hook('hb', adapter.hook, 'start', tmpl);
+ Y.mojito.hooks.hook('hb', adapter.hook, 'start', template);
// HookSystem::EndBlock
- this._getTemplateObj(tmpl, !cacheTemplates, handler);
+ stack = new Y.Parallel();
+ partials = {};
+
+ // first item in the asyc queue is the actual view
+ this._getTemplateObj(template, stack.add(function (err, obj) {
+ if (err) {
+ Y.log('Error trying to compile view ' + cacheKey, 'error', NAME);
+ Y.log(err, 'error', NAME);
+ return;
+ }
+ cache[cacheKey] = obj;
+ }));
+
+ // after the first item, we just add any partial
+ if (instance && instance.partials && Y.Object.keys(instance.partials).length > 0) {
+ fn = function (partial, err, obj) {
+ if (err) {
+ Y.log('Error trying to compile partial [' + partial + '] on view ' +
+ cacheKey, 'error', NAME);
+ Y.log(err, 'error', NAME);
+ return;
+ }
+ partials[partial] = obj.compiled;
+ };
+ for (partial in instance.partials) {
+ if (instance.partials.hasOwnProperty(partial)) {
+ this._getTemplateObj(instance.partials[partial],
+ stack.add(Y.bind(fn, this, partial)));
+ }
+ }
+ }
+
+ // finally, let's just put the compiled view and partials together
+ stack.done(function () {
+ if (!cache[cacheKey]) {
+ handler(new Error("Error trying to render view " + cacheKey));
+ return;
+ }
+ cache[cacheKey].partials = partials;
+ handler(null, cache[cacheKey]);
+ });
+
},
/**
@@ -74,7 +140,7 @@ YUI.add('mojito-hb', function(Y, NAME) {
* that can be sent to the client side.
*/
compiler: function (tmpl, callback) {
- this._getTemplateObj(tmpl, false, function (err, obj) {
+ this._getTemplateObj(tmpl, function (err, obj) {
callback(err, JSON.stringify(obj.raw));
});
},
@@ -85,36 +151,36 @@ YUI.add('mojito-hb', function(Y, NAME) {
* @return {string} the precompiled template that can be sent to the client side as Javascript code.
*/
precompile: function (tmpl, callback) {
- this._loadTemplate(tmpl, function (err, raw) {
- callback(err, HB.precompile(raw));
+ this._getTemplateObj(tmpl, function (err, obj) {
+ callback(err, HB.precompile(obj.raw));
});
},
/**
- * Cache the reference to a compiled handlebar template, plus
+ * Build a compiled handlebar template, plus
* a raw string representation of the template.
* @private
- * @param {string} tmpl The name of the template to render.
- * @param {boolean} bypassCache Whether or not we should rely on the cached content.
+ * @param {object|string} template The view object from RS to render with format:
+ * {'content-path': 'path to view', content: 'cached string'} or a string with the
+ * fullpath of the template to be loaded.
* @param {function} callback The function that is called with the compiled template
*/
- _getTemplateObj: function (tmpl, bypassCache, callback) {
- if (cache[tmpl] && !bypassCache) {
- callback(null, cache[tmpl]);
- return;
- }
-
- this._loadTemplate(tmpl, function (err, str) {
- if (err) {
- callback(err);
- return;
- }
- cache[tmpl] = {
- raw: str,
- compiled: HB.compile(str)
+ _getTemplateObj: function (template, callback) {
+ var fn = function (err, str) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ callback(null, {
+ raw: str,
+ compiled: HB.compile(str)
+ });
};
- callback(null, cache[tmpl]);
- });
+ if (template.content) {
+ fn(null, template.content);
+ } else {
+ this._loadTemplate((typeof template === 'string' ? template : template['content-path']), fn);
+ }
},
/**
@@ -133,5 +199,7 @@ YUI.add('mojito-hb', function(Y, NAME) {
Y.namespace('mojito.addons.viewEngines').hb = HandleBarsAdapter;
}, '0.1.0', {requires: [
+ 'mojito',
+ 'parallel',
'mojito-hooks'
]});
View
501 lib/app/addons/view-engines/mu.client.js
@@ -4,8 +4,6 @@
* See the accompanying LICENSE file for terms.
*/
-
-/*jslint anon:true, sloppy:true, nomen:true*/
/*global YUI*/
/**
@@ -20,501 +18,12 @@
/**
* @Module ViewEngines
*/
-YUI.add('mojito-mu', function(Y, NAME) {
-
- var CACHE = {},
- QUEUE_POOL = {}, // hash URL: contents private functions
- isCached,
- Mustache;
-
-
- Mustache = (function() {
- var sRE,
- Renderer = function() {};
-
- Renderer.prototype = {
- otag: '{{',
- ctag: '}}',
- pragmas: {},
- buffer: [],
- pragmas_implemented: {
- 'IMPLICIT-ITERATOR': true
- },
- context: {},
-
-
- render: function(template, context, partials, in_recursion) {
- var html;
-
- if (!in_recursion) {
- this.context = context;
- this.buffer = [];
- }
-
- if (!this.includes('', template)) {
- if (in_recursion) {
- return template;
- }
- this.send(template);
- return;
- }
-
- template = this.render_pragmas(template);
- html = this.render_section(template, context, partials);
-
- if (in_recursion) {
- return this.render_tags(html, context, partials,
- in_recursion);
- }
-
- this.render_tags(html, context, partials, in_recursion);
- },
-
-
- send: function(line) {
- if (line) {
- this.buffer.push(line);
- }
- },
-
-
- render_pragmas: function(template) {
- var my,
- regex;
-
- if (!this.includes('%', template)) {
- return template;
- }
-
- my = this;
- regex = new RegExp(this.otag + '%([\\w-]+) ?([\\w]+=[\\w]+)?' +
- this.ctag);
-
- return template.replace(regex,
- function(match, pragma, options) {
- var opts;
- if (!my.pragmas_implemented[pragma]) {
- throw ({message:
- 'This implementation of mustache ' +
- 'doesn\'t understand the ' +
- pragma + '\' pragma'});
- }
-
- my.pragmas[pragma] = {};
- if (options) {
- opts = options.split('=');
- my.pragmas[pragma][opts[0]] = opts[1];
- }
- return '';
- });
- },
-
-
- render_partial: function(name, context, partials) {
- name = this.trim(name);
- if (!partials || partials[name] === undefined) {
- throw ({message: 'unknown_partial \'' + name + '\''});
- }
- if (typeof context[name] !== 'object') {
- return this.render(partials[name], context, partials, true);
- }
- return this.render(partials[name], context[name], partials,
- true);
- },
-
-
- render_section: function(template, context, partials) {
- var my,
- regex;
-
- if (!this.includes('#', template) &&
- !this.includes('^', template)) {
- return template;
- }
-
- my = this;
- regex = new RegExp(this.otag + '(\\^|\\#)\\s*(.+)\\s*' +
- this.ctag + '\n*([\\s\\S]+?)' +
- this.otag + '\\/\\s*\\2\\s*' +
- this.ctag + '\\s*', 'mg');
-
- return template.replace(regex,
- function(match, type, name, content) {
- var value = my.find(name, context);
- if (type === '^') {
- if (!value || (my.is_array(value) &&
- value.length === 0)) {
- return my.render(content, context, partials,
- true);
- }
- return '';
- }
-
- if (type === '#') { // normal section
- if (my.is_array(value)) {
- // Enumerable, Let's loop!
- return my.map(value,
- function(row) {
- return my.render(content,
- my.create_context(row),
- partials, true);
- }).join('');
- }
-
- if (my.is_object(value)) {
- // Object, Use it as subcontext!
- return my.render(content,
- my.create_context(value), partials, true);
- }
-
- if (typeof value === 'function') {
- // higher order section
- return value.call(context, content,
- function(text) {
- return my.render(text, context,
- partials, true);
- });
- }
-
- if (value) {
- // boolean section
- return my.render(content, context, partials,
- true);
- }
-
- return '';
- }
- });
- },
-
-
- render_tags: function(template, context, partials, in_recursion) {
- var my = this,
- new_regex = function() {
- return new RegExp(my.otag +
- '(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?' +
- my.ctag + '+', 'g');
- },
- regex = new_regex(),
- tag_replace_callback = function(match, operator, name) {
- switch (operator) {
- case '!': // ignore comments
- return '';
- case '=': // set new delimiters, rebuild replace regexp
- my.set_delimiters(name);
- regex = new_regex();
- return '';
- case '>': // render partial
- return my.render_partial(name, context, partials);
- case '{': // the triple mustache is unescaped
- return my.find(name, context);
- default: // escape the value
- return my.escape(my.find(name, context));
- }
- },
- lines = template.split('\n'),
- i;
-
- for (i = 0; i < lines.length; i += 1) {
- lines[i] = lines[i].replace(regex, tag_replace_callback,
- this);
- if (!in_recursion) {
- this.send(lines[i]);
- }
- }
-
- if (in_recursion) {
- return lines.join('\n');
- }
- },
-
-
- set_delimiters: function(delimiters) {
- var dels = delimiters.split(' ');
- this.otag = this.escape_regex(dels[0]);
- this.ctag = this.escape_regex(dels[1]);
- },
-
-
- escape_regex: function(text) {
- // thank you Simon Willison
- if (!sRE) {
- var specials = [
- '/', '.', '*', '+', '?', '|',
- '(', ')', '[', ']', '{', '}', '\\'
- ];
- sRE = new RegExp(
- '(\\' + specials.join('|\\') + ')',
- 'g'
- );
- }
- return text.replace(sRE, '\\$1');
- },
-
-
- find: function(name, context) {
- var value;
-
- // TODO: don't assign to a parameter.
- name = this.trim(name);
-
- // TODO: make this a utility function.
- // Checks whether a value is thruthy or false or 0.
- function is_kinda_truthy(bool) {
- return bool === false || bool === 0 || bool;
- }
-
- if (is_kinda_truthy(context[name])) {
- value = context[name];
- } else if (is_kinda_truthy(this.context[name])) {
- value = this.context[name];
- }
-
- if (typeof value === 'function') {
- return value.apply(context);
- }
- if (value !== undefined) {
- return value;
- }
- return '';
- },
-
-
- includes: function(needle, haystack) {
- return haystack.indexOf(this.otag + needle) !== -1;
- },
-
-
- escape: function(s) {
- s = String(s === null ? '' : s);
- return s.replace(/&(?!\w+;)|["'<>\\]/g,
- function(s) {
- switch (s) {
- case '&':
- return '&amp;';
- case '\\':
- return '\\\\';
- case '"':
- return '&quot;';
- case '\'':
- return '&#39;';
- case '<':
- return '&lt;';
- case '>':
- return '&gt;';
- default:
- return s;
- }
- });
- },
-
-
- create_context: function(_context) {
- var iterator,
- ctx;
- if (this.is_object(_context)) {
- return _context;
- }
-
- iterator = '.';
- if (this.pragmas['IMPLICIT-ITERATOR']) {
- iterator = this.pragmas['IMPLICIT-ITERATOR'].iterator;
- }
- ctx = {};
- ctx[iterator] = _context;
- return ctx;
- },
-
-
- is_object: function(a) {
- return a && typeof a === 'object';
- },
-
-
- is_array: function(a) {
- return Object.prototype.toString.call(a) === '[object Array]';
- },
-
-
- trim: function(s) {
- return s.replace(/^\s*|\s*$/g, '');
- },
-
-
- map: function(array, fn) {
- var r, l, i;
- if (typeof array.map === 'function') {
- return array.map(fn);
- }
-
- r = [];
- l = array.length;
- for (i = 0; i < l; i += 1) {
- r.push(fn(array[i]));
- }
- return r;
- }
- }; // End of Renderer.prototype definition.
-
- return {
- name: 'mustache.js',
- version: '0.3.1-dev',
-
- to_html: function(template, view, partials, send_fun) {
- var renderer = new Renderer();
- if (send_fun) {
- renderer.send = send_fun;
- }
- renderer.render(template, view, partials);
- if (!send_fun) {
- return renderer.buffer.join('\n');
- }
- }
- };
- }()); // End of Mustache definition.
-
-
- /**
- * Class text.
- * @class MuAdapterClient
- * @private
- */
- function MuAdapter() {}
-
-
- /**
- * Renders the mustache template using the data provided.
- * @param {object} data The data to render.
- * @param {string} mojitType The name of the mojit type.
- * @param {string} tmpl The name of the template to render.
- * @param {object} adapter The output adapter to use.
- * @param {object} meta Optional metadata.
- * @param {boolean} more Whether there will be more content later.
- */
- MuAdapter.prototype.render = function(data,
- mojitType, tmpl, adapter, meta, more) {
-
- var handler,
- useCompiled = true,
- handlerArgs,
- ns = mojitType.replace(/\./g, '_');
-
- handler = function(id, obj) {
- var i,
- iC,
- queue = [],
- myAdapter,
- myMore,
- myData,
- myMeta;
-
- CACHE[tmpl] = obj.responseText;
-
- queue = QUEUE_POOL[tmpl].slice(0);
- QUEUE_POOL[tmpl] = undefined;
-
- for (i = 0, iC = queue.length; i < iC; i += 1) {
- myAdapter = queue[i].adapter;
- myMore = queue[i].more;
- myData = queue[i].data;
- myMeta = queue[i].meta;
-
- if (myMore) {
- myAdapter.flush(
- Mustache.to_html(obj.responseText, myData),
- myMeta
- );
- } else {
- myAdapter.done(
- Mustache.to_html(obj.responseText, myData),
- myMeta
- );
- }
- }
- };
-
- if (meta && meta.view && meta.view['content-path']) {
- // in this case, the view name doesn't necessarily relate to the
- // contents
- useCompiled = false;
- }
-
- handlerArgs = {
- data: data,
- adapter: adapter,
- meta: meta,
- more: more
- };
-
- if (!QUEUE_POOL[tmpl]) {
- QUEUE_POOL[tmpl] = [handlerArgs];
- } else {
- QUEUE_POOL[tmpl].push(handlerArgs);
- return;
- }
-
- // Do we have a compiled template?
- if (useCompiled && isCached(meta, ns)) {
- // Log we are using a compiled view
- Y.log('Using a compiled view for file "' + tmpl + '"', 'mojito',
- NAME);
- // If we do just hand it to the handler.
- handler(null, {
- responseText:
- YUI._mojito._cache.compiled[ns].views[meta.view.name]
- });
- // We don't need to do an IO call now so return.
- return;
- }
-
- // Now we do some bad stuff for iOS
- if (typeof window !== 'undefined') {
- tmpl = Y.mojito.util.iOSUrl(tmpl);
- }
-
- /*
- * YUI has a bug that returns failure on success with file://.
- * calls.
- */
- if (!CACHE[tmpl]) {
- Y.io(tmpl, {
- on: {
- complete: handler
- }
- });
- } else {
- handler(null, {responseText: CACHE[tmpl]});
- }
- };
-
-
- // TODO: refactor this into app/autoload/view-renderer.common.js?
- // (so that each view engine doesn't need to implement it itself)
- isCached = function(meta, ns) {
-
- // TODO: would a simple try/catch on the final one here be faster? It's
- // either there (and no error) or not (error) and we return accordingly.
- // try {
- // return YUI._mojito._cache.compiled[ns].views[meta.view.name] !==
- // 'undefined';
- // } catch {
- // return false;
- // }
+YUI.add('mojito-mu', function (Y, NAME) {
- // wow, what a checklist!
- return meta &&
- meta.view &&
- meta.view.name &&
- YUI._mojito._cache &&
- YUI._mojito._cache.compiled &&
- YUI._mojito._cache.compiled[ns] &&
- YUI._mojito._cache.compiled[ns].views &&
- YUI._mojito._cache.compiled[ns].views[meta.view.name];
- };
+ 'use strict';
- Y.namespace('mojito.addons.viewEngines').mu = MuAdapter;
+ Y.mojito.addons.viewEngines.mu = Y.mojito.addons.viewEngines.hb;
-}, '0.1.0', {requires: [
- 'mojito-util',
- 'io-base'
+}, '0.2.0', {requires: [
+ 'mojito-hb'
]});
View
84 lib/app/addons/view-engines/mu.server.js
@@ -12,84 +12,12 @@
/**
* @Module ViewEngines
*/
-YUI.add('mojito-mu', function(Y, NAME) {
+YUI.add('mojito-mu', function (Y, NAME) {
- var mu = YUI.require(__dirname + '/../../libs/Mulib/Mu'),
- fs = require('fs');
+ 'use strict';
+ Y.mojito.addons.viewEngines.mu = Y.mojito.addons.viewEngines.hb;
- /**
- * Class text.
- * @class MuAdapterServer
- * @private
- */
- function MuAdapter(viewId, options) {
- this.viewId = viewId;
- this.options = options || {};
- }
-
-
- MuAdapter.prototype = {
-
- /**
- * Renders the mustache template using the data provided.
- * @method render
- * @param {object} data The data to render.
- * @param {string} mojitType The name of the mojit type.
- * @param {string} tmpl The name of the template to render.
- * @param {object} adapter The output adapter to use.
- * @param {object} meta Optional metadata.
- * @param {boolean} more Whether there will be more content later.
- */
- render: function(data, mojitType, tmpl, adapter, meta, more) {
- var me = this,
- buffer = '',
- bufferOutput = (this.options.mu && this.options.mu.bufferOutput) || false,
- handleRender = function(err, output) {
- if (err) {
- throw err;
- }
-
- output.addListener('data', function(c) {
- if (!bufferOutput) {
- adapter.flush(c, meta);
- } else {
- buffer += c;
- }
- });
-
- output.addListener('end', function() {
- if (!more) {
- if (!bufferOutput) {
- buffer = '';
- }
- adapter.done(buffer, meta);
- } else {
- if (bufferOutput) {
- adapter.flush(buffer, meta);
- }
- }
- });
- };
-
- /*
- * We can't use pre-compiled Mu templates on the server :(
- */
-
- // If we don't have a compliled template, make one.
- Y.log('Rendering template "' + tmpl + '"', 'mojito', NAME);
- mu.render(tmpl, data, {cached: meta.view.cacheTemplates},
- handleRender);
- },
-
-
- compiler: function(tmpl, callback) {
- fs.readFile(tmpl, 'utf8', function (err, data) {
- callback(err, JSON.stringify(data));
- });
- }
- };
-
- Y.namespace('mojito.addons.viewEngines').mu = MuAdapter;
-
-}, '0.1.0', {requires: []});
+}, '0.2.0', {requires: [
+ 'mojito-hb'
+]});
View
59 lib/app/autoload/action-context.common.js
@@ -284,16 +284,12 @@ YUI.add('mojito-action-context', function(Y, NAME) {
store = opts.store,
actionFunction,
error,
- staticAppConfig,
- my;
+ my = this;
- my = this;
// HookSystem::StartBlock
Y.mojito.hooks.hook('actionContext', opts.adapter.hook, 'start', my, opts);
// HookSystem::EndBlock
- staticAppConfig = store.getStaticAppConfig();
-
// It's possible to setup a route that calls "foo.", which means that
// the default action in the instance should be used instead.
if (!command.action) {
@@ -311,13 +307,8 @@ YUI.add('mojito-action-context', function(Y, NAME) {
this.instance = command.instance;
this._adapter = opts.adapter;
- // in here we should whitelist the stuff we need
- this.staticAppConfig = {
- actionTimeout: staticAppConfig.actionTimeout,
- pathToRoot: staticAppConfig.pathToRoot,
- cacheViewTemplates: staticAppConfig.cacheViewTemplates,
- viewEngineOptions: staticAppConfig.viewEngine
- };
+ // pathToRoot, viewEngine, amoung others will be available through this.
+ this.staticAppConfig = store.getStaticAppConfig();
// Create a function which will properly delegate to the dispatcher to
// perform the actual processing.
@@ -413,13 +404,7 @@ YUI.add('mojito-action-context', function(Y, NAME) {
action = this.command.action,
mojitView,
renderer = null,
- contentType,
- contentPath,
-
- // static app configuration options
- pathToRoot = this.staticAppConfig.pathToRoot,
- cacheViewTemplates = this.staticAppConfig.cacheViewTemplates,
- viewEngineOptions = this.staticAppConfig.viewEngine || {};
+ contentType;
// HookSystem::StartBlock
Y.mojito.hooks.hook('actionContextDone', adapter.hook, 'start', this);
@@ -447,9 +432,6 @@ YUI.add('mojito-action-context', function(Y, NAME) {
meta.http.headers = meta.http.headers || {};
meta.view = meta.view || {};
- // Cache all templates by default
- meta.view.cacheTemplates = (cacheViewTemplates === false ? false : true);
-
// Check to see we need to serialize the data
if (meta.serialize && serializer[meta.serialize]) {
// Warning: this metod can change the "meta" object
@@ -562,34 +544,11 @@ YUI.add('mojito-action-context', function(Y, NAME) {
// Y.log('Rendering "' + meta.view.name + '" view for "' +
// (instance.id || '@' + instance.type) + '"', 'info', NAME);
- contentPath = mojitView['content-path'];
- // this is mainly used by html5app
- if (pathToRoot) {
- contentPath = pathToRoot + contentPath;
- }
-
- // HookSystem::StartBlock
- Y.mojito.hooks.hook('Render', adapter.hook, data, instance, contentPath);
- // HookSystem::EndBlock
-
- // optimize for server only
- if ('server' === context.runtime) {
- renderer = CACHE.renderers[mojitView.engine];
- if (!renderer) {
- // viewEngineOptions are app level
- CACHE.renderers[mojitView.engine] = renderer =
- new (Y.mojito.addons.viewEngines[mojitView.engine])('', viewEngineOptions);
- }
- renderer.viewId = meta.view.id;
- renderer.render(data, instance.type, contentPath, adapter, meta, more);
- } else {
- renderer = new Y.mojito.ViewRenderer(
- mojitView.engine,
- meta.view.id,
- viewEngineOptions
- );
- renderer.render(data, instance.type, contentPath, adapter, meta, more);
- }
+ // TODO: we might want to use a view renderer factory
+ // that can provide caching capabilities for better performance
+ // instead of creating objects over and over again per mojit instance
+ renderer = new Y.mojito.ViewRenderer(mojitView.engine, this.staticAppConfig.viewEngine);
+ renderer.render(data, instance, mojitView, adapter, meta, more);
} else {
View
4 lib/app/autoload/dispatch.client.js
@@ -208,6 +208,10 @@ YUI.add('mojito-dispatcher', function (Y, NAME) {
Y.log('Cannot expand instance "' + (command.instance.base || '@' +
command.instance.type) + '". Trying with the tunnel in case ' +
'it is a remote mojit.', 'info', NAME);
+ if (err) {
+ // logging the error
+ Y.log(err, 'warn', NAME);
+ }
my.rpc(command, adapter);
return;
View
10 lib/app/autoload/mojito-client.client.js
@@ -59,7 +59,7 @@ YUI.add('mojito-client', function(Y, NAME) {
// this is the heart of mojitProxy.render(), but it needs to be a separate
// function called once we have mojit type details
- function privateRender(mp, data, view, cb) {
+ function privateRender(mp, data, view, viewEngine, cb) {
var mojitView,
renderer;
@@ -73,7 +73,8 @@ YUI.add('mojito-client', function(Y, NAME) {
data.mojit_assets = data.mojit_assets || mp._assetsRoot;
mojitView = mp._views[view];
- renderer = new Y.mojito.ViewRenderer(mojitView.engine);
+ renderer = new Y.mojito.ViewRenderer(mojitView.engine, viewEngine);
+
Y.log('Rendering "' + view + '" in Binder', 'debug', NAME);
renderer.render(data, mp.type, mojitView['content-path'], {
buffer: '',
@@ -666,6 +667,7 @@ YUI.add('mojito-client', function(Y, NAME) {
doRender: function(mp, data, view, cb) {
+ var viewEngine = this.config.appConfig.viewEngine;
if (!mp._views || !mp._assetsRoot) {
this.resourceStore.expandInstance({type: mp.type}, mp.context,
function(err, typeInfo) {
@@ -678,10 +680,10 @@ YUI.add('mojito-client', function(Y, NAME) {
}
mp._views = typeInfo.views;
mp._assetsRoot = typeInfo.assetsRoot;
- privateRender(mp, data, view, cb);
+ privateRender(mp, data, view, viewEngine, cb);
});
} else {
- privateRender(mp, data, view, cb);
+ privateRender(mp, data, view, viewEngine, cb);
}
},
View
32 lib/app/autoload/store.server.js
@@ -640,7 +640,8 @@ YUI.add('mojito-resource-store', function(Y, NAME) {
details,
ress,
r,
- res;
+ res,
+ template;
if ('shared' === mojitType) {
throw new Error('Mojit name "shared" is special and isn\'t a real mojit.');
@@ -654,6 +655,7 @@ YUI.add('mojito-resource-store', function(Y, NAME) {
details.binders = {};
details.langs = {};
details.models = {};
+ details.partials = {};
details.views = {};
ress = this.getResources(env, ctx, { mojit: mojitType });
@@ -697,16 +699,23 @@ YUI.add('mojito-resource-store', function(Y, NAME) {
}
if (res.type === 'view') {
- if (!details.views[res.name]) {
- details.views[res.name] = {};
- }
- if (env === 'client') {
- details.views[res.name]['content-path'] = res.url;
+ template = {
+ 'content-path': (env === 'client' ?
+ this._libs.path.join(this._appConfigStatic.pathToRoot || '', res.url) :
+ res.source.fs.fullPath),
+ 'content': res.content,
+ 'engine': res.view.engine
+ };
+ // we want to separate partials from actual templates
+ // in case the engine supports partials
+ if (res.name.indexOf('partials/') === 0) {
+ // removing the "partials/" prefix
+ details.partials[this._libs.path.basename(res.name)] = template;
} else {
- details.views[res.name]['content-path'] = res.source.fs.fullPath;
+ details.views[res.name] = template;
+ details.views[res.name].assets = res.view.assets;
+ details.views[res.name].engine = res.view.engine;
}
- details.views[res.name].assets = res.view.assets;
- details.views[res.name].engine = res.view.engine;
}
}
@@ -1399,6 +1408,11 @@ YUI.add('mojito-resource-store', function(Y, NAME) {
}
res.name = this._libs.path.join(fs.subDirArray.join('/'), baseParts.join('.'));
res.id = [res.type, res.subtype, res.name].join('-');
+ // for performance reasons, we might want to preload all
+ // views in memory.
+ if (this._appConfigStatic.viewEngine && this._appConfigStatic.viewEngine.preloadTemplates) {
+ res.content = this._libs.fs.readFileSync(source.fs.fullPath, 'utf8');
+ }
return res;
}
View
19 lib/app/autoload/view-renderer.client.js
@@ -11,21 +11,21 @@ YUI.add('mojito-view-renderer', function (Y) {
'use strict';
+ var cache = {};
+
/*
- * Mojito's view renderer abstraction. Will craete a rendering
- * engine object depending on the 'type' specified.
+ * Mojito's view renderer abstraction. Will plugin in the specified view
+ * plugin to do the rendering, depending on the 'type' specified.
* @class ViewRenderer
* @namespace Y.mojito
* @constructor
* @param {String} type view engine addon type to use
- * @param {String} viewId
- * @param {Object} options
+ * @param {Object} options View engines configuration.
*/
- function Renderer(type, viewId, options) {
+ function Renderer(type, options) {
this._type = type || 'hb';
- this._viewId = viewId;
this._options = options;
- this._renderer = null;
+ this._renderer = cache[type];
}
@@ -44,7 +44,7 @@ YUI.add('mojito-view-renderer', function (Y) {
var my = this,
viewEngines = Y.mojito.addons.viewEngines,
fn = function () {
- callback(null, new (viewEngines[my._type])(my._viewId, my._options));
+ callback(null, new (viewEngines[my._type])(my._options));
};
// some perf optimization to avoid calling Y.use when
@@ -54,7 +54,7 @@ YUI.add('mojito-view-renderer', function (Y) {
if (viewEngines[my._type]) {
fn();
} else {
- // attaching the view engine in a form of mojito-mu, mojito-hb, etc
+ // attaching the view engine yui module
Y.use('mojito-' + this._type, function () {
if (!viewEngines[my._type]) {
callback(new Error('Invalid view engine: ' + my._type));
@@ -95,6 +95,7 @@ YUI.add('mojito-view-renderer', function (Y) {
return;
}
my._renderer = engine;
+ cache[my._type] = engine; // caching renderer instance
my._renderer.render(data, mojitType, tmpl, adapter, meta, more);
});
} else {
View
24 lib/app/autoload/view-renderer.server.js
@@ -7,26 +7,31 @@
/*jslint anon:true, nomen:true*/
/*global YUI*/
-YUI.add('mojito-view-renderer', function(Y) {
+YUI.add('mojito-view-renderer', function (Y) {
'use strict';
+ var cache = {};
+
/*
* Mojito's view renderer abstraction. Will plugin in the specified view
* plugin to do the rendering, depending on the 'type' specified.
* @class ViewRenderer
- * @namespace Y.mojit
+ * @namespace Y.mojito
* @constructor
* @param {String} type view engine addon type to use
- * @param {String} viewId
- * @param {Object} options
+ * @param {Object} options View engines configuration.
*/
- function Renderer(type, viewId, options) {
- type = type || 'hb';
- this._renderer = new (Y.mojito.addons.viewEngines[type])(viewId, options);
+ function Renderer(type, options) {
+ this._type = type || 'hb';
+ this._options = options;
+ if (!cache[this._type]) {
+ // caching renderer instance
+ cache[this._type] = new (Y.mojito.addons.viewEngines[this._type])(options);
+ }
+ this._renderer = cache[type];
}
-
Renderer.prototype = {
/*
@@ -41,12 +46,13 @@ YUI.add('mojito-view-renderer', function(Y) {
* @param {boolean} more Whether there will be more data to render
* later. (streaming)
*/
- render: function(data, mojitType, tmpl, adapter, meta, more) {
+ render: function (data, mojitType, tmpl, adapter, meta, more) {
// HookSystem::StartBlock
Y.mojito.hooks.hook('Render', adapter.hook, data, mojitType, tmpl, adapter, meta, more);
// HookSystem::EndBlock
this._renderer.render(data, mojitType, tmpl, adapter, meta, more);
}
+
};
Y.namespace('mojito').ViewRenderer = Renderer;
View
4 lib/app/commands/compile.js
@@ -45,8 +45,8 @@ var libpath = require('path'),
'async-queue': null,
'json-parse': null,
'json-stringify': null,
- 'mojito-hb': BASE + 'lib/app/addons/view-engines/mu.server.js',
- 'mojito-mu': BASE + 'lib/app/addons/view-engines/hb.server.js'
+ 'mojito-hb': BASE + 'lib/app/addons/view-engines/hb.server.js',
+ 'mojito-mu': BASE + 'lib/app/addons/view-engines/mu.server.js'
});
usage = [
View
224 lib/app/libs/Mulib/Mu.js
@@ -1,224 +0,0 @@
-var Path = require('path');
-var Mu = exports;
-var baseProto = ({}).__proto__;
-
-Mu.Parser = require('./mu/parser');
-Mu.Compiler = require('./mu/compiler');
-Mu.Preprocessor = require('./mu/preprocessor');
-
-Mu.cache = {};
-Mu.templateRoot = '';
-Mu.templateExtension = '';
-
-/**
- * Compiles a template into a executable function. This performs a parse check
- * to make sure that the template is well formed.
- *
- * @example
- *
- * myTemplate.mu
- *
- * Hello {{name}}!
- *
- * run.js
- *
- * var util = require('util'),
- * Mu = require('./lib/mu');
- * Mu.compile('myTemplate', function (compiled) {
- * compiled({name: 'Jim'}).addListener('data', function (c) { util.print(c) })
- * });
- *
- * Running run.js will produce:
- *
- * Hello Jim!
- *
- *
- * @param {String} filename The filename of the template to load, parse and
- * compile. This should not include the templateRoot or extension.
- * @param {Function} callback The callback that will be called on success or error.
- */
-Mu.compile = function Mu_compile(filename, callback) {
- Mu.Parser.parse(filename, Mu.templateRoot, Mu.templateExtension,
- function (err, parsed) {
- if (err) {
- return callback(err);
- }
-
- var pp = Mu.Preprocessor;
-
- try {
- var compiled = Mu.Compiler.compile(pp.check(pp.clean(parsed)));
- Mu.cache[filename] = compiled
- callback(undefined, compiled);
- } catch (e) {
- callback(e);
- }
- });
-}
-
-/**
- * Compiles a template as text instead of a filename. You are responsible for
- * providing your own partials as they will not be expanded via files.
- *
- * @example
- *
- * var util = require('util');
- * var tmpl = "Hello {{> part}}. What is your {{name}}?";
- * var partials = {part: "World"};
- * var compiled = Mu.compileText(tmpl, partials);
- * compiled({name: 'Jim'}).addListener('data', function (c) { util.puts(c); });
- *
- * @param {String} text The main template to compile.
- * @param {Object} partials The partials to expand when encountered. The object
- * takes the form of {partialName: partialText}
- * @returns {Function} The compiled template.
- */
-Mu.compileText = function Mu_compileText(text, partials) {
- var parsed = Mu.Parser.parseText(text, "main");
- var parsedPartials = {};
-
- for (var key in partials) {
- if (partials.hasOwnProperty(key)) {
- parsedPartials[key] = Mu.Parser.parseText(partials[key], key);
- }
- }
-
- var pp = Mu.Preprocessor;
-
- return Mu.Compiler.compile(
- pp.check(
- pp.clean(
- pp.expandPartialsFromMap(parsed, parsedPartials))));
-}
-
-/**
- * Shorcut to parse, compile and render a template.
- *
- * @param {String} filename The filename of the template to load, parse and
- * compile. This should not include the templateRoot or extension.
- * @param {Object} context The data that should be used when rendering the template.
- * @param {Function} callback The callback that will be called on success or error.
- */
-Mu.render = function Mu_render(filename, context, options, callback) {
- if (Mu.cache[filename] && options['cached'] !== false) {
- process.nextTick(function () {
- try {
- callback(undefined, Mu.cache[filename](context, options));
- } catch (e) {
- callback(e);
- }
- });
- } else {
- Mu.compile(filename, function (err, compiled) {
- if (err) {
- return callback(err);
- }
-
- try {
- callback(undefined, compiled(context, options));
- } catch (e) {
- callback(e);
- }
- });
- }
-}
-
-/**
- * HTML escapes a string.
- *
- * @param {String} string The string to escape.
- * @returns {String} The escaped string.
- */
-Mu.escape = function Mu_escape(string) {
- return string.replace(/[&<>"]/g, escapeReplace);
-}
-
-/**
- * Normalizes the param by calling it if it is a function, calling .toString
- * or simply returning a blank string.
- *
- * @param {Object} val The value to normalize.
- * @returns {String} The normalized value.
- */
-Mu.normalize = function Mu_normalize(context, name) {
- var val = context[name];
-
- if (typeof val === 'function') {
- val = val.call(context);
- }
-
- return (null === val || typeof val === 'undefined') ? '' : val.toString();
-}
-
-/**
- * Depending on the val passed into this function, different things happen.
- *
- * If val is a boolean, fn is called if it is true and the return value is
- * returned.
- * If val is an Array, fn is called once for each element in the array and
- * the strings returned from those calls are collected and returned.
- * Else if val is defined fn is called with the val.
- * Otherwise an empty string is returned.
- *
- * @param {Object} context The context that fn is called with if the val
- * is a true boolean.
- * @param {Boolean|Array|Object} val The value that decides what happens.
- * @param {Function} fn The callback.
- */
-Mu.enumerable = function Mu_enumerable(context, val, fn) {
- if (typeof(val) === 'function') {
- val = val.call(context);
- }
-
- if (null === val || typeof val === 'undefined') {
- return '';
- }
-
- if (typeof val === 'boolean') {
- return val ? fn(context) : '';
- }
-
- if (val instanceof Array) {
- var result = '';
- for (var i = 0, len = val.length; i < len; i++) {
- result += Mu.enumerable(context, val[i], fn);
- }
- return result;
- }
-
- if (typeof val === 'object' && val) {
- var oproto = insertProto(val, context);
- var ret = fn(val);
- oproto.__proto__ = baseProto;
-
- return ret;
- }
-
- return '';
-}
-
-
-// Private
-
-function insertProto(obj, newProto, replaceProto) {
- replaceProto = replaceProto || baseProto;
- var proto = obj.__proto__;
- while (proto !== replaceProto) {
- obj = proto;
- proto = proto.__proto__;
- }
-
- obj.__proto__ = newProto;
- return obj;
-}
-
-
-function escapeReplace(char) {
- switch (char) {
- case '<': return '&lt;';
- case '>': return '&gt;';
- case '&': return '&amp;';
- case '"': return '&quot;';
- default: return char;
- }
-}
View
134 lib/app/libs/Mulib/mu/compiler.js
@@ -1,134 +0,0 @@
-var util = require('util'),
- Path = require('path'),
- Mu = require('../Mu');
-
-exports.compile = compile;
-exports.compilePartial = compilePartial;
-
-function compile(parsed) {
- var M = Mu;
-
- var code =
- '(function COMPILED(context, options) {\n' +
- ' options = options || {};\n' +
- ' var Mu = M;\n' +
- ' var REE = new RenderEventEmitter(options);\n' +
- ' process.nextTick(function () {\n' +
- compilePartial(parsed) + '\n' +
- ' REE.close();\n' +
- ' });\n' +
- ' return REE;\n' +
- '})';
-
- var func = eval(code);
-
- func.LINES = Mu.Preprocessor.rebuildLines(parsed);
- func.SOURCE = Mu.Preprocessor.rebuildSource(parsed);
-
- return func;
-}
-
-function compilePartial(parsed) {
- return parsed.map(function (line) {
- var lineNumber = line.number;
- var tokens = line.tokens;
-
- return tokens.map(function (token) {
- var value = token.value;
-
- switch (token.type) {
-
- case 'string':
- return 'REE.write("' + value + '");';
-
- case 'variable':
- return 'REE.write(Mu.escape(Mu.normalize(context, "' + value + '")));';
-
- case 'unescaped':
- return 'REE.write(Mu.normalize(context, "' + value + '"));';
-
- case 'start_enumerable':
- return 'Mu.enumerable(' +
- 'context, context.' + value + ', function enum_' + value + '(context) {';
-
- case 'start_inverted_enumerable':
- return 'Mu.enumerable(' +
- 'context, !context.' + value + ', function enum_' + value + '(context) {';
-
- case 'end_enumerable':
- return '});';
-
- case 'partial':
- return compilePartial(value);
-
- }
- }).join('');
- }).join('');
-}
-
-
-function RenderEventEmitter(options) {
- this.chunkSize = options.chunkSize || 1024;
- this.buffer = '';
- this.paused = false;
- this.closed = false;
- this.emittedEOF = false;
-}
-util.inherits(RenderEventEmitter, process.EventEmitter);
-
-mixin(RenderEventEmitter.prototype, {
- write: function (chunk) {
- if (this.closed) {
- return;
- }
-
- this.buffer += chunk;
- this.emitChunks();
- },
-
- close: function () {
- this.closed = true;
- this.emitChunks();
- },
-
- pause: function () {
- this.paused = true;
- },
-
- resume: function () {
- this.paused = false;
- this.emitChunks();
- },
-
- emitChunks: function () {
- while (this.buffer.length >= this.chunkSize) {
- if (this.paused) {
- return;
- }
-
- var chunk = this.buffer.substring(0, this.chunkSize);
- this.buffer = this.buffer.substring(this.chunkSize);
-
- this.emit('data', chunk);
- }
-
- if (this.closed && !this.paused && this.buffer.length) {
- this.emit('data', this.buffer);
- this.buffer = '';
- }
-
- if (this.closed && !this.emittedEOF && !this.buffer.length) {
- this.emittedEOF = true;
- this.emit('end');
- }
- }
-});
-
-// Simple shallow object merge.
-function mixin(obj1, obj2) {
- for (var key in obj2) {
- if (obj2.hasOwnProperty(key)) {
- obj1[key] = obj2[key];
- }
- }
-}
View
207 lib/app/libs/Mulib/mu/parser.js
@@ -1,207 +0,0 @@
-var fs = require('fs'),
- Path = require('path'),
- Mu = require('../Mu');
-
-exports.parse = parse;
-exports.parseText = parseText;
-
-var otag = '{{';
-var ctag = '}}';
-
-function parse(filename, root, ext, callback) {
- fs.readFile(extension(root, filename, ext), function (err, contents) {
- if (err) {
- return callback(err);
- }
-
- Mu.Preprocessor.expandPartials(
- parseText(contents.toString("utf8"), filename),
- root,
- ext,
- function (err, res) {
- callback(err, res, filename);
- }
- );
- });
-}
-
-function parseText(text, filename) {
- filename = filename || "<raw text>";
- if (!text.split) {
- // Cast the passed Object back to aa String.
- text = new String(text);
- }
- var parsed = text.split('\n').map(function (val, i) {
- return parseLine(i+1, val, filename);
- });
- otag = '{{';
- ctag = '}}';
- return parsed;
-}
-
-function parseLine(lineNumber, lineText, filename) {
- if (lineNumber !== 1) {
- lineText = '\\n' + lineText;
- }
-