prototype: Template enhancements. Closes #8166.
This commit is contained in:
parent
b59399bdd3
commit
ff45622e39
10
CHANGELOG
10
CHANGELOG
|
@ -1,6 +1,14 @@
|
|||
*SVN*
|
||||
|
||||
* Extended grep semantics. The first argument to Enumerable#grep is now a "filter" (an object with a match() method) so you can now e.g. filter an array of DOM nodes by CSS selector. RegExp#match is now an alias to RegExp#test, so grep can still be used to filter an array of strings with a regular expression. Closes #7596. [Christophe Porteneuve, sam]
|
||||
* Template enhancements. Closes #8166. [Christophe Porteneuve]
|
||||
- Added String#interpolate as a shortcut for new Template(...).evaluate(...).
|
||||
- If you pass String#interpolate or Template#evaluate an object with a toTemplateReplacements() method, the return value of that method will be used as the replacement object.
|
||||
- You can now substitute properties of template replacement values in template strings, using dot or bracket notation (or both). Example:
|
||||
"#{name.last}, #{name.first[0]}. (#{location})".interpolate({
|
||||
name: { first: "Christophe", last: "Porteneuve" }, location: "Paris"
|
||||
}) // "Porteneuve, C. (Paris)"
|
||||
|
||||
* Extended grep semantics. The first argument to Enumerable#grep is now a "filter" (an object with a match() method) so you can now e.g. filter an array of DOM nodes by CSS selector. RegExp#match is now an alias to RegExp#test, so grep can still be used to filter an array of strings with a regular expression. Closes #7596. [Christophe Porteneuve, sam]
|
||||
|
||||
* Make String#scan explicitly return a string. This prevents possible issues with methods expecting input data that is typeof == 'string'. Closes #6350. [AndrewRev, Tobie Langel]
|
||||
|
||||
|
|
|
@ -199,6 +199,10 @@ Object.extend(String.prototype, {
|
|||
|
||||
blank: function() {
|
||||
return /^\s*$/.test(this);
|
||||
},
|
||||
|
||||
interpolate: function(object, pattern) {
|
||||
return new Template(this, pattern).evaluate(object);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -231,14 +235,32 @@ Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
|
|||
Template.prototype = {
|
||||
initialize: function(template, pattern) {
|
||||
this.template = template.toString();
|
||||
this.pattern = pattern || Template.Pattern;
|
||||
this.pattern = pattern || Template.Pattern;
|
||||
},
|
||||
|
||||
evaluate: function(object) {
|
||||
if (typeof object.toTemplateReplacements == 'function')
|
||||
object = object.toTemplateReplacements();
|
||||
|
||||
return this.template.gsub(this.pattern, function(match) {
|
||||
var before = match[1];
|
||||
if (object == null) return '';
|
||||
|
||||
var before = match[1] || '';
|
||||
if (before == '\\') return match[2];
|
||||
return before + String.interpret(object[match[3]]);
|
||||
});
|
||||
|
||||
var ctx = object, expr = match[3];
|
||||
var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
|
||||
if (match == null) return '';
|
||||
|
||||
while (match != null) {
|
||||
var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
|
||||
ctx = ctx[comp];
|
||||
if (null == ctx || '' == match[3]) break;
|
||||
expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
|
||||
match = pattern.exec(expr);
|
||||
}
|
||||
|
||||
return before + String.interpret(ctx);
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
assertEnumEqual(['a','b'],'ab'.toArray());
|
||||
assertEnumEqual(['f','o','o'],'foo'.toArray());
|
||||
}},
|
||||
|
||||
|
||||
/*
|
||||
Note that camelize() differs from its Rails counterpart,
|
||||
as it is optimized for dealing with JavaScript object
|
||||
|
@ -313,7 +313,67 @@
|
|||
assertEqual('<tr><td>0</td><td>false</td><td></td><td></td><td></td></tr>',
|
||||
template.evaluate(falses));
|
||||
}},
|
||||
|
||||
|
||||
testTemplateEvaluationWithNested: function() {with(this) {
|
||||
var source = '#{name} #{manager.name} #{manager.age} #{manager.undef} #{manager.age.undef} #{colleagues.first.name}';
|
||||
var subject = { manager: { name: 'John', age: 29 }, name: 'Stephan', age: 22, colleagues: { first: { name: 'Mark' } } };
|
||||
assertEqual('Stephan', new Template('#{name}').evaluate(subject));
|
||||
assertEqual('John', new Template('#{manager.name}').evaluate(subject));
|
||||
assertEqual('29', new Template('#{manager.age}').evaluate(subject));
|
||||
assertEqual('', new Template('#{manager.undef}').evaluate(subject));
|
||||
assertEqual('', new Template('#{manager.age.undef}').evaluate(subject));
|
||||
assertEqual('Mark', new Template('#{colleagues.first.name}').evaluate(subject));
|
||||
assertEqual('Stephan John 29 Mark', new Template(source).evaluate(subject));
|
||||
}},
|
||||
|
||||
testTemplateEvaluationWithIndexing: function() {with(this) {
|
||||
var source = '#{0} = #{[0]} - #{1} = #{[1]} - #{[2][0]} - #{[2].name} - #{first[0]} - #{[first][0]} - #{[\\]]} - #{first[\\]]}';
|
||||
var subject = [ 'zero', 'one', [ 'two-zero' ] ];
|
||||
subject[2].name = 'two-zero-name';
|
||||
subject.first = subject[2];
|
||||
subject[']'] = '\\';
|
||||
subject.first[']'] = 'first\\';
|
||||
assertEqual('zero', new Template('#{[0]}').evaluate(subject));
|
||||
assertEqual('one', new Template('#{[1]}').evaluate(subject));
|
||||
assertEqual('two-zero', new Template('#{[2][0]}').evaluate(subject));
|
||||
assertEqual('two-zero-name', new Template('#{[2].name}').evaluate(subject));
|
||||
assertEqual('two-zero', new Template('#{first[0]}').evaluate(subject));
|
||||
assertEqual('\\', new Template('#{[\\]]}').evaluate(subject));
|
||||
assertEqual('first\\', new Template('#{first[\\]]}').evaluate(subject));
|
||||
assertEqual('empty - empty2', new Template('#{[]} - #{m[]}').evaluate({ '': 'empty', m: {'': 'empty2'}}));
|
||||
assertEqual('zero = zero - one = one - two-zero - two-zero-name - two-zero - two-zero - \\ - first\\', new Template(source).evaluate(subject));
|
||||
}},
|
||||
|
||||
testTemplateToTemplateReplacements: function() {with(this) {
|
||||
var source = 'My name is #{name}, my job is #{job}';
|
||||
var subject = {
|
||||
name: 'Stephan',
|
||||
getJob: function() { return 'Web developer'; },
|
||||
toTemplateReplacements: function() { return { name: this.name, job: this.getJob() } }
|
||||
};
|
||||
assertEqual('My name is Stephan, my job is Web developer', new Template(source).evaluate(subject));
|
||||
}},
|
||||
|
||||
testTemplateEvaluationCombined: function() {with(this) {
|
||||
var source = '#{name} is #{age} years old, managed by #{manager.name}, #{manager.age}.\n' +
|
||||
'Colleagues include #{colleagues[0].name} and #{colleagues[1].name}.';
|
||||
var subject = {
|
||||
name: 'Stephan', age: 22,
|
||||
manager: { name: 'John', age: 29 },
|
||||
colleagues: [ { name: 'Mark' }, { name: 'Indy' } ]
|
||||
};
|
||||
assertEqual('Stephan is 22 years old, managed by John, 29.\n' +
|
||||
'Colleagues include Mark and Indy.',
|
||||
new Template(source).evaluate(subject));
|
||||
}},
|
||||
|
||||
testInterpolate: function() {with(this) {
|
||||
var subject = { name: 'Stephan' };
|
||||
var pattern = /(^|.|\r|\n)(#\((.*?)\))/;
|
||||
assertEqual('#{name}: Stephan', '\\#{name}: #{name}'.interpolate(subject));
|
||||
assertEqual('#(name): Stephan', '\\#(name): #(name)'.interpolate(subject, pattern));
|
||||
}},
|
||||
|
||||
testToQueryParams: function() {with(this) {
|
||||
// only the query part
|
||||
var result = {a:undefined, b:'c'};
|
||||
|
|
Loading…
Reference in New Issue