Template Class

0 views
Skip to first unread message

Yaffle

unread,
Jun 13, 2009, 12:41:09 PM6/13/09
to Prototype: Core
Why Template Class can't split template string when initializing?

Example:

function xTemplate(template){
var a = template.split(Template.Pattern),
l = a.length;
return function(object){
var x = a[0];
for(var i=1;i<l;i+=4){
x+= a[i]+String.interpret(object[a[i+2]])+a[i+3];
}
return x;
};
}
var st = "<option value='#{index}' #{selected}>#{name}</option>",
f = xTemplate(st),
o = {index:1,name:'name'},
t = new Template(st),
now = function(){ return (new Date()).valueOf(); },
xt = now()+500;
while(now()<xt);
var t1 = now();
for(var i=0;i<50000;i++){
f(o);
}
var t2 = now();
for(var i=0;i<50000;i++){
t.evaluate(o);
}
alert((now()-t2)/(t2-t1));

with xTemplate we can't use escape sequence and other, but
Template class can be rewritten...
Also String.prototype.split isn't cross-browser, when called with
RegExp as separator, so I used there fixed version from
http://blog.stevenlevithan.com/archives/cross-browser-split .

Yaffle

unread,
Jun 13, 2009, 3:07:16 PM6/13/09
to Prototype: Core
var Template = Class.create({
initialize: function(template, pattern) {
this.template = template.toString();
pattern = pattern || Template.Pattern;
if (!Object.isString(pattern) && pattern.global)
pattern = new RegExp(pattern.source);
this.pattern = pattern;

var parts = this.template.split(this.pattern);
var rfunc = function(object, before, escaped, expr) {
if (object == null) return '';

if (before == '\\') return escaped;
if (before == null) before = '';

var ctx = object;
var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
var match = pattern.exec(expr);
if (match == null) return before;

while (match != null) {
var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/
g, ']') : 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);
};
this.evaluate = function(object){
if (Object.isFunction(object.toTemplateReplacements)){
object = object.toTemplateReplacements();
}

var r = parts[0], l = parts.length;
for(var i=1;i<l;i+=4){
r+= rfunc(object,parts[i],parts[i+1],parts[i+2])+parts[i+3];
}
return r;
};
}
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

Yaffle

unread,
Jun 14, 2009, 10:41:00 AM6/14/09
to Prototype: Core

var Template = Class.create({
initialize: function(template, pattern){
var parts = template.toString().split(pattern ||
Template.Pattern),
pl = parts.length,
pattern2 = /^([^.\[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;

for(var i=1,k=1;i<pl;i+=4){
var before = parts[i] || '',
escaped = parts[i+1];
if(before=='\\'){
parts[k-1] += escaped;
}else{
parts[k-1] += before;

var propList = [],
expr = parts[i+2],
match = pattern2.exec(expr);
while(match){
propList.push( match[1].startsWith('[') ? match[2].replace(/\
\\\\]/g, ']') : match[1] );
if(!match[3]){
break;
}
expr = expr.substring('[' == match[3] ? match[1].length :
match[0].length);
match = pattern2.exec(expr);
}

if(propList.length){
parts[k] = propList;
parts[k+1] = parts[i+3];
k+=2;
}else{
parts[k-1] += parts[i+3];
}

}
}
parts.length = k;
this.parts = parts;
},
evaluate: function(object){
if(Object.isFunction(object.toTemplateReplacements)){
object = object.toTemplateReplacements();
}
object = object || '';

var r = this.parts[0];
for(var i=1,pl=this.parts.length;i<pl;i+=2){
var propList = this.parts[i],
ctx = object;
for(var j=0,l = propList.length;j<l && ctx;j++){
ctx = ctx[propList[j]];
}
r+= String.interpret(ctx)+this.parts[i+1];
}
return r;
}
});
Template.Pattern = /(^|.|\r|\n)(#\{([^\}]*)\})/;

Allen

unread,
Aug 12, 2009, 8:41:17 AM8/12/09
to Prototype: Core
You should use Strings interpolate function for this. A template is
meant to be reused many times with many different filler values.

Yaffle

unread,
Aug 12, 2009, 2:32:10 PM8/12/09
to Prototype: Core
String#interpolate uses Template#evaluate.
This Template realization makes some actions in constructor of
Template class instead of Template#evaluate.
So if you will use Template#evaluate many times for one Template
object, you will save a little time.

Allen

unread,
Aug 12, 2009, 2:37:40 PM8/12/09
to Prototype: Core
Sorry, misunderstood what you were asking. I thought you meant why
can't the values be populated into the string at initialization.

Samuel Lebeau

unread,
Aug 25, 2009, 7:28:02 PM8/25/09
to prototy...@googlegroups.com
Hi,

I've been working on a Template rewrite trying to reduce code size and
complexity.
It uses `String#replace` instead of `String#gsub` and thus performs
better.
Do you guys see any enhancements (in terms of performance or code
size) that could be made ?

Diff: http://github.com/samleb/prototype/commit/8ba0a0b80c5ad230cec32e766360e0e6be7e956c#diff-0
File: http://github.com/samleb/prototype/blob/8ba0a0b80c5ad230cec32e766360e0e6be7e956c/src/lang/template.js

Best,
Samuel.

T.J. Crowder

unread,
Aug 27, 2009, 5:35:35 AM8/27/09
to Prototype: Core
Hi Simon,

Funny you should be doing that, I was just looking at Template the
other day and thinking I might suggest a rewrite for 1.7 or 2.0.

When I did a similar thing in Java a few years back, I made it heavy
on the initialization and light on evaluation -- since the point of a
template is to be reused. The idea was to turn the template string
into an array of literal segments and replacement segments on init,
e.g.

"Hello #{username}, welcome to #{sitename}!"

becomes:

Literal: "Hello "
Replace: 'username'
Literal: ", welcome to "
Replace: 'sitename'
Literal: "!"

Then evaluating the template is just running through the array;
roughly (http://pastie.org/596449):

var result, n, l, sub;
l = segments.length;
result = new Array(l);
for (n = 0; n < l; ++n) {
segment = segments[n];
if (typeof segment == 'string') {
result[n] = segment;
}
else {
sub = /* ...get substitution ... */
result[n] = sub;
}
}
return result.join('');

Your current code for #evaluate could readily be recast as the
#initialize piece.

Granted this may be slightly larger (because we need to add the loop
above), but it should (subject to testing) be faster. It also lends
itself to future enhancement, such as having formatting syntax, since
that cost is front-loaded. I've already proposed the first, trivial,
formatter for a common use case -- a flag indicating we should escape
HTML tags when subbing:
http://github.com/tjcrowder/prototype/commit/29b76e7a9e9c6168d62f1d1c6da9aafb1bf5086f

FWIW,
--
T.J. Crowder
tj / crowder software / com


On Aug 26, 12:28 am, Samuel Lebeau <samuel.leb...@gmail.com> wrote:
> Hi,
>
> I've been working on a Template rewrite trying to reduce code size and  
> complexity.
> It uses `String#replace` instead of `String#gsub` and thus performs  
> better.
> Do you guys see any enhancements (in terms of performance or code  
> size) that could be made ?
>
> Diff:http://github.com/samleb/prototype/commit/8ba0a0b80c5ad230cec32e76636...
> File:http://github.com/samleb/prototype/blob/8ba0a0b80c5ad230cec32e766360e...

T.J. Crowder

unread,
Aug 27, 2009, 6:19:35 AM8/27/09
to Prototype: Core
Samuel,

Sorry for the "Simon" error in my last post. Years ago I knew a guy
named Simon Lebeau, clearly it got ingrained my brain.

-- T.J. :-)
> HTML tags when subbing:http://github.com/tjcrowder/prototype/commit/29b76e7a9e9c6168d62f1d1c...

Yaffle

unread,
Aug 27, 2009, 6:31:21 AM8/27/09
to Prototype: Core
My Template implementation does it too =)

In Template contrsuctor template string splits into parts and arrays
of properties.
Example:
"<a>#{x.y}</a>" -> ["<a>",["x","y"],"</a>"]
=)

I use String#split for this. ( so i use fixed cross-browser split )
Reply all
Reply to author
Forward
0 new messages