JavaScript Micro-Templating


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):

  1. // Simple JavaScript Templating
  2. // John Resig - http://ejohn.org/ - MIT Licensed
  3. (function(){
  4.   var cache = {};
  5.  
  6.   this.tmpl = function tmpl(str, data){
  7.     // Figure out if we're getting a template, or if we need to
  8.     // load the template - and be sure to cache the result.
  9.     var fn = !/\W/.test(str) ?
  10.       cache[str] = cache[str] ||
  11.         tmpl(document.getElementById(str).innerHTML) :
  12.      
  13.       // Generate a reusable function that will serve as a template
  14.       // generator (and which will be cached).
  15.       new Function("obj",
  16.         "var p=[],print=function(){p.push.apply(p,arguments);};" +
  17.        
  18.         // Introduce the data as local variables using with(){}
  19.         "with(obj){p.push('" +
  20.        
  21.         // Convert the template into pure JavaScript
  22.         str
  23.           .replace(/[\r\t\n]/g, " ")
  24.           .split("<%").join("\t")
  25.           .replace(/((^|%>)[^\t]*)'/g, "$1\r")
  26.           .replace(/\t=(.*?)%>/g, "',$1,'")
  27.           .split("\t").join("');")
  28.           .split("%>").join("p.push('")
  29.           .split("\r").join("\\'")
  30.       + "');}return p.join('');");
  31.    
  32.     // Provide some basic currying to the user
  33.     return data ? fn( data ) : fn;
  34.   };
  35. })();

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):

  1. <script type="text/html" id="item_tmpl">
  2.   <div id="<%=id%>" class="<%=(i % 2 == 1 ? " even" : "")%>">
  3.     <div class="grid_1 alpha right">
  4.       <img class="righted" src="<%=profile_image_url%>"/>
  5.     </div>
  6.     <div class="grid_6 omega contents">
  7.       <p><b><a href="/<%=from_user%>"><%=from_user%></a>:</b> <%=text%></p>
  8.     </div>
  9.   </div>
  10. </script>

You can also inline script:

  1. <script type="text/html" id="user_tmpl">
  2.   <% for ( var i = 0; i < users.length; i++ ) { %>
  3.     <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
  4.   <% } %>
  5. </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:

  1. var results = document.getElementById("results");
  2. results.innerHTML = tmpl("item_tmpl", dataObject);

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:

  1. var show_user = tmpl("item_tmpl"), html = "";
  2. for ( var i = 0; i < users.length; i++ ) {
  3.   html += show_user( users[i] );
  4. }

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.

95 Comments (Show Comments)



Comments are closed.
Comments are automatically turned off two weeks after the original post. If you have a question concerning the content of this post, please feel free to contact me.


Secrets of the JavaScript Ninja

Secrets of the JS Ninja

Secret techniques of top JavaScript programmers. Published by Manning.

Ukiyo-e Database and Search

Ukiyo-e.org

Japanese woodblock print database and search engine.


John Resig Twitter Updates

@jeresig

Infrequent, short, updates and links.


via Ad Packs