I’ve had a little utility that I’ve been kicking around for some time now that I’ve found to be quite useful in my JavaScript application-building endeavors. It’s a super-simple templating function that is fast, caches quickly, and is easy to use. I have a couple tricks that I use to make it real fun to mess with.
Here’s the source code to the templating function (a more-refined version of this code will be in my upcoming book Secrets of the JavaScript Ninja):
You would use it against templates written like this (it doesn’t have to be in this particular manner – but it’s a style that I enjoy):
You can also inline script:
Quick tip: Embedding scripts in your page that have a unknown content-type (such is the case here – the browser doesn’t know how to execute a text/html script) are simply ignored by the browser – and by search engines and screenreaders. It’s a perfect cloaking device for sneaking templates into your page. I like to use this technique for quick-and-dirty cases where I just need a little template or two on the page and want something light and fast.
and you would use it from script like so:
You could pre-compile the results for later use. If you call the templating function with only an ID (or a template code) then it’ll return a pre-compiled function that you can execute later:
The biggest falling-down of the method, at this point, is the parsing/conversion code – it could probably use a little love. It does use one technique that I enjoy, though: If you’re searching and replacing through a string with a static search and a static replace it’s faster to perform the action with .split("match").join("replace")
– which seems counter-intuitive but it manages to work that way in most modern browsers. (There are changes going in place to grossly improve the performance of .replace(/match/g, "replace")
in the next version of Firefox – so the previous statement won’t be the case for long.)
Feel free to have fun with it – I’d be very curious to see what mutations occur with the script. Since it’s so simple it seems like there’s a lot that can still be done with it.
Posted: July 16th, 2008 -->
If you particularly enjoy my work, I appreciate donations given with Gittip.
Kyle Simpson (July 16, 2008 at 11:44 pm)
the “text/html” script island for hiding/cloaking data (in this case, a template) is quite interesting. Thanks for the tips!
Iraê (July 17, 2008 at 1:56 am)
Awesome!
Its amusing to see this kind of tricks. Also very useful.
I currently have some pages witch are rendered with all data coming from JSONP calls (I know, it sucks, but it’s not my decision) and I use one external javascript with the following pattern for templates:
markup={};
markup.user = function() {
html='<dl>\
<dt>'+this.name+'</dt>\
<dd>'+this.about+'</dd>\
</dl>';
return html;
}
And I call this functions with:
$('#output').html(markup.user.apply(data));
In my tests this was a pretty fast method. But now I’m looking forward to get to work tomorrow to try out your templates.
I just can’t wait for the book!
michele (July 17, 2008 at 2:15 am)
Very cool, you rock, I can’t wait to get my hands on your next book!
Edwin Khodabakchian (July 17, 2008 at 2:37 am)
Hi John,
You might want to take a look at that:
www.devhd.com/item0002.htm
Similar to what you are describing but with pre-compilation support
-Edwin
Lenny (July 17, 2008 at 3:30 am)
Funnily enough I started working on a very similar problem only yesterday, except using XSL loaded via XHR to populate templates with JS objects. Yet again I am made to feel slightly inadequate by your solution…
Very impressed by the type=”text/html” workaround though. I can’t help thinking it’s a little too based around browser quirks for my liking, and as such I might be loathed to use it in production builds in case it gets “fixed” in future browser versions. Maybe I’m being paranoid.
Andrea Giammarchi (July 17, 2008 at 3:33 am)
John, I think it is interesting but I cannot spot portability or “component” re-usability, over performances.
What I mean, is that XSL(T) does similar things, in a more standard way, and using browser core functions to perform those tasks.
Am I that wrong?
Bakyt Niyazov (July 17, 2008 at 3:54 am)
I was looking for it :D
And was thinking to write myself.
type=”text/html” – it is something new and seems to be very very useful.
But no one could do it better than you!
Valentin Vago (July 17, 2008 at 4:27 am)
Excellent!
I was looking at such functionnality/feature and started to write it myself (but i will defenetly use yours as I feel myself under skilled :) )
by the way, i agree with Bakyt Niyazov, no one could do it better than you and I would like to thanks you for all what you share with us!
John Resig (July 17, 2008 at 7:25 am)
@Edwin: Seems like an interesting solution. I don’t think there’s really that big of a hit in the compilation stage (at least not for what I’m showing above) so handling it in pure JavaScript would seem to be fine, for now. Obviously for something that’s really long a pre-compiled solution would be better.
@Lenny: This isn’t really a feature that browsers can “fix”. It’s fully supported by browsers that you can use multiple scripting languages within a page (browsers may not support the individual languages, but they allow them to exist and don’t manipulate them). Search engines and screen readers definitely agree on this point so it’s actually a very safe solution.
@Andrea: Except that all browsers don’t support client-side, JavaScript-accessible, XSL transformations. If they did then that would certainly be a viable option.
Ludwig Pettersson (July 17, 2008 at 7:51 am)
Best book name, ever.
I haven’t seen a client side template system before, so it’s pretty strange – it feels wrong somehow. Still, great work!
Andrea Giammarchi (July 17, 2008 at 9:18 am)
@John,
>> Except that all browsers don’t support client-side, JavaScript-accessible, XSL transformations
and here we come, don’t we? :D
It could be interesting to base a client side template with XSL-T model, so browsers that support them will be truly fast, while others still compatible (but I think a lot of browsers support native XSL transformation).
Anyway, is there some library capable to implement them?
Brian R (July 17, 2008 at 9:28 am)
Seems like everybody wants to write a JS templating solution. I’ve been using TrimPath JST for quite a while with good success.
That trick with the script block though — very cool.
Lenny (July 17, 2008 at 9:45 am)
@John – on second thoughts, you are of course correct, I think I was just distracted by you saying “the browser doesn’t know how to execute a text/html script” and my mind thinking that browsers should naturally be able to understand text/html.
You could obviously put anything in place of text/html (as long as it isn’t an actual script type) and render my paranoia moot.
As you said, again, this is only really a quick and dirty solution for “sneaking templates into the page”. I’m guessing if you were going to apply this more rigorously then you’d probably use some kind of XHR to load the templates?
If I get anywhere with my XSL based implementation (and can persuade the people paying me to write it that they don’t mind) then I’ll post it somewhere.
John Resig (July 17, 2008 at 9:56 am)
@Andrea: Ah – specifically I was thinking of Safari 2. Although the usage of that browser has dropped dramatically so it’s probably far less of an issue, now. Here’s an XSLT.js script I found after a minute of searching.
@Lenny: Correct – any sort of content-type would work there – and absolutely using XHR to load in complex material would probably be ideal.
Kyle Simpson (July 17, 2008 at 11:52 am)
@John:
Have you tried using this with a src attribute on your script tag? In other words, pulling in a template externally instead of embedding it in the HTML stream?
I can’t seem to make this work. IE-Debugbar and FF-Firebug seem to show that the contents do get loaded, but I can’t access them via innerHTML like I can with inline stuff… wonder why… If I could figure that part out, I would think this would be a really helpful technique.
Wade Harrell (July 17, 2008 at 12:05 pm)
Does client side XSLT rear its head again?!? Oh Safari 2 how I loath thee. I have been waiting for the acceptance of client-side XSLT for what seems like forever now… but then JSON came along and nobody wants to send XML to the client anymore. (oh the heartache to choose between json and xslt)
Kevin H (July 17, 2008 at 1:45 pm)
I’m used to seeing templates embedded in a hidden textarea. Do you think there are benefits to using script over textarea?
As far as the templating function, it is a nice compact little thing, but I prefer a more feature-filled template language, like TrimPath JST
Leo Horie (July 17, 2008 at 4:42 pm)
@Kevin: off the top of my head, I reckon doing forms with a textarea-bound template would cause a few headaches since I assume having another textarea inside the template would effectively close the container textarea prematurely. A script tag sounds safe in that sense assuming you use unobtrusive javascript.
@John: Just by curiosity, what benefits do you see to using client-side templates over doing the templating on the server-side, taking into account what you mentioned about XHR being an ideal way of loading the content?
Chuckchillout (July 17, 2008 at 5:21 pm)
I’ve used JSONT a few times and this seems to accomplish the same. Does anyone see any advantages/disadvantages between the 2 approaches?
Jethro Larson (July 17, 2008 at 6:02 pm)
I’ve been playing with ejs and that’s been fun. It still feels very alpha but I’d like to see it get more robust. I like your solution, I would just like to see it get a little bigger.
Jon (July 17, 2008 at 8:58 pm)
@Wade I would like to see pure XML/xform (not predefined XHTML) and CSS . I just feel XSLT is a technology that shouldn’t be used to remedy browser inadequacy. Just need to have script capabilities in XML files. Xform functions are not powerful enough.
Iraê (July 18, 2008 at 12:31 am)
@Kevin H: Templates in textareas are a nightmare. Depending on how you access this information the data could be parsed by ID and you get an weird output. I’ve seen a really old code in where I work witch used textareas for
<object><embed /></object>
that IE6 and IE7 returned only the ‘embed’ part, stripping the entire ‘object’ tag.@Leo Horie: I think that John expects this templates to be useful in pages where you fetch your data or process your data in javascript by need, not by choice. i.e. when you are accessing an external JSONP API or when you wish to work with data in a desktop-like fell, such as Y~Mail. At least I wouldn’t use it purposefully on any kind of content I could use serverside parsing, since SEO is a big concern today.
Bertrand Le Roy (July 18, 2008 at 1:33 am)
Interesting. We converged on similar ideas on our own template engine: we also build a function (once) for the template and run it to instantiate, use JavaScript as the expression language and use “with” to bring the data object’s members into scope. We totally differ on the template parsing though, and one thing that isn’t quite clear from reading your code is whether you’re handling encoding and how resilient this is to injection.
John (July 18, 2008 at 6:01 am)
Thanks for sharing!. I’m learning so much from trying to run and understand your code. I get this error when I run the micro template code:
text is not defined
[Break on this error] + “‘);}return p.join(”);”
This is how I’m calling the tmpl function:
var results = document.getElementById("results");
var dataObject = {};
dataObject.from_user = "#";
dataObject.profile_image_url = "/test/";
dataObject.id = "test";
results.innerHTML = tmpl("item_tmpl", dataObject);
Can anyone help?
Peter Benoit (July 18, 2008 at 10:14 am)
I might be wrong, but it appears your template looking for “text” from your dataObject?
John Resig (July 18, 2008 at 2:54 pm)
@John: Do you have a demo page up anywhere so I can take a quick peek? I tweaked a demo that a user sent me yesterday and put it online here, if you want to see more examples.
henrah (July 21, 2008 at 10:45 am)
Surely the greatest advantage of doing inline templating is that in a server-side javascript environment, you can choose on the fly whether to do template processing beforehand and serve a static page or do dynamic updates on the client, depending on the capabilities of the requesting browser.
Bravo for this excellent work.
Sean Catchpol (July 22, 2008 at 11:43 am)
John this is excellent. The
<script type="text/html">
is particularly useful.Josh Rehman (July 22, 2008 at 8:01 pm)
I don’t understand how this code works! For example:
1. Why this.tmpl = function tmpl(…){}? Why not “tmpl”: function(){}? (This is a matter of convention I think, and not all that important).
2. I really don’t understand the body of tmpl()! What the heck is going on there?
WRT #2, I think it would be interesting to share how you built this script – for example, did you start with a concrete test template and data, write a script that did the interpolation, and then generalize it?
Also WRT#2 it would be interesting to know which functions are “built in” and which you are defining. What makes it particularly confusing is the absence of an eval() – all I see is this mysterious push().
Another problem, I think, is the syntax highlighting. The third replace in the anon function is highlighted as a literal string. Since there is already some meta-programming going on, this confuses things. :)
There’s actually a third problem – the API might be better documented: tmpl takes the ID of a DOM elt that contains template data, and an Object that represents the data, and returns the interpolated string. One thing that puzzles me
Mr Speaker (July 22, 2008 at 8:30 pm)
The demo template uses: “i % 2″ to set the class name – Presumably “i” is the loop index… I was wondering how you set “i” in your data object? Do you have to do “user[i].i = i” in the loop before you call the tmpl function?
diyism (July 22, 2008 at 9:53 pm)
Why not use textarea as template container just like that in TrimPath JS Template?
<textarea id="item_tmpl" style="display:none;">
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a class="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</textarea>
Josh Rehman (July 22, 2008 at 11:45 pm)
@Mr Speaker I’ve spent some time this afternoon unfolding this code, and the simple answer to your question is because the template is converted to JavaScript by stripping the tokens and wrapping the rest of it in string literals, with special care taken for the case.
The code is terse, fast, completely unreadable, and a great exercise for the JavaScript student to decipher. :) There is a high density of interesting things here.
As interesting as this code is, and potentially useful, I’m concerned that it might lead people astray – templating is BAD. One of the reasons I like jQuery is that you can do “passive templating” with it – use plain old static HTML as a template, using contextual hints to do your “templating”. I mean, I’m all for client side templating – but of the passive, unintrusive sort.
diyism (July 23, 2008 at 12:04 am)
Guys, try these:
<script>
(function(){
var cache = {};
if (document.all) // ie6
{var tag_start='<!';
var tag_end='!>';
}
else
{var tag_start='<!--';
var tag_end='-->';
}
this.tmpl = function tmpl(str, data){//alert(document.getElementById(str).innerHTML);
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +