Skip to content
This repository

Y.Template and Y.Template.Micro - Generic template API and simple ERB/Underscore-style JavaScript templates #230

Closed
wants to merge 10 commits into from

12 participants

Ryan Grove Dav Glass Eric Ferraiuolo Luke Smith Simon Højberg Jay Shirley Juan Ignacio Dopazo Ryuichi Okumura Nicholas Husher Yuri Zapuchlak Dmitri S Daniel Stockman
Ryan Grove
Collaborator

While working on a new widget recently, I found myself wanting a string-based templating solution that was more advanced than Y.Lang.sub() or Y.substitute(), but without the overhead of Y.Handlebars. I needed support for interpolation, if/else branching, and looping, but only for a few very small templates. Underscore-style templates (more familiar as ERB-style templates to Rubyists) seemed like exactly the right fit.

These commits add a new Y.Template class which provides a generic template engine API, and a Y.Template.Micro static class which provides a simple micro-templating engine. Y.Template can be used to compile, precompile, render, and revive precompiled templates using Handlebars or Y.Template.Micro.

Y.Template

Using with Y.Template.Micro (the default template engine):

YUI().use('template', function (Y) {
    var micro = new Y.Template(),
        html  = micro.render('<%= data.message %>', {message: 'hello!'});

    // ...
});

Using with Handlebars:

YUI().use('template-base', 'handlebars', function (Y) {
    var handlebars = new Y.Template(Y.Handlebars),
        html       = handlebars.render('{{message}}', {message: 'hello!'});

    // ...
});

See the API docs for further details.

Y.Template.Micro

Provides simple ERB/Underscore-style micro-templating. Can be used standalone or via Y.Template.

Within a template, <% ... %> is used to insert a block of JavaScript code, <%= ... %> evaluates and prints an expression as HTML-escaped output, and <%== ... %> evaluates and prints an expression as raw, unescaped output. Properties on the data object passed to a template function are made available within the template on the data variable.

Here's a simple template that renders a list:

<ul class="<%= data.classNames.list %>">
    <% Y.Array.each(data.items, function (item) { %>
        <li><%= item %></li>
    <% }); %>
</ul>

And here's the code to compile the template and render it (assume listTemplate is a string containing the template above):

YUI().use('template-micro', function (Y) {
    var compiled = Y.Template.Micro.compile(listTemplate),
        html;

    // Render the compiled template to HTML.
    html = compiled({
        classNames: {list: 'yui3-list'},
        items     : ['one', 'two', 'three', 'four']
    });

    // And again, with a different set of data.
    html = compiled({
        classNames: {list: 'yui3-list'},
        items     : ['a', 'b', 'c', 'd']
    });
});

Precompilation is supported too, so Micro can be used to precompile templates at build time or on the server.

// `precompile()` returns a string containing JavaScript code that will evaluate
// to a template function. It can be written to a file, served to a remote
// client, etc.
var source = Y.Template.Micro.precompile(listTemplate);

Performance

Y.Template.Micro is significantly faster than both Underscore and Handlebars at both compiling and rendering templates. See http://jsperf.com/y-template-vs-others/6

Running Locally

The built template, handlebars, and yui modules are intentionally not included in this pull request in order to keep the diffs clean. Before testing this change, you'll need to build them.

$ cd src/handlebars && shifter
$ cd src/template && shifter
$ cd src/yui && shifter
added some commits August 24, 2012
Ryan Grove Initial commit of Y.template().
ERB and Underscore-style templating for YUI.
5ee431d
Ryan Grove Trailing comma. 884289f
Ryan Grove
Collaborator

Oh travisbot. When will you learn to read?

Dav Glass
Owner

Ha! yogi picked up your tests automatically since they are in there ;)

I'll have to check and see if the new Travis Env var is there for pull requests. Then I can have shifter auto build the modules before running tests. I'll look into this tomorrow ;)

Eric Ferraiuolo
Owner

I worry that this muddies the waters with respect to YUI and templates. Also, putting JavaScript in the templates might encourage people to do bad things; someone like yourself will do so wisely, but I worry about the average developer uses this as a hammer.

I think we should be encouraging people to move templates outside of their JavaScript code and this seems like it might encourage people to keep them internal (as does Y.Lang.sub().)

I'm curious to hear your arguments of why this should be in core vs. Gallery though — it might just be that this is bringing back horrible memories of ASP for me :)

Ryan Grove
Collaborator

@ericf: Long story short? Because today was the second time in the past few weeks that I've found myself writing a widget that needed templates more complex than Y.Lang.sub() could handle, but not as complex as Y.Handlebars would have been. After spending some time going down the path of precompiling Handlebars templates at build time and baking them into my modules (and then seeing how much my code size ballooned as a result), I decided we needed an option in between.

For me, Y.template() is the Goldilocks option. It's simple, small, fast, and powerful. That also means that, yes, you can use it to shoot your foot off. But YUI is already a pretty big gun, and this is just one of many bullets. If we wanted to protect everyone from themselves, we'd be politicians, not JavaScript developers. ;)

Y.Lang.sub() is great for strings. Y.Handlebars() is great for web pages and complicated views. I see Y.template() being great for widgets that don't need complex views, but do need to render HTML (like the list above) with simple conditional or looping logic.

Given a choice between doing something unholy with Y.Lang.sub(), doing something way overkill with Y.Handlebars, or doing something quick, simple, and readable with Y.template(), I'll choose Y.template() every time.

Why should it be in core? Well, beyond what I said above, if it's not in core, then the widgets I'm working on can't go into core either. And I think you might want them. ;)

Eric Ferraiuolo
Owner

If precompiled Handlebars templates were easy to work with and the plumbing was baked into core (and Shifter), would you still feel the need for Y.template()?

