Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Handlebars support in mojito. #143

Closed
wants to merge 5 commits into from

4 participants

@caridy
Owner

Let's get the ball rolling for the handlebars support in mojito. This is just a preliminary job, I plan to add all the missing features into this branch to match the current mustache support. Feel free to reject the pull request for now.

For now, I also added the new yui->3.5.0 to the dependency list although only used by the handlebars engine implementation who requires yui/handlebars. Eventually, when the transition to the new YUI node package happens, should be easy to merge this changes.

Client side is mostly covered by yui 3.5.0, we just need to create the mojito wrapper for it.

TODO:

  • tests
  • figure out how to call done and composite.done without specifying the content-path as part of the view object.
  • create view-engines/hb.client.js as a wrapper for YUI 3.5.0 handlebars implementation

I don't plan to add support for {{> partial}} since that requires some extra machinery to resolves paths etc.

isao and others added some commits
@FabianFrank

great job @caridy let me know if you need help :-)

@caridy
Owner

If you want to try out the current implementation, check the demo here:

https://github.com/caridy/mojito-handlebars-demo

@caridy
Owner

@Schnitz of course I need help jajajaja

@caridy caridy adding support for mojito compile views when running handlebars templ…
…ates. in the case of the mustache templates they are just strings, but handlebars produce javascript functions so we have to do a little bit of hackery to get it done within the mojito scheme for cached objects.
9316b9d
@drewfish drewfish commented on the diff
source/lib/management/commands/compile.js
@@ -971,6 +991,12 @@ YuiModuleCacher.prototype.createNamespace = function(ns) {
_c: {},
cache: function(k, v) {
this._c[k] = v;
+ // exposing the cache object in case we want to do
@drewfish Owner
drewfish added a note

This comment should really be a comment introducing the cache() method (plus add yuidoc tags).

@caridy Owner
caridy added a note

agreed, I will adjust it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@drewfish drewfish commented on the diff
source/lib/app/addons/ac/partial.common.js
@@ -72,6 +72,32 @@ YUI.add('mojito-partial-addon', function(Y, NAME) {
}, meta);
},
+ /**
@drewfish Owner
drewfish added a note

Why does this ac.partial.compiler() method exist? Where/how might it be used?

@caridy Owner
caridy added a note

Today, with mustache, we can add "mojito-partial-addon" and call for ac.partial.compiler('foo') to get the stringify version of the template, and pass it to the client side if we want to use that there.

With handlebars, we should be able to do the exact same. Here is an example:

https://github.com/caridy/mojito-handlebars-demo/blob/master/mojits/HelloHandlebars/controller.server.js

The difference between mustache and handlebars at this point is that mustache produce a string that has to be processed at the client side by the mustache engine, while handlebars produce a Javascript code that represents a function, and by calling that function, it will return the rendered template.

But from the mojito point of view, they are the same thing, so, ac.partial should behave the same.

@drewfish Owner
drewfish added a note

Why would anyone want to call ac.partial.compiler() and send the results to the client side? mojito compile views is meant to take care compiling the views, so that nothing special has to be done in the controller.

@caridy Owner
caridy added a note

@drewfish "mojito compile views" is pretty dummy today (it doesn't select specific views per context, etc). Shaker does a better job in this area, but still I feel that, sometimes, having the ability to serialize a partial and sending it over to the client side to execute a particular process without all the hazard of compiling all of them during build time is a nice to have feature, specially if you have some sort of logic to pick the right view programmatically rather than a fixed value.

@drewfish Owner

Hmm... OK. I would say that if ac.partial.compiler() is sufficiently useful (as you argue) then we should let it drive a change to the view engine API and require that -all- view engines (including mu) support it (via the new compiler method).

@drewfish Owner

And we should perhaps update mojito compile views to be better about contexts, etc. It seems a little dangerous to invent a new feature when the old one isn't sufficient, when we could instead fix up the old feature. We don't want to end up with a million slightly-different ways of doing things.

@caridy Owner
caridy added a note

I will argue that this type of rollup process (mojito compile views) should be made by shaker rather than mojito itself. But about the specific feature we are discussing here (partial->compiler), I added to facilitate (in the near future) the use of {{> partial}} for handlebars templates. At this point, mojito-partial-addon is the only external addon that knows how to resolve (based on the name of the view) the partial fullpath, etc. So, if we are going to support this feature anytime soon, we need a way to pre-compute partials before executing the composite view. That being said, we can stick to the plan of having no compiler routine at the addon level if you feel strong about this.

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

looks like this won't apply cleanly now, sorry. but really looking forward to it.
side note: since views are .hb. it doesn't need to be 100% interoperable with the mojito mu implementation

@caridy
Owner

@isao, the idea is to have the same support since mustache is a subset of handlebars, so we can drop mustache implementation and still support *.mu.html files as an alias of *.hb.html. I don't see why we need to have both when handlebars can perfectly handle mustache templates.

@drewfish
Owner

Sorry, I didn't mean to let this pull request linger so long.

If you made a pull request that just added handlebars, it would likely be accepted pretty quickly.

The ac.partial.compiler() stuff worries me. I'm worried that it is taking Mojito in the wrong direction. If/when Mojito supports "partial" (which I think of as "includes") in templating engines, that shouldn't have to involve the controller (or even addons or ActionContext) -- it should just work (from the view of the developer using Mojito).

@caridy
Owner

sure, let's close this one then. I will pull another next week!

@isao

Looking forward to it, thanks Caridy

@isao isao closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 24, 2012
  1. @isao
  2. @drewfish
Commits on May 1, 2012
  1. @caridy

    adding support for handlebars for the server runtime only for now. ad…

    caridy authored
    …ding yui 3.5.0 as a dependency so we can require handlebars for precompilation etc.
Commits on May 5, 2012
  1. @caridy

    adding support for handlebars partials and precompile capabilities fo…

    caridy authored
    …r all template engines through the compiler method in partial.compiler and renderer.compiler.
Commits on May 6, 2012
  1. @caridy

    adding support for mojito compile views when running handlebars templ…

    caridy authored
    …ates. in the case of the mustache templates they are just strings, but handlebars produce javascript functions so we have to do a little bit of hackery to get it done within the mojito scheme for cached objects.
This page is out of date. Refresh to see the latest.
View
4 DEPRECATIONS.md
@@ -15,10 +15,6 @@ Currently Deprecated
### Deprecated but Available
-* (2012-04-23) The `autoload/` directory is going away in favor of
-`yui_modules/`, which better reflects its contents. Everthing else about it is
-the same, only the name has changed. You can start using `yui_modules/` today.
-
* (2012-04-23) The `.guid` member of Mojito metadata (such as binder metadata)
is going away. Often there's an associated member which more specifically
expresses the intent of the unique ID (for example `.viewId` or `.instanceId`).
View
26 source/lib/app/addons/ac/partial.common.js
@@ -72,6 +72,32 @@ YUI.add('mojito-partial-addon', function(Y, NAME) {
}, meta);
},
+ /**
@drewfish Owner
drewfish added a note

Why does this ac.partial.compiler() method exist? Where/how might it be used?

@caridy Owner
caridy added a note

Today, with mustache, we can add "mojito-partial-addon" and call for ac.partial.compiler('foo') to get the stringify version of the template, and pass it to the client side if we want to use that there.

With handlebars, we should be able to do the exact same. Here is an example:

https://github.com/caridy/mojito-handlebars-demo/blob/master/mojits/HelloHandlebars/controller.server.js

The difference between mustache and handlebars at this point is that mustache produce a string that has to be processed at the client side by the mustache engine, while handlebars produce a Javascript code that represents a function, and by calling that function, it will return the rendered template.

But from the mojito point of view, they are the same thing, so, ac.partial should behave the same.

@drewfish Owner
drewfish added a note

Why would anyone want to call ac.partial.compiler() and send the results to the client side? mojito compile views is meant to take care compiling the views, so that nothing special has to be done in the controller.

@caridy Owner
caridy added a note

@drewfish "mojito compile views" is pretty dummy today (it doesn't select specific views per context, etc). Shaker does a better job in this area, but still I feel that, sometimes, having the ability to serialize a partial and sending it over to the client side to execute a particular process without all the hazard of compiling all of them during build time is a nice to have feature, specially if you have some sort of logic to pick the right view programmatically rather than a fixed value.

@drewfish Owner

Hmm... OK. I would say that if ac.partial.compiler() is sufficiently useful (as you argue) then we should let it drive a change to the view engine API and require that -all- view engines (including mu) support it (via the new compiler method).

@drewfish Owner

And we should perhaps update mojito compile views to be better about contexts, etc. It seems a little dangerous to invent a new feature when the old one isn't sufficient, when we could instead fix up the old feature. We don't want to end up with a million slightly-different ways of doing things.

@caridy Owner
caridy added a note

I will argue that this type of rollup process (mojito compile views) should be made by shaker rather than mojito itself. But about the specific feature we are discussing here (partial->compiler), I added to facilitate (in the near future) the use of {{> partial}} for handlebars templates. At this point, mojito-partial-addon is the only external addon that knows how to resolve (based on the name of the view) the partial fullpath, etc. So, if we are going to support this feature anytime soon, we need a way to pre-compute partials before executing the composite view. That being said, we can stick to the plan of having no compiler routine at the addon level if you feel strong about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * This method compiles the "view" specified.
+ * The "view" must be the name of one of the files in the current
+ * Mojits "views" folder.
+ * @param {string} view The view name to be used for rendering.
+ * @param {Object} opts Optional argument depending on the engine.
+ * @return {string} compiled view.
+ */
+ compiler: function(view, opts) {
+ var renderer,
+ mojitView,
+ instance = this.command.instance;
+
+ if (!instance.views[view]) {
+ Y.log('View "' + view + '" not found', 'debug', NAME);
+ return;
+ }
+
+ mojitView = instance.views[view];
+ renderer = new Y.mojito.ViewRenderer(mojitView.engine);
+
+ Y.log('Compiling "' + view + '" view for "' + (instance.id || '@' +
+ instance.type) + '"', 'debug', NAME);
+
+ return renderer.compiler(mojitView['content-path'], opts);
+ },
/**
* This method calls the current mojit's controller with the "action"
View
81 source/lib/app/addons/view-engines/hb.server.js
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved.
+ * Copyrights licensed under the New BSD License.
+ * See the accompanying LICENSE file for terms.
+ */
+
+
+/*jslint anon:true, sloppy:true, nomen:true, node:true*/
+/*global YUI*/
+
+
+YUI.add('mojito-hb', function(Y, NAME) {
+
+ var fs = require('fs'),
+ HB = require('yui/handlebars').Handlebars,
+ cache = YUI.namespace('Env.Handlebars');
+
+ /**
+ * Class text.
+ * @class HandleBarsAdapterServer
+ * @private
+ */
+ function HandleBarsAdapter(viewId) {
+ this.viewId = viewId;
+ }
+
+ 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} 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 str, precompiled, template, result;
+
+ // apply a very dummy cache
+ if (!cache[tmpl] || !meta.view.cacheTemplates) {
+ str = fs.readFileSync(tmpl, 'utf8');
+ cache[tmpl] = {
+ raw: str,
+ template: HB.compile(str)
+ };
+ }
+ adapter.flush(cache[tmpl].template(data), meta);
+ Y.log('render complete for view "' +
+ this.viewId + '"',
+ 'mojito', 'qeperf');
+ adapter.done('', meta);
+ },
+
+ /**
+ * Precompiles the handlebars template.
+ * @param {string} tmpl The name of the template to render.
+ * @param {Object} opts Optional argument depending on the engine.
+ * @return {string} the string representation of the template
+ * that can be sent to the client side.
+ */
+ compiler: function(tmpl, opts) {
+ // this is a little bit of black magic. We return an object
+ // that can be transformed into a string to facilitate the
+ // use of this compiled view through mojito compile view command.
+ // This is the way to differenciate regular strings
+ // (like mustache compiled views) from handlebars javascript
+ // functions.
+ return {
+ _d: HB.precompile(fs.readFileSync(tmpl, 'utf8')),
+ toString: function () {
+ return this._d;
+ }
+ };
+ }
+ };
+
+ Y.namespace('mojito.addons.viewEngines').hb = HandleBarsAdapter;
+
+}, '0.1.0', {requires: []});
View
12 source/lib/app/autoload/view-renderer.common.js
@@ -40,7 +40,19 @@ YUI.add('mojito-view-renderer', function(Y) {
*/
render: function(data, mojitType, tmpl, adapter, meta, more) {
this._renderer.render(data, mojitType, tmpl, adapter, meta, more);
+ },
+
+ /*
+ * Compiles a view into an string representation.
+ * @param {String} tmpl path to template to be precompiled
+ * engine.
+ * @param {Object} opts Optional argument depending on the engine.
+ * @return {String} the precompiled view.
+ */
+ compiler: function(tmpl, opts) {
+ return this._renderer.compiler(tmpl, opts);
}
+
};
Y.mojito.ViewRenderer = Renderer;
View
37 source/lib/management/commands/compile.js
@@ -559,12 +559,32 @@ compile.views = function(context, options, callback) {
renderer = new (Y.mojito.addons.viewEngines[engine])();
if (typeof renderer.compiler === 'function') {
- renderedView = JSON.parse(
- renderer.compiler(source).toString()
- );
+ // there is not need to stringify this anymore
+ // since we have a custom method to take care of that
+ // down the road.
+ renderedView = renderer.compiler(source);
+
yuiModuleCacheWriter.createNamespace('compiled.' +
mojitNs + '.views').cache(viewName,
- renderedView);
+ renderedView).toString = function () {
+ var chunks = [],
+ viewName;
+ // to support precompiled views as javascript
+ // we need to mess around a little bit. If the compiled version
+ // is an object exposing the compiled view through .toString() method,
+ // the we should be able to inject functions into the cache structure.
+ for (viewName in this) {
+ if (typeof this[viewName] !== 'function') {
+ chunks.push('"' + viewName + '":' + this[viewName].toString());
+ }
+ }
+ // we are synthetically creating the result string for each group of
+ // views. We intentionally call for toString() for every view so we
+ // can create specific compilers that produce functions or any other
+ // structure intentionally.
+ return '{' + chunks.join(',') + '}';
+ };
+
}
}
}
@@ -971,6 +991,12 @@ YuiModuleCacher.prototype.createNamespace = function(ns) {
_c: {},
cache: function(k, v) {
this._c[k] = v;
+ // exposing the cache object in case we want to do
@drewfish Owner
drewfish added a note

This comment should really be a comment introducing the cache() method (plus add yuidoc tags).

@caridy Owner
caridy added a note

agreed, I will adjust it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ // some black magic to bend the way we produce cached
+ // content. Specifically to cache compiled views as
+ // javascript function rather than regular strings.
+ // Ding Ding, I'm talking about handlebars precompile.
+ return this._c;
}
};
}
@@ -987,9 +1013,10 @@ YuiModuleCacher.prototype.dump = function() {
namespaces = this.namespaces;
Object.keys(namespaces).forEach(function(ns) {
+ var item = namespaces[ns]._c;
s += ' YUI.namespace("_mojito._cache.' + ns + '");\n';
s += ' YUI._mojito._cache.' + ns + ' = ' +
- JSON.stringify(namespaces[ns]._c) + ';\n';
+ ( item.toString ? item.toString() : JSON.stringify(item) ) + ';\n';
});
s += '});\n';
return s;
View
3  source/package.json
@@ -19,7 +19,8 @@
"express": "2.5.2",
"jsdom": "0.2.0",
"mime": "1.2.4",
- "yui3": "0.7.12"
+ "yui3": "0.7.12",
+ "yui": "3.5.0"
},
"keywords": [
"framework",
Something went wrong with that request. Please try again.