Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Support for handlebar partials #842

Merged
merged 26 commits into from

2 participants

@caridy
Owner

##Example:

./MojitName/views/
    foo.hb.html
./MojitName/views/partials/
    bar.hb.html

by following that structure, you should be able to define foo.hb.html like this:

<div id="{{mojit_view_id}}">
    {{> bar}}
</div>

Few more notes:

  • a partial view can be used by any view within the same context/mojit.
  • global partial views are also supported.

##Details:

  • adding support for partials in the store. there is now instance.partials with a similar structure than instance.views after expanding the instance.
  • views within partials/* folder are now consider partials, which are an special type of view.
  • adding support for application.json->viewEngine->preloadTemplates to preload any view in memory, and this is disabled by default.
  • consolidating application.json->viewEngine->cacheTemplates as a static config across the board.
  • adding support for preloadTemplates for HB client and server to avoid loading the template if is is part of the expanded instance.
  • adding support for partials on hb client and server.
  • view-renderer is now a class and a factory to avoid creating renderer objects over and over again.
  • removing viewId argument that is not really relevant for this abstraction.
  • using the new Y.mojito.ViewRenderer() factory to access the renderer instance.
  • removing unnecesary dependencies (mojito-perf).
  • removing unnecessary configuration that is not semantically correct for action-context or mojito-client like cacheTemplates and pathToRoot.

Second attempt for PR #683

caridy added some commits
@caridy caridy adding support for partials in the store. views within partials/* fol…
…der are now consider partials, which are an special type of view. Adding support for application.json->viewEngine->preloadTemplates to preload any view in memory, and this is disabled by default for now
374826d
@caridy caridy consolidating application.json->viewEngine->cacheTemplates as a stati…
…c config across the board. Adding support for preloadTemplates for HB client and server to avoid loading the template if is is part of the expanded instance. Adding support for partials on hb client and server.
9a40739
@caridy caridy view-renderer is now a class and a factory to avoid creating renderer…
… objects over and over again. removing viewId argument that is not really relevant for this abstraction.
da53ea3
@caridy caridy using the new Y.mojito.ViewRenderer factory to access the renderer in…
…stance. removing unnecesary dependencies and unnecessary configuration that is not semantically correct for action-context or mojito-client
6b2a5c4
@caridy caridy getting pr4 merged for partials 459bd50
@caridy caridy adding warning and moving assets for top level views to exclude them …
…from partials
a2311e4
@caridy caridy Merge branch 'develop-perf' of git://github.com/yahoo/mojito into par…
…tials
82fcda6
@caridy caridy Merge branch 'yui-name-collision' of github.com:caridy/mojito into gl…
…obal-models
baed690
@caridy caridy supporting registration of global models f5251aa
@caridy caridy Merge branch 'develop-perf' of git://github.com/yahoo/mojito into par…
…tials
5948e36
@caridy caridy Merge branch 'develop-perf' of git://github.com/yahoo/mojito into par…
…tials
e6499c5
@caridy caridy Merge branch 'develop-perf' of git://github.com/yahoo/mojito into par…
…tials
9d75095
@caridy caridy merge partials and 0.5.0GA 2044d1f
@caridy caridy Merge branch 'develop' of yahoo/mojito into partials 5721724
@caridy
Owner

This is set to land on 0.5.2.

@caridy
Owner

Ready for review.

@drewfish
Owner

From reading the description:

I worry about getting rid of pathToRoot. It was necessary for some users of mojito build html5app. Evidence strongly suggests that this was no longer being used (dead code) anyways.

@drewfish drewfish commented on the diff
lib/app/addons/view-engines/hb.client.js
((16 lines not shown))
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.
@drewfish Owner

Passing the whole instance, nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@drewfish drewfish commented on the diff
lib/app/autoload/store.server.js
((5 lines not shown))
- 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;
@drewfish Owner

This looks like it won't support subdirectories in views/partials/, which might be nice to have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@drewfish
Owner

+1 good stuff.

One comment about supporting subdirectories in views/partials/, but we can add that feature in the future when/if requested.

@caridy
Owner

Thanks @drewfish, I will resolve the conflicts and merge.

@caridy caridy merged commit d7c0e8b into yahoo:develop
@isao isao referenced this pull request from a commit in isao/mojito
@isao isao fix unit tests after newsboxes code was updated for pull #842 71e02ac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 30, 2012
  1. @caridy

    adding support for partials in the store. views within partials/* fol…

    caridy authored
    …der are now consider partials, which are an special type of view. Adding support for application.json->viewEngine->preloadTemplates to preload any view in memory, and this is disabled by default for now
  2. @caridy

    consolidating application.json->viewEngine->cacheTemplates as a stati…

    caridy authored
    …c config across the board. Adding support for preloadTemplates for HB client and server to avoid loading the template if is is part of the expanded instance. Adding support for partials on hb client and server.
  3. @caridy

    view-renderer is now a class and a factory to avoid creating renderer…

    caridy authored
    … objects over and over again. removing viewId argument that is not really relevant for this abstraction.
  4. @caridy

    using the new Y.mojito.ViewRenderer factory to access the renderer in…

    caridy authored
    …stance. removing unnecesary dependencies and unnecessary configuration that is not semantically correct for action-context or mojito-client
Commits on Nov 15, 2012
  1. @caridy
  2. @caridy
Commits on Nov 17, 2012
  1. @caridy
  2. @caridy
  3. @caridy
Commits on Nov 19, 2012
  1. @caridy
Commits on Nov 20, 2012
  1. @caridy
Commits on Nov 21, 2012
  1. @caridy
Commits on Dec 6, 2012
  1. @caridy

    merge partials and 0.5.0GA

    caridy authored
  2. @caridy
Commits on Dec 7, 2012
  1. @caridy
  2. @caridy
  3. @caridy
  4. @caridy
Commits on Jan 23, 2013
  1. @caridy

    updating PR with the latest from develop after 2.5.2, solving conflit…

    caridy authored
    …s generated by the hook system.
Commits on Jan 24, 2013
  1. @caridy
  2. @caridy

    cleaning tests mocks

    caridy authored
  3. @caridy
  4. @caridy
Commits on Jan 28, 2013
  1. @caridy
Commits on Jan 29, 2013
  1. @caridy
  2. @caridy
This page is out of date. Refresh to see the latest.
Showing with 367 additions and 1,977 deletions.
  1. +0 −7 README.md
  2. +0 −366 examples/sandbox/bindertime/assets/js/mu.client.js
  3. +2 −1  lib/app/addons/ac/partial.common.js
  4. +96 −28 lib/app/addons/view-engines/hb.client.js
  5. +102 −34 lib/app/addons/view-engines/hb.server.js
  6. +5 −496 lib/app/addons/view-engines/mu.client.js
  7. +6 −78 lib/app/addons/view-engines/mu.server.js
  8. +9 −50 lib/app/autoload/action-context.common.js
  9. +4 −0 lib/app/autoload/dispatch.client.js
  10. +6 −4 lib/app/autoload/mojito-client.client.js
  11. +23 −9 lib/app/autoload/store.server.js
  12. +10 −9 lib/app/autoload/view-renderer.client.js
  13. +15 −9 lib/app/autoload/view-renderer.server.js
  14. +2 −2 lib/app/commands/compile.js
  15. +0 −224 lib/app/libs/Mulib/Mu.js
  16. +0 −134 lib/app/libs/Mulib/mu/compiler.js
  17. +0 −207 lib/app/libs/Mulib/mu/parser.js
  18. +0 −270 lib/app/libs/Mulib/mu/preprocessor.js
  19. +3 −0  lib/config.json
  20. +0 −1  tests/base/mojito-test.js
  21. +1 −0  tests/fixtures/store/mojits/test_mojit_1/views/partials/test_3.hb.html
  22. +17 −13 tests/unit/lib/app/addons/ac/test-partial.common.js
  23. +7 −6 tests/unit/lib/app/autoload/test-action-context.common.js
  24. +4 −0 tests/unit/lib/app/autoload/test-store.server.js
  25. +6 −8 tests/unit/lib/app/autoload/test-view-renderer.client.js
  26. +49 −21 tests/unit/lib/app/autoload/test-view-renderer.server.js
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.
@drewfish Owner

Passing the whole instance, nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * @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,12 +18,14 @@ 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 = {
@@ -31,14 +33,15 @@ YUI.add('mojito-hb', function(Y, NAME) {
/**
* 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,10 +50,12 @@ 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) {
@@ -58,13 +63,74 @@ YUI.add('mojito-hb', function(Y, NAME) {
} 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;
@drewfish Owner

This looks like it won't support subdirectories in views/partials/, which might be nice to have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
} 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);