If you assume the handlebars-base module was always going to be loaded (so don't count it), would the precompiled Handlebars template functions still add too much bloat to your Widgets?

Ryan Grove
Collaborator

@ericf: Even if handlebars-base (~3KB) were always loaded (which I find unlikely), a precompiled Handlebars template for the list example above would still come to 1,380 bytes, whereas the precompiled Y.template() template is only 260 bytes.

https://gist.github.com/3459956 vs. https://gist.github.com/3459993

Even for a tiny template, Handlebars has a lot of boilerplate overhead. Y.template() doesn't.

Eric Ferraiuolo
Owner

@rgrove Gzipped the templates are 183B and 462B respectively. So in this case it's 60% smaller than Handlebars, as the template grows in size, the Handlebars boilerplate will hopefully become less of the overall size of a template.

The tentative plan I've began talking about with people is moving all Widget templates to Handlebars, precompiling the template(s) for a module into a separate YUI module as part of the build process, and making the template-module a dependency of the Widget.

I would like to see us be able take a single approach for HTML templates and I think the fact that you've created Y.template() means there's more to consider for smaller templates and how we should approach templates for all Widgets. It would be great to have a single solution we can use for everything, including for people to use for their app templates :-/

I think this means we should have this discussion now instead of in Q4 when this work on library-wide templates is scheduled.

Ryan Grove
Collaborator

@ericf: Actually, I'd expect the opposite to be true. Handlebars has significant boilerplate code for each {{token}}, whereas Y.template() doesn't. So as the size of the template grows, the Handlebars boilerplate will continue to balloon. Luckily, it's very repetitive so it should gzip well, but even so, that's significant.

I don't think there's anything wrong with having multiple templating solutions to meet different needs, as long as the reasons for using each are clear. Handlebars has its place, as does Y.template(). I see Handlebars being a more attractive solution for view management (partials and helpers are big wins there) and for server-side templates. But I think Y.template() is more attractive for the kinds of client-side widgets YUI has right now.

Luke Smith
Collaborator

I expect Handlebars would be overkill for widgets in general, and including that much overhead for the (albeit unrealistic) "putting a single X on the page requires YK of YUI code?" story is bad for adoption. It reenforces the perennial "YUI is heavy/over-engineered" complaint.

Y.template feels like the right balance for use by widgets for markup creation. Adding vastly more logic in the templates (via Handlebars) is a direction we don't want to go because that easily balloons into PHP, and will likely hinder configurability of individual widgets. For simple string substitution, Y.Lang.sub is fine, but the lack of a compile step is a missed opportunity for widget performance. The security consciousness of <%= ... %> is a safe default for markup generation that saves boilerplate code and/or prevents developer forgetfulness. The conditionals I'm on the fence about, because they are the first step on the slippery slope toward Handlebars-esque feature richness and complexity. But I do think it's a reasonable place to draw the line in the sand and will prove practical and useful for widgets.

DataTable might benefit from Y.template.

Simon Højberg

Side discussion.
Should this be called something other than Y.template() ? I feel like Y.template should be used for something that is template-engine agnostic, and could be configured to use specific engines. Could make it a target for gallery modules that added new template engines.

Ryan Grove
Collaborator

@hojberg: I doubt we're likely to see (or need) something like that in YUI, but I'm open to other names if there's something that's clearly better.

Jay Shirley

I find myself agreeing with nearly all of the comments here. Y.template would discourage usage of Handlebars, when in many cases Handlebars would be the right solution. I can see in cases of widgets, the currently named Y.template.

However, having the name as global as Y.template without substituting the engine seems overly empowering to the (very slightly) more dangerous option.

I do think the name is key. Y.Handlebars exists specifically for this, but perhaps Y.Template as a top level namespace is better, and this can be something like:

var myTemplate = Y.Template.simple("Hello <%= name =%>!");
myTemplate({ name : 'World' });

(Then a wrapper for Y.Template.handlebars that calls Y.Handlebars.compile, for a consistent interface).

Dav Glass
Owner
Juan Ignacio Dopazo
Collaborator

I understand the motivations, but I agree with Eric and Dav. It reminds me of my old PHP days as well. I don't like that it doesn't look at all like the existing options in YUI. So I ask myself, what do we need that's between Y.Lang.sub and Handlebars? Iteration? Mapping objects?

Why not Mustache instead? It's a subset of Handlebars and weighs much less:

  • Handlebars minified with Uglify: 29.5 KB
  • Mustache minified with Uglify: 4.39 KB
Simon Højberg

@jshirley exactly what I was thinking!

Ryan Grove
Collaborator

@jshirley: I'd be okay with moving this to the Y.Template namespace, and I think your comments tie in nicely with what Dav said...

@davglass: Y.Lang.sub() is still useful. Y.substitute() is useless, crappy, and needs to die. I'm not interested in adding a compat shim so that Y.template() can work like Y.substitute(), because I think the way Y.substitute() works is stupid.

I definitely see your point about Y.template() and Y.Handlebars needing to share common ground in how templates are compiled and loaded. I'll give this some thought. Whatever happens though, I don't want Y.template() to become something more complex than it is now -- there's huge value in a tiny (<1KB) module that can meet all simple templating needs with no confusing abstractions.

@juandopazo: What does Mustache provide that Y.template() doesn't to make it worth its 4.39KB? That's over four times the size of Y.template(), and even larger than the handlebars-base module needed to render precompiled Handlebars templates. If it's the syntax you like, then Y.template() can easily support that. Mustache's many shortcomings are why we ended up choosing Handlebars as YUI's heavy-weight templating solution. One of its biggest shortcomings is that templates are always interpreted at render time and can't be compiled, which would make Mustache both slower and bigger than using precompiled Handlebars templates.

I have to accept some of the blame for the current situation. Back when we were trying to decide on a templating solution for YUI, I had the urge to investigate Underscore-style templating, but ended up being swayed by the power and strict separation of concerns that Handlebars provided. In hindsight, I think Underscore-style templating would have been a better fit for YUI's client-side templating needs, and on the server where Handlebars templates make more sense it's easy enough to use the non-YUI Handlebars module.

I won't say I regret bringing Handlebars into YUI (or writing all that documentation -- gawd), but I do think it's not the best template language for client-side widgets, and I think Y.template() is.

Ryan Grove Generic Y.Template API and Y.template() -> Y.Template.Micro
Moves Y.template() to a static Y.Template.Micro namespace and
adds Y.Template, a generic template engine API that supports
Y.Handlebars, Y.Template.Micro, and any other template engine
that adheres to a simple API interface.
d3ce3d7
Ryan Grove
Collaborator

Okay dudes. My latest commit incorporates your feedback and adds a generic template API for both micro-templates and Handlebars. It also moves Y.template() to a static Y.Template.Micro namespace. I've updated the pull request description above with details of the new changes. Let me know what you think.

Ryuichi Okumura
Collaborator

I feel that need to add docs/* if pull in this module. And, I would like to expect to get benefit from this one, but I think to need to some notes about "JavaScript in the templates" on docs as Eric remarks.

Ryan Grove
Collaborator

I'll definitely add a user guide if this gets pulled in. Wasn't going to write one on spec though -- too much work if things change.

Simon Højberg

@rgrove great stuff!

Ryan Grove
Collaborator

Updated benchmarks: http://jsperf.com/y-template-vs-others/4/

Turns out Y.Template.Micro using the variable option to disable the use of with inside compiled templates is faster than both Underscore and Handlebars.

Dav Glass
Owner

@rgrove I like the changes, great work as always!

Ryuichi Okumura
Collaborator

I'd like to share talk about this Pull Request.
Part 1: http://www.youtube.com/watch?v=dLZ3AGD5d1w
Part 2: http://www.youtube.com/watch?v=qO2xCmnY53g

Ryan Grove
Collaborator

I filed an enhancement ticket for shifter describing how I think it should support automatic template precompilation for YUI modules using Y.Template. Feel free to weigh in on that discussion as well: yui/shifter#11

src/template/js/template-micro.js
((138 lines not shown))
  138
+        // Replace the token placeholders with code.
  139
+        .replace(/\ufffe(\d+)\uffff/g, function (match, index) {
  140
+            return blocks[parseInt(index, 10)];
  141
+        }) +
  142
+
  143
+        "';";
  144
+
  145
+    if (!options.variable) {
  146
+        source = "with(data){\n" + source + "}";
  147
+    }
  148
+
  149
+    source = "var $t='';\n" + source + "\nreturn $t;";
  150
+
  151
+    // Compile the template source into an executable function.
  152
+    template = this.revive(new Function('Y', options.variable || 'data',
  153
+            'htmlEscaper', source));
2
Daniel Stockman
evocateur added a note August 29, 2012

Continuing thought from davglass/shifter#11:

This is where Y.Template.Micro#precompile() falls down, in my view, when used as a precompilation engine for shifter. It would be preferable to pass { "source": true } or something as the options for compile(), and only revive() the generated function when source is false. (Conversely, when the source option is true, return only the generated source, as with Handlebars#precompile())

if (options.source) {
    template = "function(Y," + (options.variable || 'data') +
        ",htmlEscaper){\n" + source + "\n}";
} else {
    template = this.revive(new Function ('Y', options.variable || 'data',
            'htmlEscaper', source));
}
Ryan Grove Collaborator
rgrove added a note August 29, 2012

This seems reasonable, although I don't think it has any direct bearing on what we were discussing re. shifter (except that this will make precompilation slightly more efficient). The question of whether precompilation should be possible without requiring YUI at all is still a separate issue, and I still think the answer to that is no.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
added some commits August 29, 2012
Ryan Grove Don't rely on `with`, don't do extra work during precompilation.
Removing the `with` block in compiled templates makes rendering way
faster, bumping Micro ahead of both Underscore and Handlebars in
rendering speed.
173bf22
Ryan Grove Make Y.Template.Handlebars an alias for Y.Handlebars 7d9b214
Ryan Grove
Collaborator

No movement on this pull request despite the two hour meeting we had last week in which the team consensus was that this should be merged.

What's blocking this?

Simon Højberg

I'd love so much to see this merged!

Nicholas Husher

Read through the code this morning. I would like to see this in core, especially with the ability to plug multiple render engines into Y.Template.

Yuri Zapuchlak

Personally, I don't see myself using the Micro templating language in the near future (handlebars is working well for my current needs), but I think the Y.Template engine framework is a very valuable addition as it would standardize how people go about creating modules (gallery or private) to add YUI support for any of the other popular (or niche) templating languages out there.

So I agree with many on this thread. This would be great to see pulled in.

Eric Ferraiuolo
Owner

I will merge this in after our 3.7.0 code freeze (which we're in right now). @rgrove sorry that the window passed us by for getting this into 3.7.0, and I'd like to stick firmly to our code freeze process.

This will also give me a chance to play around with the new APIs and really push on them. Having Y.Template will work out nicely with this ticket #2532428, which is something I'll be tackling for the next development sprint.

Once the code freeze is lifted, I'll merge this in and we'll start planning the first 3.8.0 preview release. Getting a preview release out quickly will get Y.Template and Y.Template.Micro on the CDN, and more importantly, be easy for other developers to try out and give feedback.

Ryan Grove
Collaborator

Thanks @ericf!

Dmitri S

is the freeze over now as 3.7.0 has been released?

Eric Ferraiuolo
Owner

@dmitris We are still in a code freeze post release to make sure we don't have to issue any emergency patches.

Simon Højberg

Possible to get this merged?

Eric Ferraiuolo
Owner
Simon Højberg

thanks @ericf !

Eric Ferraiuolo
Owner

I think that "template" is the wrong noun for the object returned from calling the Y.Template() constructor function. I found this confusing as to what to name my var which holds the instance object returned from Y.Template() while filling out the rest of the unit tests.

Really what we're doing is creating a template engine instance. The following seems more correct to me:

var engine = Y.Template.createEngine(Y.Handlebars),
    html   = engine.render('{{message}}', {message: 'hello!'});
Ryan Grove
Collaborator

@ericf: I think of it like this: Y.Template.Micro is an engine. An instance of Y.Template.Micro is a template.

Incidentally, what unit tests are you filling out?

Eric Ferraiuolo
Owner

@rgrove Agreed, so what is the return value from Y.Template()? To me it returns a generic engine wrapper, therefore I see it as more of an engine factory than returning a template instance.

My updates to the unit tests are very simple, edge case tests which get template to 100% line, function, statement, and branch coverage! \o/

Ryan Grove
Collaborator

@ericf: Awesome, thanks for filling out the test coverage!

The return value from new Y.Template() is still a template, it's just a template that has some internal logic that tells it, at instantiation, what engine it should use. Y.Template is a generic engine wrapper, but an instance of Y.Template is a wrapped template.

Eric Ferraiuolo
Owner

I'd argue that "{{message}}" is the template from the code snippet in my above comment.

Ryan Grove
Collaborator

@ericf: Agreed. "{{message}}" is a template, and Y.Handlebars.compile("{{message}}") is a compiled template. Y.Handlebars and Y.Template.Micro are engines, and the return value of new Y.Template() is an engine. I've made some clarifications in the doc comments.

Eric Ferraiuolo
Owner

@rgrove I'll make you a deal… if you stub out a TOC for a Template/Template.Micro user guide, I'll fill in the details. I think we can get away with one user guide to service both Y.Template() and Y.Template.Micro. After I fill in the details you can copy-edit and add polish, if you wish (and have time to do so.)

Ryan Grove
Collaborator

@ericf You're too kind! I'll have something for you later today.

Ryan Grove
Collaborator

@ericf I pushed a first pass at a user guide TOC. Let me know what you think.

Eric Ferraiuolo
Owner

This has been merged into 3.x.

Eric Ferraiuolo ericf closed this November 06, 2012
Simon Højberg

Awesome! :dancers:

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

Showing 10 unique commits by 1 author.

Aug 24, 2012
Ryan Grove Initial commit of Y.template().
ERB and Underscore-style templating for YUI.
5ee431d
Ryan Grove Trailing comma. 884289f
Ryan Grove Make precompiled templates work. 331245b
Aug 25, 2012
Ryan Grove Generic Y.Template API and Y.template() -> Y.Template.Micro
Moves Y.template() to a static Y.Template.Micro namespace and
adds Y.Template, a generic template engine API that supports
Y.Handlebars, Y.Template.Micro, and any other template engine
that adheres to a simple API interface.
d3ce3d7
Aug 29, 2012
Ryan Grove Don't rely on `with`, don't do extra work during precompilation.
Removing the `with` block in compiled templates makes rendering way
faster, bumping Micro ahead of both Underscore and Handlebars in
rendering speed.
173bf22
Ryan Grove Make Y.Template.Handlebars an alias for Y.Handlebars 7d9b214
Oct 01, 2012
Ryan Grove Support switch statements inside templates. 6b97684
Oct 15, 2012
Ryan Grove Template docs: Clarify the diff. between a template and an engine. 66162f1
Oct 17, 2012
Ryan Grove Roughing out a user guide TOC for Template. 30b1658
Oct 31, 2012
Ryan Grove Template: Don't print `undefined` for falsy values.
Can't believe I forgot this.
1ecbf4f
This page is out of date. Refresh to see the latest.
9  src/handlebars/js/yui-handlebars-base-after.js
@@ -33,9 +33,6 @@ Y.Handlebars = Handlebars;
33 33
 
34 34
 Handlebars.VERSION += '-yui';
35 35
 
36  
-// The rest of this file is just API docs for methods defined in Handlebars
37  
-// itself.
38  
-
39 36
 /**
40 37
 Registers a helper function that will be made available to all templates.
41 38
 
@@ -113,3 +110,9 @@ Converts a precompiled template into a renderable template function.
113 110
 @param {Function} template Precompiled Handlebars template function.
114 111
 @return {Function} Compiled template function.
115 112
 */
  113
+
  114
+// Alias for Y.Handlebars.template(), used by Y.Template.
  115
+Handlebars.revive = Handlebars.template;
  116
+
  117
+// Make Y.Template.Handlebars an alias for Y.Handlebars.
  118
+Y.namespace('Template').Handlebars = Handlebars;
7  src/template/HISTORY.md
Source Rendered
... ...
@@ -0,0 +1,7 @@
  1
+Template Change History
  2
+=======================
  3
+
  4
+3.8.0
  5
+-----
  6
+
  7
+* Initial release.
5  src/template/README.md
Source Rendered
... ...
@@ -0,0 +1,5 @@
  1
+Template
  2
+========
  3
+
  4
+Provides a generic API for using template engines such as Handlebars and
  5
+`Y.Template.Micro`.
17  src/template/build.json
... ...
@@ -0,0 +1,17 @@
  1
+{
  2
+    "name": "template",
  3
+
  4
+    "builds": {
  5
+        "template-base": {
  6
+            "jsfiles": [
  7
+                "template-base.js"
  8
+            ]
  9
+        },
  10
+
  11
+        "template-micro": {
  12
+            "jsfiles": [
  13
+                "template-micro.js"
  14
+            ]
  15
+        }
  16
+    }
  17
+}
13  src/template/docs/component.json
... ...
@@ -0,0 +1,13 @@
  1
+{
  2
+    "name"       : "template",
  3
+    "displayName": "Template",
  4
+    "description": "Provides a generic template engine API and a micro-templating language similar to ERB and Underscore templates.",
  5
+    "author"     : ["rgrove", "ericf"],
  6
+
  7
+    "tags": [
  8
+        "utility", "beta", "template", "templating", "micro", "handlebars",
  9
+        "erb", "underscore", "view"
  10
+    ],
  11
+
  12
+    "use": ["template"]
  13
+}
69  src/template/docs/index.mustache
... ...
@@ -0,0 +1,69 @@
  1
+<div class="intro">
  2
+<p>
  3
+The Template component provides `Y.Template`, a generic template engine API, and `Y.Template.Micro`, a string-based micro-templating language similar to <a href="http://ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html">ERB</a> and <a href="http://underscorejs.org/#template">Underscore</a> templates.
  4
+</p>
  5
+</div>
  6
+
  7
+{{>getting-started}}
  8
+
  9
+<h2>Using `Template`</h2>
  10
+
  11
+<h3>Quick Start</h3>
  12
+
  13
+<p>
  14
+A template engine takes a template&mdash;usually in the form of a string&mdash;and some data, and renders the data into the template to produce an HTML or text string. Using templates to keep markup and structure separate from content encourages reuse and can make code easier to read and maintain, and in many cases faster.
  15
+</p>
  16
+
  17
+<p>
  18
+`Y.Template` provides a common API that can be used to compile and render templates with a variety of template engines. The two template engines included in YUI are <a href="../handlebars/">Handlebars</a> and [[#Using Template.Micro|Template.Micro]].
  19
+</p>
  20
+
  21
+<p>[example of rendering a Template.Micro template]</p>
  22
+
  23
+<p>[example of rendering a Handlebars template]</p>
  24
+
  25
+<h3>Generic Template API</h3>
  26
+
  27
+<p>[overview of the purpose of Y.Template's generic API -- a widget's default templates can be Handlebars templates, but you can override with Micro templates, etc.]</p>
  28
+
  29
+<h3>Instantiating a Template Engine</h3>
  30
+
  31
+<h3>Compiling and Rendering Templates</h3>
  32
+
  33
+<h3>Precompiling and Reviving Templates</h3>
  34
+
  35
+<h3>Creating a Custom Template Engine</h3>
  36
+
  37
+<p>[how to create a template engine that conforms to the `Y.Template` API interface]</p>
  38
+
  39
+<h2>Using `Template.Micro`</h2>
  40
+
  41
+<p>[Overview of what Template.Micro is and what kinds of use cases it's suited for -- when you'd use it vs. Handlebars, etc.]</p>
  42
+
  43
+<h3>Template Syntax</h3>
  44
+
  45
+<h4>Basic Expressions</h4>
  46
+
  47
+<h4>HTML Escaping</h4>
  48
+
  49
+<h4>Inline Code &amp; Code Blocks</h4>
  50
+
  51
+<h3>Compiling and Rendering Templates</h3>
  52
+
  53
+<p>[may be redundant; possibly just refer to the Y.Template section above?]</p>
  54
+
  55
+<h3>Precompiling and Reviving Templates</h3>
  56
+
  57
+<p>[may be redundant; possibly just refer to the Y.Template section above?]</p>
  58
+
  59
+<h3>Customizing Template Syntax</h3>
  60
+
  61
+<p>[describe how to override Y.Template.Micro's regexes to customize the template syntax if desired]</p>
  62
+
  63
+<h2>Using Templates in Custom Components</h2>
  64
+
  65
+<p>[guidelines for bundling templates with custom views, widgets, etc. -- how to build, compile, and make them available on a namespace]</p>
  66
+
  67
+<h2>Best Practices</h2>
  68
+
  69
+<p>[recommended best practices for templates -- don't embed too much logic, don't embed huge template strings in JS, compile once; render often, etc.]</p>
132  src/template/js/template-base.js
... ...
@@ -0,0 +1,132 @@
  1
+/**
  2
+Virtual rollup of the `template-base` and `template-micro` modules.
  3
+
  4
+@module template
  5
+@main template
  6
+@since 3.8.0
  7
+**/
  8
+
  9
+/**
  10
+Provides a generic API for using template engines such as Handlebars and
  11
+`Y.Template.Micro`.
  12
+
  13
+@module template
  14
+@submodule template-base
  15
+@since 3.8.0
  16
+**/
  17
+
  18
+/**
  19
+Provides a generic API for using template engines such as Handlebars and
  20
+`Y.Template.Micro`.
  21
+
  22
+### Examples
  23
+
  24
+Using with `Y.Template.Micro` (the default template engine):
  25
+
  26
+    YUI().use('template', function (Y) {
  27
+        var micro = new Y.Template(),
  28
+            html  = micro.render('<%= data.message %>', {message: 'hello!'});
  29
+
  30
+        // ...
  31
+    });
  32
+
  33
+Using with Handlebars:
  34
+
  35
+    YUI().use('template-base', 'handlebars', function (Y) {
  36
+        var handlebars = new Y.Template(Y.Handlebars),
  37
+            html       = handlebars.render('{{message}}', {message: 'hello!'});
  38
+
  39
+        // ...
  40
+    });
  41
+
  42
+@class Template
  43
+@param {Mixed} [engine=Y.Template.Micro] Template engine to use, such as
  44
+    `Y.Template.Micro` or `Y.Handlebars`. Defaults to `Y.Template.Micro` if not
  45
+    specified.
  46
+@constructor
  47
+@since 3.8.0
  48
+**/
  49
+
  50
+function Template(engine) {
  51
+    /**
  52
+    Template engine class.
  53
+
  54
+    @property {Mixed} engine
  55
+    **/
  56
+    this.engine = engine || Y.Template.Micro;
  57
+
  58
+    if (!this.engine) {
  59
+        Y.error('No template engine loaded.');
  60
+    }
  61
+}
  62
+
  63
+Template.prototype = {
  64
+    /**
  65
+    Compiles a template with the current template engine and returns a compiled
  66
+    template function.
  67
+
  68
+    @method compile
  69
+    @param {String} text Template text to compile.
  70
+    @param {Object} [options] Options to pass along to the template engine. See
  71
+        template engine docs for options supported by each engine.
  72
+    @return {Function} Compiled template function.
  73
+    **/
  74
+    compile: function (text, options) {
  75
+        return this.engine.compile(text, options);
  76
+    },
  77
+
  78
+    /**
  79
+    Precompiles a template with the current template engine and returns a string
  80
+    containing JavaScript source code for the precompiled template.
  81
+
  82
+    @method precompile
  83
+    @param {String} text Template text to compile.
  84
+    @param {Object} [options] Options to pass along to the template engine. See
  85
+        template engine docs for options supported by each engine.
  86
+    @return {String} Source code for the precompiled template.
  87
+    **/
  88
+    precompile: function (text, options) {
  89
+        return this.engine.precompile(text, options);
  90
+    },
  91
+
  92
+    /**
  93
+    Compiles and renders a template with the current template engine in a single
  94
+    step, and returns the rendered result.
  95
+
  96
+    @method render
  97
+    @param {String} text Template text to render.
  98
+    @param {Object} data Data object to provide when rendering the template.
  99
+    @param {Object} [options] Options to pass along to the template engine. See
  100
+        template engine docs for options supported by each engine.
  101
+    @return {String} Rendered result.
  102
+    **/
  103
+    render: function (text, data, options) {
  104
+        if (this.engine.render) {
  105
+            return this.engine.render(text, data, options);
  106
+        }
  107
+
  108
+        return this.engine.compile(text, options)(data, options);
  109
+    },
  110
+
  111
+    /**
  112
+    Revives a precompiled template function into an executable template function
  113
+    using the current template engine. The precompiled code must already have
  114
+    been evaluated; this method won't evaluate it for you.
  115
+
  116
+    @method revive
  117
+    @param {Function} precompiled Precompiled template function.
  118
+    @param {Object} [options] Options to pass along to the template engine. See
  119
+        template engine docs for options supported by each engine.
  120
+    @return {Function} Compiled template function.
  121
+    **/
  122
+    revive: function (precompiled, options) {
  123
+        return this.engine.revive ? this.engine.revive(precompiled, options) :
  124
+                precompiled;
  125
+    }
  126
+};
  127
+
  128
+// Copy existing namespaced properties from Y.Template to the Template function
  129
+// if Y.Template already exists, then make the function the new Y.Template.
  130
+// This ensures that other modules can safely add stuff to the Y.Template
  131
+// namespace even if they're loaded before this one.
  132
+Y.Template = Y.Template ? Y.mix(Template, Y.Template) : Template;
212  src/template/js/template-micro.js
... ...
@@ -0,0 +1,212 @@
  1
+/**
  2
+Adds the `Y.Template.Micro` template engine, which provides fast, simple
  3
+string-based micro-templating similar to ERB or Underscore templates.
  4
+
  5
+@module template
  6
+@submodule template-micro
  7
+@since 3.8.0
  8
+**/
  9
+
  10
+/**
  11
+Fast, simple string-based micro-templating engine similar to ERB or Underscore
  12
+templates.
  13
+
  14
+@class Template.Micro
  15
+@since 3.8.0
  16
+@static
  17
+**/
  18
+
  19
+// This code was heavily inspired by Underscore.js's _.template() method
  20
+// (written by Jeremy Ashkenas), which was in turn inspired by John Resig's
  21
+// micro-templating implementation.
  22
+
  23
+var Micro = Y.namespace('Template.Micro');
  24
+
  25
+/**
  26
+Default options for `Y.Template.Micro`.
  27
+
  28
+@property {Object} options
  29
+
  30
+    @param {RegExp} [options.code] Regex that matches code blocks like
  31
+        `<% ... %>`.
  32
+    @param {RegExp} [options.escapedOutput] Regex that matches escaped output
  33
+        tags like `<%= ... %>`.
  34
+    @param {RegExp} [options.rawOutput] Regex that matches raw output tags like
  35
+        `<%== ... %>`.
  36
+    @param {RegExp} [options.stringEscape] Regex that matches characters that
  37
+        need to be escaped inside single-quoted JavaScript string literals.
  38
+
  39
+@static
  40
+@since 3.8.0
  41
+**/
  42
+Micro.options = {
  43
+    code         : /<%([\s\S]+?)%>/g,
  44
+    escapedOutput: /<%=([\s\S]+?)%>/g,
  45
+    rawOutput    : /<%==([\s\S]+?)%>/g,
  46
+    stringEscape : /\\|'|\r|\n|\t|\u2028|\u2029/g
  47
+};
  48
+
  49
+/**
  50
+Compiles a template string into a JavaScript function. Pass a data object to the
  51
+function to render the template using the given data and get back a rendered
  52
+string.
  53
+
  54
+Within a template, use `<%= ... %>` to output the value of an expression (where
  55
+`...` is the JavaScript expression or data variable to evaluate). The output
  56
+will be HTML-escaped by default. To output a raw value without escaping, use
  57
+`<%== ... %>`, but be careful not to do this with untrusted user input.
  58
+
  59
+To execute arbitrary JavaScript code within the template without rendering its
  60
+output, use `<% ... %>`, where `...` is the code to be executed. This allows the
  61
+use of if/else blocks, loops, function calls, etc., although it's recommended
  62
+that you avoid embedding anything beyond basic flow control logic in your
  63
+templates.
  64
+
  65
+Properties of the data object passed to a template function are made available
  66
+on a `data` variable within the scope of the template. So, if you pass in
  67
+the object `{message: 'hello!'}`, you can print the value of the `message`
  68
+property using `<%= data.message %>`.
  69
+
  70
+@example
  71
+
  72
+    YUI().use('template-micro', function (Y) {
  73
+        var template = '<ul class="<%= data.classNames.list %>">' +
  74
+                           '<% Y.Array.each(data.items, function (item) { %>' +
  75
+                               '<li><%= item %></li>' +
  76
+                           '<% }); %>' +
  77
+                       '</ul>';
  78
+
  79
+        // Compile the template into a function.
  80
+        var compiled = Y.Template.Micro.compile(template);
  81
+
  82
+        // Render the template to HTML, passing in the data to use.
  83
+        var html = compiled({
  84
+            classNames: {list: 'demo'},
  85
+            items     : ['one', 'two', 'three', 'four']
  86
+        });
  87
+    });
  88
+
  89
+@method compile
  90
+@param {String} text Template text to compile.
  91
+@param {Object} [options] Options. If specified, these options will override the
  92
+    default options defined in `Y.Template.Micro.options`. See the documentation
  93
+    for that property for details on which options are available.
  94
+@return {Function} Compiled template function. Execute this function and pass in
  95
+    a data object to render the template with the given data.
  96
+@static
  97
+**/
  98
+Micro.compile = function (text, options) {
  99
+    var blocks     = [],
  100
+        tokenClose = "\uffff",
  101
+        tokenOpen  = "\ufffe",
  102
+        source;
  103
+
  104
+    options = Y.merge(Micro.options, options);
  105
+
  106
+    // Parse the input text into a string of JavaScript code, with placeholders
  107
+    // for code blocks. Text outside of code blocks will be escaped for safe
  108
+    // usage within a double-quoted string literal.
  109
+    source = "var $b='',$t='" +
  110
+
  111
+        // U+FFFE and U+FFFF are guaranteed to represent non-characters, so no
  112
+        // valid UTF-8 string should ever contain them. That means we can freely
  113
+        // strip them out of the input text (just to be safe) and then use them
  114
+        // for our own nefarious purposes as token placeholders!
  115
+        //
  116
+        // See http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Noncharacters
  117
+        text.replace(/\ufffe|\uffff/g, '')
  118
+
  119
+        .replace(options.rawOutput, function (match, code) {
  120
+            return tokenOpen + (blocks.push("'+\n((" + code + ")||$b)+\n'") - 1) + tokenClose;
  121
+        })
  122
+
  123
+        .replace(options.escapedOutput, function (match, code) {
  124
+            return tokenOpen + (blocks.push("'+\n$e((" + code + ")||$b)+\n'") - 1) + tokenClose;
  125
+        })
  126
+
  127
+        .replace(options.code, function (match, code) {
  128
+            return tokenOpen + (blocks.push("';\n" + code + "\n$t+='") - 1) + tokenClose;
  129
+        })
  130
+
  131
+        .replace(options.stringEscape, "\\$&")
  132
+
  133
+        // Replace the token placeholders with code.
  134
+        .replace(/\ufffe(\d+)\uffff/g, function (match, index) {
  135
+            return blocks[parseInt(index, 10)];
  136
+        })
  137
+
  138
+        // Remove noop string concatenations that have been left behind.
  139
+        .replace(/\n\$t\+='';\n/g, '\n') +
  140
+
  141
+        "';\nreturn $t;";
  142
+
  143
+    // If compile() was called from precompile(), return precompiled source.
  144
+    if (options.precompile) {
  145
+        return "function (Y, $e, data) {\n" + source + "\n}";
  146
+    }
  147
+
  148
+    // Otherwise, return an executable function.
  149
+    return this.revive(new Function('Y', '$e', 'data', source));
  150
+};
  151
+
  152
+/**
  153
+Precompiles the given template text into a string of JavaScript source code that
  154
+can be evaluated later in another context (or on another machine) to render the
  155
+template.
  156
+
  157
+A common use case is to precompile templates at build time or on the server,
  158
+then evaluate the code on the client to render a template. The client only needs
  159
+to revive and render the template, avoiding the work of the compilation step.
  160
+
  161
+@method precompile
  162
+@param {String} text Template text to precompile.
  163
+@param {Object} [options] Options. If specified, these options will override the
  164
+    default options defined in `Y.Template.Micro.options`. See the documentation
  165
+    for that property for details on which options are available.
  166
+@return {String} Source code for the precompiled template.
  167
+@static
  168
+**/
  169
+Micro.precompile = function (text, options) {
  170
+    options || (options = {});
  171
+    options.precompile = true;
  172
+
  173
+    return this.compile(text, options);
  174
+};
  175
+
  176
+/**
  177
+Compiles and renders the given template text in a single step.
  178
+
  179
+This can be useful for single-use templates, but if you plan to render the same
  180
+template multiple times, it's much better to use `compile()` to compile it once,
  181
+then simply call the compiled function multiple times to avoid recompiling.
  182
+
  183
+@method render
  184
+@param {String} text Template text to render.
  185
+@param {Object} data Data to pass to the template.
  186
+@param {Object} [options] Options. If specified, these options will override the
  187
+    default options defined in `Y.Template.Micro.options`. See the documentation
  188
+    for that property for details on which options are available.
  189
+@return {String} Rendered result.
  190
+@static
  191
+**/
  192
+Micro.render = function (text, data, options) {
  193
+    return this.compile(text, options)(data);
  194
+};
  195
+
  196
+/**
  197
+Revives a precompiled template function into a normal compiled template function
  198
+that can be called to render the template. The precompiled function must already
  199
+have been evaluated to a function -- you can't pass raw JavaScript code to
  200
+`revive()`.
  201
+
  202
+@method revive
  203
+@param {Function} precompiled Precompiled template function.
  204
+@return {Function} Revived template function, ready to be rendered.
  205
+@static
  206
+**/
  207
+Micro.revive = function (precompiled) {
  208
+    return function (data) {
  209
+        data || (data = {});
  210
+        return precompiled.call(data, Y, Y.Escape.html, data);
  211
+    };
  212
+};
20  src/template/meta/template.json
... ...
@@ -0,0 +1,20 @@
  1
+{
  2
+    "template": {
  3
+        "use": [
  4
+            "template-base",
  5
+            "template-micro"
  6
+        ]
  7
+    },
  8
+
  9
+    "template-base": {
  10
+        "requires": [
  11
+            "yui-base"
  12
+        ]
  13
+    },
  14
+
  15
+    "template-micro": {
  16
+        "requires": [
  17
+            "escape"
  18
+        ]
  19
+    }
  20
+}
218  src/template/tests/unit/assets/template-test.js
... ...
@@ -0,0 +1,218 @@
  1
+YUI.add('template-test', function (Y) {
  2
+
  3
+var Assert = Y.Assert,
  4
+    Micro  = Y.Template.Micro,
  5
+
  6
+    templateSuite = Y.TemplateTestSuite = new Y.Test.Suite('Template'),
  7
+    microSuite    = new Y.Test.Suite('Micro');
  8
+
  9
+// -- Y.Template.Micro ---------------------------------------------------------
  10
+
  11
+// -- Methods ------------------------------------------------------------------
  12
+microSuite.add(new Y.Test.Case({
  13
+    name: 'Methods',
  14
+
  15
+    'compile() should return a compiled template function': function () {
  16
+        var compiled = Micro.compile('test');
  17
+
  18
+        Assert.isFunction(compiled, 'return value should be a function');
  19
+        Assert.areSame('test', compiled(), 'executing the function should render the template');
  20
+    },
  21
+
  22
+    'compile() should return precompiled source if the `precompile` option is `true`': function () {
  23
+        Assert.isString(Micro.compile('test', {precompile: true}));
  24
+    },
  25
+
  26
+    'compile() options should be overridable': function () {
  27
+        var compiled = Micro.compile('{{data.foo}}', {escapedOutput: /\{\{([\s\S]+?)\}\}/g});
  28
+        Assert.areSame('bar', compiled({foo: 'bar'}));
  29
+    },
  30
+
  31
+    'precompile() should return precompiled template code': function () {
  32
+        Assert.areSame(
  33
+            "function (Y, $e, data) {\nvar $b='',$t='test';\nreturn $t;\n}",
  34
+            Micro.precompile('test')
  35
+        );
  36
+    },
  37
+
  38
+    'precompile() should respect compile options': function () {
  39
+        Assert.areSame(
  40
+            "function (Y, $e, data) {\nvar $b='',$t=''+\n$e((data.test)||$b)+\n'';\nreturn $t;\n}",
  41
+
  42
+            Micro.precompile('{{data.test}}', {
  43
+                escapedOutput: /\{\{([\s\S]+?)\}\}/g
  44
+            })
  45
+        )
  46
+    },
  47
+
  48
+    'render() should compile and render in a single step': function () {
  49
+        Assert.areSame('bar baz', Micro.render('<%=data.a%> <%=data.b%>', {a: 'bar', b: 'baz'}));
  50
+    },
  51
+
  52
+    'render() should respect compile options': function () {
  53
+        Assert.areSame('foo', Micro.render('{{data.a}}', {a: 'foo'}, {escapedOutput: /\{\{([\s\S]+?)\}\}/g}));
  54
+    },
  55
+
  56
+    'revive() should revive a precompiled template into an executable template function': function () {
  57
+        eval('var precompiled=' + Micro.precompile('<%== data.a %> <%= data.b %>') + ';');
  58
+
  59
+        var revived = Micro.revive(precompiled);
  60
+
  61
+        Assert.areSame('bar baz', revived({a: 'bar', b: 'baz'}));
  62
+    },
  63
+
  64
+    '`this` object in the compiled template should refer to the data object': function () {
  65
+        Assert.areSame('bar', Micro.render('<%= this.foo %>', {foo: 'bar'}));
  66
+    }
  67
+}));
  68
+
  69
+// -- Syntax -------------------------------------------------------------------
  70
+microSuite.add(new Y.Test.Case({
  71
+    name: 'Syntax',
  72
+
  73
+    '<% ... %> should be rendered as a block of JavaScript code': function () {
  74
+        var compiled = Micro.compile('<% if (data.display) { %>hello<% } %>');
  75
+
  76
+        Assert.areSame('hello', compiled({display: true}));
  77
+        Assert.areSame('', compiled({display: false}));
  78
+    },
  79
+
  80
+    '<% ... %> should allow line breaks': function () {
  81
+        var compiled = Micro.compile("<%\nif (data.display) {\n%>hello<%\n}\n%>");
  82
+
  83
+        Assert.areSame('hello', compiled({display: true}));
  84
+        Assert.areSame('', compiled({display: false}));
  85
+    },
  86
+
  87
+    '<% ... %> should support switch statements': function () {
  88
+        var compiled = Micro.compile(
  89
+            'test' +
  90
+            '<% switch (data.foo) { %>' +
  91
+                '<% case "a": %>' +
  92
+                    'a' +
  93
+                    '<% break; %>' +
  94
+
  95
+                '<% case "b": %>' +
  96
+                    'b' +
  97
+                    '<% break; %>' +
  98
+
  99
+                '<% case "c": %>' +
  100
+                    'c' +
  101
+                    '<% break; %>' +
  102
+            '<% } %>' +
  103
+            'test'
  104
+        );
  105
+
  106
+        Assert.areSame('testatest', compiled({foo: 'a'}));
  107
+        Assert.areSame('testbtest', compiled({foo: 'b'}));
  108
+        Assert.areSame('testctest', compiled({foo: 'c'}));
  109
+    },
  110
+
  111
+    '<%= ... %> should be rendered as HTML-escaped output': function () {
  112
+        Assert.areSame('at&amp;t', Micro.render('<%= data.name %>', {name: 'at&t'}));
  113
+    },
  114
+
  115
+    '<%= ... %> should print an empty string if given a falsy value': function () {
  116
+        Assert.areSame('foobar', Micro.render('foo<%= data.bogus %>bar'));
  117
+    },
  118
+
  119
+    '<%== ... %> should be rendered as raw output': function () {
  120
+        Assert.areSame('at&t', Micro.render('<%== data.name %>', {name: 'at&t'}));
  121
+    },
  122
+
  123
+    '<%== ... %> should print an empty string if given a falsy value': function () {
  124
+        Assert.areSame('foobar', Micro.render('foo<%== data.bogus %>bar'));
  125
+    }
  126
+}));
  127
+
  128
+templateSuite.add(microSuite);
  129
+
  130
+// -- Y.Template ---------------------------------------------------------------
  131
+
  132
+templateSuite.add(new Y.Test.Case({
  133
+    name: 'Lifecycle',
  134
+
  135
+    'constructor should accept an engine class': function () {
  136
+        Assert.areSame(Y.Handlebars, (new Y.Template(Y.Handlebars)).engine);
  137
+    },
  138
+
  139
+    'engine should default to Y.Template.Micro if available': function () {
  140
+        Assert.areSame(Y.Template.Micro, (new Y.Template()).engine);
  141
+    }
  142
+}));
  143
+
  144
+templateSuite.add(new Y.Test.Case({
  145
+    name: 'Methods',
  146
+
  147
+    'compile() should compile a template using the selected engine': function () {
  148
+        Assert.areSame('foo', (new Y.Template()).compile('<%=data.a%>')({a: 'foo'}), 'should compile Micro templates');
  149
+        Assert.areSame('foo', (new Y.Template(Y.Handlebars)).compile('{{a}}')({a: 'foo'}), 'should compile Handlebars templates');
  150
+    },
  151
+
  152
+    'compile() should pass options to the selected engine': function () {
  153
+        Assert.areSame(
  154
+            'foo',
  155
+            (new Y.Template()).compile('{{data.a}}', {
  156
+                escapedOutput: /\{\{([\s\S]+?)\}\}/g
  157
+            })({a: 'foo'})
  158
+        );
  159
+    },
  160
+
  161
+    'precompile() should precompile a template using the selected engine': function () {
  162
+        Assert.areSame(
  163
+            "function (Y, $e, data) {\nvar $b='',$t='test';\nreturn $t;\n}",
  164
+            (new Y.Template()).precompile('test')
  165
+        );
  166
+    },
  167
+
  168
+    'precompile() should pass options to the selected engine': function () {
  169
+        Assert.areSame(
  170
+            "function (Y, $e, data) {\nvar $b='',$t=''+\n$e((data.test)||$b)+\n'';\nreturn $t;\n}",
  171
+
  172
+            (new Y.Template()).precompile('{{data.test}}', {
  173
+                escapedOutput: /\{\{([\s\S]+?)\}\}/g
  174
+            })
  175
+        )
  176
+    },
  177
+
  178
+    'render() should compile and render a template using the selected engine': function () {
  179
+        Assert.areSame(
  180
+            'foo',
  181
+            (new Y.Template()).render('<%=data.a%>', {a: 'foo'})
  182
+        );
  183
+    },
  184
+
  185
+    "render() should compile and render internally if the selected engine doesn't have a render() method": function () {
  186
+        var FakeEngine = Y.merge(Y.Template.Micro);
  187
+        delete FakeEngine.render;
  188
+
  189
+        Assert.areSame(
  190
+            'foo',
  191
+            (new Y.Template(FakeEngine)).render('<%=data.a%>', {a: 'foo'})
  192
+        );
  193
+    },
  194
+
  195
+    'render() should pass options to the selected engine': function () {
  196
+        Assert.areSame(
  197
+            'foo',
  198
+            (new Y.Template()).render('{{data.a}}', {a: 'foo'}, {
  199
+                escapedOutput: /\{\{([\s\S]+?)\}\}/g
  200
+            })
  201
+        );
  202
+    },
  203
+
  204
+    'revive() should revive a precompiled template': function () {
  205
+        eval('var precompiled = ' + Micro.precompile('<%=data.a%>') + ';');
  206
+        Assert.areSame('foo', (new Y.Template()).revive(precompiled)({a: 'foo'}));
  207
+    },
  208
+
  209
+    "revive() should return the given template if the engine doesn't have a revive() method": function () {
  210
+        eval('var precompiled = ' + Micro.precompile('<%=data.a%>') + ';');
  211
+        Assert.areSame(precompiled, (new Y.Template({})).revive(precompiled));
  212
+
  213
+    }
  214
+}));
  215
+
  216
+}, '@VERSION@', {
  217
+    requires: ['handlebars', 'template', 'test']
  218
+});
31  src/template/tests/unit/template.html
... ...
@@ -0,0 +1,31 @@
  1
+<!DOCTYPE html>
  2
+<html>
  3
+<head>
  4
+    <meta charset="utf-8">
  5
+    <title>Test Page</title>
  6
+</head>
  7
+<body class="yui3-skin-sam">
  8
+
  9
+<div id="log"></div>
  10
+
  11
+<script src="../../../../build/yui/yui.js"></script>
  12
+<script>
  13
+var Y = YUI({
  14
+    filter: (window.location.search.match(/[?&]filter=([^&]+)/) || [])[1] || 'raw',
  15
+    coverage: [ 'template' ],
  16
+    modules: {
  17
+        'template-test': {
  18
+            fullpath: 'assets/template-test.js',
  19
+            requires: ['handlebars', 'template', 'test']
  20
+        }
  21
+    },
  22
+    useBrowserConsole: false
  23
+}).use('template-test', 'test-console', function (Y) {
  24
+    new Y.Test.Console().render('#log');
  25
+    Y.Test.Runner.add(Y.TemplateTestSuite);
  26
+    Y.Test.Runner.run();
  27
+});
  28
+</script>
  29
+
  30
+</body>
  31
+</html>
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.