John Resign created some incredibly wonderful Javascript code for templating. It’s so terse that it almost shouldn’t work…but it does. I’ve been using it on a lot of front-end JS apps lately, and realized I could make a few changes and improvements.
First off, I don’t like <% asp style tags %>. It reminds me of programming ASP. It reminds me of a trip through hell I’ve taken too many times. I changed it to use PHP-style tags instead:
<ul class="<?=myclass?>">
<? for(var i = 0; i < items.length; i++) { ?>
<li><?=items[i].name?>
<? } ?>
</ul>
This makes it easier for me to type. I also made one further modification. Adding $ in front of your variables will check if they are undefined before using them, and if not defined will return them as null:
Hello, <?=$name?>.
<? if($user.friends) { ?>
You have <?=$user.friends.length?> friends.
<? } ?>
The if statement above will compile to
if((typeof(user.friends) == 'undefined' ? null : user.friends)) { ...
This allows some simple usages of undefined variables, such as “if(undefined_var) { … } else { … }” which actually pops up a lot. You still can’t access non-existent properties of variables that aren’t defined, but this should catch a lot of errors that would otherwise turn your code into a bunch of if(typeof …)’s.
Here’s the code (for brevity, I left out all the caching stuff that makes this fast):
var template = '<h1>My Template</h1> ...';
new Function(
"obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj) {p.push('" +
// Convert the template into pure JavaScript
template.replace(/[\r\t\n]/g, " ")
// find any code blocks (not html, not <?=print_var?>... anything inside a
// <? ... ?>
.replace(/<\?(.*?)\?>/g, function(match) {
// look for any string starting with a "$" and wrap it in a ternary typeof op
return match.replace(/\$([a-z_][a-z0-9_\.]+)/gi, '(typeof($1) == "undefined" ? null : $1)');
})
.split("<?").join("\t")
.replace(/((^|\?>)[^\t]*)'/g, "$1\r")
.replace(/\t=\$?(.*?)\?>/g, "',(typeof($1) != 'undefined' ? $1 : ''),'")
.split("\t").join("');")
.split("?>").join("p.push('")
.split("\r").join("\\'") + "');}"
//+ "console.log('Loading template " + name + "');"
+ "return p.join('');"
);
This has been working for me for a bit now, and has saved me countless annoying declarations at the top if my templates. If you run into any problems, please let me know.