2009-03-05 19:56:01 +00:00
|
|
|
/** section: Language
|
2008-12-14 04:36:59 +00:00
|
|
|
* class Template
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* A class for sophisticated string interpolation.
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* Any time you have a group of similar objects and you need to produce
|
|
|
|
* formatted output for these objects, maybe inside a loop, you typically
|
2009-06-05 03:56:55 +00:00
|
|
|
* resort to concatenating string literals with the object's fields:
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-06-05 03:56:55 +00:00
|
|
|
* "The TV show " + title + " was created by " + author + ".";
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-06-05 03:56:55 +00:00
|
|
|
* There's nothing wrong with this approach, except that it is hard to
|
|
|
|
* visualize the output immediately just by glancing at the concatenation
|
|
|
|
* expression. The `Template` class provides a much nicer and clearer way of
|
|
|
|
* achieving this formatting.
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* <h4>Straightforward templates</h4>
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-06-05 03:56:55 +00:00
|
|
|
* The `Template` class uses a basic formatting syntax, similar to what is
|
|
|
|
* used in Ruby. The templates are created from strings that have embedded
|
2009-06-11 20:48:38 +00:00
|
|
|
* symbols in the form (e.g., `#{fieldName}`) that will be replaced by
|
2009-06-05 03:56:55 +00:00
|
|
|
* actual values when the template is applied (evaluated) to an object.
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* // the template (our formatting expression)
|
|
|
|
* var myTemplate = new Template(
|
|
|
|
* 'The TV show #{title} was created by #{author}.');
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* // our data to be formatted by the template
|
2009-06-11 20:48:38 +00:00
|
|
|
* var show = {
|
2009-03-06 21:41:11 +00:00
|
|
|
* title: 'The Simpsons',
|
|
|
|
* author: 'Matt Groening',
|
|
|
|
* network: 'FOX'
|
|
|
|
* };
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* // let's format our data
|
|
|
|
* myTemplate.evaluate(show);
|
|
|
|
* // -> "The TV show The Simpsons was created by Matt Groening."
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* <h4>Templates are meant to be reused</h4>
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* As the example illustrates, `Template` objects are not tied to specific
|
|
|
|
* data. The data is bound to the template only during the evaluation of the
|
|
|
|
* template, without affecting the template itself. The next example shows the
|
|
|
|
* same template being used with a handful of distinct objects.
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* // creating a few similar objects
|
|
|
|
* var conversion1 = { from: 'meters', to: 'feet', factor: 3.28 };
|
|
|
|
* var conversion2 = { from: 'kilojoules', to: 'BTUs', factor: 0.9478 };
|
|
|
|
* var conversion3 = { from: 'megabytes', to: 'gigabytes', factor: 1024 };
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
|
|
|
* // the template
|
2009-03-06 21:41:11 +00:00
|
|
|
* var templ = new Template(
|
|
|
|
* 'Multiply by #{factor} to convert from #{from} to #{to}.');
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* // let's format each object
|
|
|
|
* [conversion1, conversion2, conversion3].each( function(conv){
|
|
|
|
* templ.evaluate(conv);
|
|
|
|
* });
|
|
|
|
* // -> Multiply by 3.28 to convert from meters to feet.
|
|
|
|
* // -> Multiply by 0.9478 to convert from kilojoules to BTUs.
|
|
|
|
* // -> Multiply by 1024 to convert from megabytes to gigabytes.
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* <h4>Escape sequence</h4>
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* There's always the chance that one day you'll need to have a literal in your
|
|
|
|
* template that looks like a symbol, but is not supposed to be replaced. For
|
2009-06-05 03:56:55 +00:00
|
|
|
* these situations there's an escape character: the backslash (<code>\\</code>).
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* // NOTE: you're seeing two backslashes here because the backslash
|
|
|
|
* // is also an escape character in JavaScript strings, so a literal
|
|
|
|
* // backslash is represented by two backslashes.
|
|
|
|
* var t = new Template(
|
|
|
|
* 'in #{lang} we also use the \\#{variable} syntax for templates.');
|
|
|
|
* var data = { lang:'Ruby', variable: '(not used)' };
|
|
|
|
* t.evaluate(data);
|
|
|
|
* // -> in Ruby we also use the #{variable} syntax for templates.
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* <h4>Custom syntaxes</h4>
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* The default syntax of the template strings will probably be enough for most
|
|
|
|
* scenarios. In the rare occasion where the default Ruby-like syntax is
|
|
|
|
* inadequate, there's a provision for customization. `Template`'s
|
|
|
|
* constructor accepts an optional second argument that is a regular expression
|
|
|
|
* object to match the replaceable symbols in the template string. Let's put
|
2009-03-07 21:44:50 +00:00
|
|
|
* together a template that uses a syntax similar to the ubiquitous `<&= %>`
|
2009-03-06 21:41:11 +00:00
|
|
|
* constructs:
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-07 21:44:50 +00:00
|
|
|
* // matches symbols like '<&= field %>'
|
2009-06-11 20:48:38 +00:00
|
|
|
* var syntax = /(^|.|\r|\n)(\<%=\s*(\w+)\s*%\>)/;
|
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* var t = new Template(
|
2009-03-07 21:44:50 +00:00
|
|
|
* '<div>Name: <b><&= name %></b>, Age: <b><&=age%></b></div>',
|
|
|
|
* syntax);
|
2009-03-06 21:41:11 +00:00
|
|
|
* t.evaluate( {name: 'John Smith', age: 26} );
|
|
|
|
* // -> <div>Name: <b>John Smith</b>, Age: <b>26</b></div>
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-03-06 21:41:11 +00:00
|
|
|
* There are important constraints to any custom syntax. Any syntax must
|
|
|
|
* provide at least three groupings in the regular expression. The first
|
|
|
|
* grouping is to capture what comes before the symbol, to detect the backslash
|
|
|
|
* escape character (no, you cannot use a different character). The second
|
|
|
|
* grouping captures the entire symbol and will be completely replaced upon
|
|
|
|
* evaluation. Lastly, the third required grouping captures the name of the
|
|
|
|
* field inside the symbol.
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2008-12-14 04:36:59 +00:00
|
|
|
**/
|
2008-12-11 10:35:20 +00:00
|
|
|
var Template = Class.create({
|
2008-12-14 04:36:59 +00:00
|
|
|
/**
|
|
|
|
* new Template(template[, pattern = Template.Pattern])
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-02-24 02:35:49 +00:00
|
|
|
* Creates a Template object.
|
2009-06-11 20:48:38 +00:00
|
|
|
*
|
2009-02-24 02:35:49 +00:00
|
|
|
* The optional `pattern` argument expects a `RegExp` that defines a custom
|
|
|
|
* syntax for the replaceable symbols in `template`.
|
2008-12-14 04:36:59 +00:00
|
|
|
**/
|
2008-12-11 10:35:20 +00:00
|
|
|
initialize: function(template, pattern) {
|
|
|
|
this.template = template.toString();
|
|
|
|
this.pattern = pattern || Template.Pattern;
|
|
|
|
},
|
2009-06-11 20:48:38 +00:00
|
|
|
|
2008-12-14 04:36:59 +00:00
|
|
|
/**
|
|
|
|
* Template#evaluate(object) -> String
|
|
|
|
*
|
2009-06-11 20:48:38 +00:00
|
|
|
* Applies the template to `object`'s data, producing a formatted string
|
|
|
|
* with symbols replaced by `object`'s corresponding properties.
|
2008-12-14 04:36:59 +00:00
|
|
|
**/
|
2008-12-11 10:35:20 +00:00
|
|
|
evaluate: function(object) {
|
2009-05-26 20:26:33 +00:00
|
|
|
if (object && Object.isFunction(object.toTemplateReplacements))
|
2008-12-11 10:35:20 +00:00
|
|
|
object = object.toTemplateReplacements();
|
|
|
|
|
|
|
|
return this.template.gsub(this.pattern, function(match) {
|
2009-05-26 20:26:33 +00:00
|
|
|
if (object == null) return (match[1] + '');
|
2009-06-11 20:48:38 +00:00
|
|
|
|
2008-12-11 10:35:20 +00:00
|
|
|
var before = match[1] || '';
|
|
|
|
if (before == '\\') return match[2];
|
2009-06-11 20:48:38 +00:00
|
|
|
|
2008-12-11 10:35:20 +00:00
|
|
|
var ctx = object, expr = match[3];
|
|
|
|
var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
|
|
|
|
match = pattern.exec(expr);
|
|
|
|
if (match == null) return before;
|
|
|
|
|
|
|
|
while (match != null) {
|
2009-07-14 16:32:34 +00:00
|
|
|
var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
|
2008-12-11 10:35:20 +00:00
|
|
|
ctx = ctx[comp];
|
|
|
|
if (null == ctx || '' == match[3]) break;
|
|
|
|
expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
|
|
|
|
match = pattern.exec(expr);
|
|
|
|
}
|
2009-06-11 20:48:38 +00:00
|
|
|
|
2008-12-11 10:35:20 +00:00
|
|
|
return before + String.interpret(ctx);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
|