Background
John Resig was the first to document a technique for storing templates in <script> tags;
<script type="text/html" id="item_template"> <li><b>{{ name }}</b> - {{ description }}</li> </script>
This works well for several reasons;
- Using the unknown type “text/html” means that the browser ignores it.
- Giving the tag an id allows us to easily grab its contents;
//Build a new item, and add it to the list //Using JQuery, Underscore.js var contents = _.template( $("#item_template").html(), {name: "Foo", description:"Sir Foo the Second"} ); $list.append( $(contents) );
Today there are many libraries available for retrieving and building templates in this way, such as Mustache.js and ICanHaz.js, and more that integrate support.
Problem
One of the only problems with this technique, is that the scripts must be declared inline.
<!-- Works! --> <script type="text/html" id="item_template"> <li><b>{{ name }}</b> - {{ description }}</li> </script> <!-- Doesn't work. --> <script type="text/html" id="item_template2" src="templates/item2.html"></script>
If the browser doesn’t understand the type, it won’t load the source. Whatever the reason, the template text is never added to the DOM.
Inlining template contents is detrimental to readability, maintainability, and traceability.
Some libraries offer asynchronous methods for loading templates. (require.js among them) Some have issues with complexity and performance.
It feels natural to define and compile the template functions at class definition time. Other solutions may add unnecessary layers of indirection.
Solution
I’ll preface this by saying “Okay, maybe this won’t work for you”, but so far it’s working well, transparently, and fast. And when you go to production, a decent deployment optimisation process will inline the templates anyway.
What if the src attributes were read properly?
<script type="text/html" id="template_widgets" src="templates/widgets.html"></script> <script type="text/html" id="template_widgets_item src="templates/widgets_item.html"></script> ... ... <script type="text/javascript"> //Synchronously load the templates var $templates = $('script[type="text/html"]'); $templates.each(function() { //Only get non-inline templates //No need to change code for production. if ($(this).attr("src")) { $.ajax( $(this).attr("src"), { async:false, context:this, success:function(data) { $(this).html(data); } } ); } }); //From this point onwards (in the same script, in the //following scripts) all the "text/html" script tags //will be loaded as if they were inline. </script>
Just insert this code after all your template tags, and before you build your template functions, and you’ll be able to treat them as if they were inline all along.
Easy.* 🙂
*There are some downsides to loading template serially and synchronously. I’ve written a follow-up article that discusses a technically better solution.