jQuery makes it pretty easy to manipulate client side elements, so it’s not all that often that I think about using something like client side templating to get content loaded into pages. However, recently I have been working on a few apps that had fairly complex list based layouts and it started getting  tedious to use manual code to update all the items. Further doing it by hand can be brittle if your layout changes as you have to keep the layout and the update code in sync.

Templating can address this problem by letting you use templates that describe your output with ‘holes’ that are filled in with data when the template is processed. Templating is a good solution in a few scenarios:

  • Loading all data from the server especially in rich list displays
  • Adding or updating new items in lists
  • Anywhere you need to add new complex content to the page
  • Anything that requires client side HTML rendering

All of these scenarios have in common that new items are created and these items are injected into the page from the client.

‘Manual’ Templating

Templating can take many forms actually and it may not even require an official templating engine to feed data into the page. For example in a number of applications I’ve been using hidden elements that serve as a template into which data is then filled manually via jQuery code. Take this layout for example:

TemplateManual

which is filled from a ‘manual’ template that looks like this:

<div id="StockItemTemplate" class="itemtemplate" style="display:none">
     <div class="stockicon"></div>
     <div class="itemtools">
         <a href='javascript:{}' class="hoverbutton" 
onclick="DeleteQuote(this.parentNode.parentNode,event);return false;">
<
img src="../images/remove.gif" /></a> </div> <div class="itemstockname"></div> <div class="itemdetail"> <table style="padding: 5px;"><tr> <td>Last Trade:</td> <td id="tdLastPrice" class="stockvaluecolumn"></td> <td>Qty:</td> <td id="tdLastQty" class="stockvaluecolumn"></td> <td>Holdings:</td> <td id="tdItemValue" class="stockvaluecolumn"></td> <td id="tdTradeDate" colspan="2"></td> </tr></table> </div> </div>

The ‘template’ is a single hidden element in the page that is the empty layout of each of the template items that is loaded without any data applied to it. When the items are loaded from the server via an AJAX callback an array of Stock items are retrieved and they are then merged via code that finds each element and assigns the value.

function LoadQuotes()
{
    if (!userToken)
        return;   // *** not logged in        
        
    proxy.invoke("GetPortfolioItems",
         {userToken: userToken },
         function( message ) {            
            $("#lstPortfolioContainer").empty();
            
            $.each( message.Items,function(i) 
            {
                var item = this;   // this is the iterated item!
                
                // *** Create a new node from the template by cloning
                var newEl = $("#StockItemTemplate").clone()
                                .attr("id",item.Pk + "_STOCK")
                                .fadeIn("slow");                       
                
                // *** dump the data into it
                UpdatePortfolioItem(newEl,item);                        
                
                // *** Append item to the list view container and hook up click event for detail
                newEl.click(function() { ShowStockEditWindow(newEl); } )    
                     .appendTo("#lstPortfolioContainer");
            });

            // *** Update totals    
            $("#spanPortfolioTotal").text( message.TotalValue.formatNumber("c") );
            $("#divPortfolioCount").text( message.TotalItems.formatNumber("f0") + " items");
         },
         OnPageError);
}
function UpdatePortfolioItem(jItem,stock)
{                    
    // *** Now fill in the stock data 
    jItem.find(".itemstockname").text(stock.Symbol + " - " + stock.Company);        
    jItem.find("#tdLastPrice").text(stock.LastPrice.toFixed(2));
    jItem.find("#tdLastQty").text(stock.Qty.toFixed(0));
    jItem.find("#tdItemValue").text(stock.ItemValue.formatNumber("c"));
    jItem.find("#tdTradeDate").text("as of: " + stock.LastDate.formatDate("MMM dd, hh:mmt") ); 
}

The manual templating works by cloning the template element, assigning a new ID to it, filling it with data and then injecting it into the document in the right place – in this case into the list container.

This is a code centric approach and it’s pretty straight forward albeit a bit tedious and as mentioned potentially brittle if the template is changed.

Copy and Fill Templating

A similar approach that doesn’t require a separate template can be used if you need to add or update items in a list. Rather than cloning an empty template that is separately loaded into the page (and which some Html purists would complain about for document clarity) you can pick up an existing item on the page and duplicate it.

So in the example above instead of cloning a template I can select the first div tag and clone it:

var newEl = $("#lstPortfolioContainer>div:first-child").clone()
                .attr("id", item.Pk + "_STOCK");
     

This works as long you start off with existing content and you’re guaranteed that SOME content exists to clone from.

In the example above this wouldn’t work because the list renders initially empty and is filled from the client, but the copy and fill can work well in forms where you add new items or update existing ones and avoids any template embedding into the page. This can be especially useful for ASP.NET applications that fill lists with data server side and you only need to update or add items.

Although this approach doesn’t work for everything, when it does work it can be a great time saver because you don’t have to duplicate anything as you are simply reusing what was already rendered server side. I’ve used this frequently for client side updates of grids for example.

jTemplates

Manual embedding works, but as you can see it can be a bit tedious to find and update each item

There are few template engines available for jQuery and the one I’ve used for a while is jTemplates. jTemplates is fairly easy to use and it works reliably, although I have to say that I’m not a fan of the python like template syntax. But it works and is fairly powerful in terms of what you can accomplish. jTemplates work by taking the template and turning it into Javascript code that gets executed which means that template placeholder items can include expressions that reference other code.

Let’s look at another example. Here’s a form that’s my admin form my currently reading list on this blog. The list is originally rendered with a ListView control on the server. I can then go in and add or edit items which pops up ‘dialog’ ontop of the existing content:

jtemplate1

When the Save button is clicked the book list is updated or a new item added using jTemplates. The jTemplates template for an individual item looks like this:

<script type="text/html" id="item_template">    
    <div style="float: right;" id="divBookOptions">
        <a href="javascript:void(0);" onclick="removeBook(this,event);return false;" >
            <img border="0" src="../images/remove.gif" class="hoverbutton"/>
        </a>
        <br />
        <small>{$T.SortOrder}</small>
    </div>
    <img src="{$T.AmazonImage}" id="imgAmazon"/>
    <b><a href="{$T.AmazonUrl}" target="_blank" />{$T.Title}</b>
    <br/>
    <small>{$T.Author}</small>
    <br/>
    {#if $T.Highlight}<small><i>Highlighted</i></small>{#/if}   
</script>

Note the little trick of using a <script type=”text/html”> which is a little gimme from John Resig that allows hiding any markup in the document without interfering with HTML validators. The script can be accessed by its ID and the content retrieved using the .html():

var template = $("#item_template").html();

which gives you the template text. This is a useful trick for all sorts of things that you might want to embed into an HTML document as Data.

jTemplates uses python like syntax and the $T is a reference to the data item passed to the template. Data can be passed in as an object and you can reference the object’s properties off this $T data item.

Here’s the code that is used to save or add a new book to the list (both updating the server database and the list on screen):

function saveBook(ctl)
{
    var jItem = $(ctl);
   
    var book = activeBook;

   
// must assign the Pk reference
  
book.Pk = bookPk;
    if(bookPk < 1)
       bookPk = -1;
   
    book.Title = $("#"+ scriptVars.txtTitleId).val();
    book.Author = $("#"+ scriptVars.txtAuthorId).val();
    book.AmazonUrl =  $("#"+ scriptVars.txtAmazonUrlId).val();
    book.AmazonImage = $("#"+ scriptVars.txtAmazonImageId).val();
    book.SortOrder = $("#"+ scriptVars.txtSortOrderId).val();
    book.Highlight = $("#"+ scriptVars.chkHighlightId).attr("checked");
    book.Category = $("#"+ scriptVars.txtBookCategoryId).val();
   
    if(book.SortOrder)
        book.SortOrder = parseInt( book.SortOrder);
   
    showProgress();
    Proxy.SaveBook(book,
                   function(savedPk)
                   {
                        showProgress(true);
                        showStatus("Book has been saved.",5000);
                        panEditBook.hide();
                       
                        book.Pk = savedPk;
                        updateBook(book);
                   },
                   onPageError);
}

function updateBook(book)
{    
   var item = $(".bookitem[@tag=" +book.Pk +"]");    
      
   if (item.length < 1) {
        // create a new item wrapper to merge template inside of
        item = $("<div id='divBookItem' class='bookitem' tag='" + book.Pk + "' onclick='editBook(this);'></div>")
                    .appendTo("#divBookListWrapper");
    }
                     
    var template = $("#item_template").html();
    
    item.setTemplate(template);
    item.processTemplate(book);
}

The first function pulls the captured input values into a book object which is then submitted to the server. On the callback – when it worked – the book is updated and the list item is updated by calling the updateBook function. Inside that callback the existing item is selected or a new ‘wrapper’ created. Then the template is applied against that item and the parsed template replaces the inner content of the item selected. IOW, the template becomes the innerHTML.

The above is a simple example of a template that’s just a single item, but jTemplates also supports iteration over items as well as the ability to do some limited structured statements.

Here’s another example that’s used to render items from the Amazon Web Service which returns an array of Amazon items to the client:

<script type="text/html" id="amazon_item_template">    
{#foreach $T.Rows as row}
<div class="amazonitem" ondblclick="selectBook(this);" tag="{$T.row.Id}">
    <img src="{$T.row.SmallImageUrl}" style="float: left; margin-right: 10px;" />
    <div><b>{$T.row.Title}</b></div>
    <div><i>{$T.row.Publisher} &nbsp; ({$T.row.PublicationDate})</i></div>
    <small>{$T.row.Author}</small>
</div>
{#/for}
</script>

which is hooked up with code like this:

function showAmazonList()
{
    panBookList_DragBehavior.show();
    var search = $("#txtSearchBooks").val();
    if (!search)
       return;
                                        
    showProgress();        
    Proxy.GetAmazonItems( search,
                          $("#" + scriptVars.radSearchTypeId + " input:checked").val(),
                          $("#" + scriptVars.txtAmazonGroupId).val(),
                          function(matches) {
                                                    
                            showProgress(true);
                            bookList = matches;
                            var item = $("#divBookList_Content");                 
                  
                            item.setTemplate(  $("#amazon_item_template").html() );
                            item.processTemplate(matches);                            
                          },
                          onPageError);
}

Matches in this case is an array of AmazonLookupItems which looks like this:

[CallbackMethod]
public List<AmazonSearchResult> GetAmazonItems(string search, string type, string amazonGroup)
{
    AmazonLookup lookup = new AmazonLookup();
    List<AmazonSearchResult> result = lookup.SearchForBook(
            (type == "Title") ?
                Amazon.AmazonLookup.SearchCriteria.Title :
                Amazon.AmazonLookup.SearchCriteria.Author,
            search,
            amazonGroup);

    //result[0].

    return result;
}

The result is serialized as an array which is what

{#foreach $T.Rows as row}

{#/for}

iterates over.

You can also a see an example of the #if construct which allows you to conditionally display content.

{#if $T.Highlight}<small><i>Highlighted</i></small>{#/if}   

jTemplate supports only a few #directives including #if,#foreach,#for,#include,#param,#cycle which are few but admittedly enough for the most  common template scenarios.

I’ve used jTemplates in a few applications and it works well, but the syntax is really not to my liking. I also am not terribly fond of the way the plug-in works and how it assigns content as content. Making a tool like this a jQuery plug-in rather than a class that produces string output or at least allows options for that is one example of overemphasizing the jQuery metaphor.

John Resig’s Microtemplating engine

A couple of months ago John Resig posted a tiny little templating engine that is positively tiny. This engine is literally 20 lines of very terse (and yes obtuse) code. Heck I’ve looked at the regex expressions for a while now and I still have not quite figured out what it all does. It’s short enough I can post it here:

Updated code that fixes issue with single quotes (per Neil’s comment below):

// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function() {
    var cache = {};

    this.tmpl = function tmpl(str, data) {
        // 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);};" +

        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +

        // Convert the template into pure JavaScript
str.replace(/[\r\t\n]/g, " ") .replace(/'(?=[^%]*%>)/g,"\t") .split("'").join("\\'") .split("\t").join("'") .replace(/<%=(.+?)%>/g, "',$1,'") .split("<%").join("');") .split("%>").join("p.push('") + "');}return p.join('');");
        // Provide some basic currying to the user
        return data ? fn(data) : fn;
    };
})();

Basically it turns a template into a block of JavaScript code and then executes that code. The syntax is ASP style markup using <%= expression %> and <% codeblock %> syntax to handle code embedding.

What’s nice about this approach is that you can utilize any Javascript in the template and you’re not limited to just a few commands. The other thing that’s really nice is that it’s really compact – in fact I’ve integrated it into my own client library with a couple of small modifications. The main change I had to make for myself is that I can’t use <% %> because I’m using the script in another library where <% %> is always evaluated as server side script (note – ASP.NET is fine with the <% %> as long as you put it inside <script type="text/html"> which the ASP.NET parser is smart enough to leave alone. But even for ASP.NET I prefer to use a different script extension just to be clear. So I ended up using <# #> for delimiters. I also added some error handling so that maybe more error information can be seen if something goes wrong with the template.

Here’s my slightly modified and deconstructed version called parseTemplate:

Updated code that fixes issue with single quotes (per Neil’s comment below in italics):

var _tmplCache = {}
this.parseTemplate = function(str, data) {
    /// <summary>
    /// Client side template parser that uses &lt;#= #&gt; and &lt;# code #&gt; expressions.
    /// and # # code blocks for template expansion.
    /// NOTE: chokes on single quotes in the document in some situations
    ///       use &amp;rsquo; for literals in text and avoid any single quote
    ///       attribute delimiters.
    /// </summary>    
    /// <param name="str" type="string">The text of the template to expand</param>    
    /// <param name="data" type="var">
    /// Any data that is to be merged. Pass an object and
    /// that object's properties are visible as variables.
    /// </param>    
    /// <returns type="string" />  
    var err = "";
    try {
        var func = _tmplCache[str];
        if (!func) {
            var strFunc =
            "var p=[],print=function(){p.push.apply(p,arguments);};" +
                        "with(obj){p.push('" +
            //                        str
            //                  .replace(/[\r\t\n]/g, " ")
            //                  .split("<#").join("\t")
            //                  .replace(/((^|#>)[^\t]*)'/g, "$1\r")
            //                  .replace(/\t=(.*?)#>/g, "',$1,'")
            //                  .split("\t").join("');")
            //                  .split("#>").join("p.push('")
            //                  .split("\r").join("\\'") + "');}return p.join('');";

            str.replace(/[\r\t\n]/g, " ")
               .replace(/'(?=[^#]*#>)/g, "\t")
               .split("'").join("\\'")
               .split("\t").join("'")
               .replace(/<#=(.+?)#>/g, "',$1,'")
               .split("<#").join("');")
               .split("#>").join("p.push('")
               + "');}return p.join('');";

            //alert(strFunc);
            func = new Function("obj", strFunc);
            _tmplCache[str] = func;
        }
        return func(data);
    } catch (e) { err = e.message; }
    return "< # ERROR: " + err.htmlEncode() + " # >";
}

If you want to see what the generated code looks like uncomment the alert line – you’ll find that the template is deconstructed into a string of Javascript code that is then created as function and executed with the data parameter as an argument. The data parameter becomes the context of the call so that the properties of the object are available.

This is sort of the same approach like WCF uses of a ‘wrapped’ object  - to pass multiple parameters you can simply create a map:

parseTemplate($("#ItemTemplate").html(),
{ name: "rick", address: { street: "32 kaiea", city: "paia"} } );

where a template might be:

<script id="ItemTemplate" type="text/html">
 <div>
    <div><#= name #></div>
    <div><#= address.street #></div>
 </div>
</script>

You can also loop through a list of items by using code blocks. Imagine you got a list of stocks returned as an array as I showed earlier. You can then do:

var s = parseTemplate($("#StockListTemplate").html(), { stocks: message.listresult.Rows } );

which is then applied against this template

 <script id="StockListTemplate" type="text/html">
 <# for(var i=0; i < stocks.length; i++)     
{
var stock = stocks[i]; #> <div> <div><#= stock.company #> ( <#= stock.symbol #>)</div> <div><#= stock.lastprice.formatNumber("c") #></div> </div> <# } #> </script>

Effectively any child properties of the passed object are available as variables in the template courtesy of the with() construct in the generated Javascript code.

Personally I prefer to do scripting this way to what jTemplates does simply because you effectively have access to full Javascript functionality in the template. It’s also a more familiar approach if you’ve used any sort of ASP.NET scripting.

To put this in perspective here’s the first example where I manually loaded up the stock template replaced with the parseTemplate approach. In this example I use a single item template and use code to loop through list rather than having the template do it

The following is a script template similar to the stock template in the first example:

<script id="ItemTemplate" type="text/html">
<# for(var i=0; i < stocks.length; i++)     
   {         
     var stock = stocks[i]; 
 #>
 <div id="<#= stock.pk #>_STOCK" class="itemtemplate" style="display:none" onclick="ShowStockEditWindow(this);">
     <div class="stockicon"></div>
     <div class="itemtools">
         <a href="javascript:{}" class="hoverbutton" onclick="DeleteQuote(this.parentNode.parentNode,event);return false;">
         <img src="../images/remove.gif" /></a>
     </div>
     <div class="itemstockname"><#= stock.symbol #> - <#= stock.company #></div>        
     <div class="itemdetail">
         <table style="padding: 5px;"><tr>
             <td>Last Trade:</td>
             <td id="tdLastPrice" class="stockvaluecolumn"><#= stock.lastprice.toFixed(2) #></td>
             <td>Qty:</td>
             <td id="tdLastQty" class="stockvaluecolumn"><#= stock.qty #></td>
             <td>Holdings:</td>
             <td id="tdItemValue" class="stockvaluecolumn"><#= stock.itemvalue.formatNumber("c") #></td>                
             <td id="tdTradeDate" colspan="2"><#= stock.lastdate.formatDate("MMM dd, hh:mmt") #></td>
         </tr></table>
     </div>
</div>
<# } #>
</script>

In this example the template is loaded either individually or  updated in a loop to load all quotes:

function LoadQuotes()
{
    Proxy.callMethod("GetPortfolioItems",
         [],
         function(message) {
             $("#lstPortfolioContainer").empty();

             if (!message)
                 return;

             if (message.listresult) {

                 // Parse template with stock rows as array input
                 var html = parseTemplate($("#ItemTemplate").html(), 
                                         { stocks: message.listresult.Rows });
                 $(html).fadeIn("slow")
                        .appendTo("#lstPortfolioContainer");
             }

             // *** Update totals    
             $("#spanPortfolioTotal").text(message.totalvalue.formatNumber("c"));
             $("#divPortfolioCount").text(message.totalitems.formatNumber("f0") + " items");
         },
         OnPageError);
}

As you can see the Javascript code has been reduced significantly and the template – to me at least – is very easy to parse understand modify.

A problem with Single Quotes

As nice as the MicroTemplating engine is there is one problem: The parser has problems with templates that contain single quotes as literal text in some cases. The RegEx expression tries to allow for single quotes and it does in some cases work. But if you use single quotes to wrap attribute values the parser will fail with an ugly string error in the parseTemplate function because the single quote will end up as the delimiter for the function string resulting in invalid Javascript code to evaluate.

While this isn’t a big issue since it should be easy to avoid single quotes in markup and you can use &rsqutoe; for quote literals in HTML markup it’s still a bit inconsistent.

Updated code that fixes issue with single quotes (per Neil’s comment below)

Other Javascript Templating

Microsoft is also at it again as well with a new client template  engine slated for Version 4.0 of ASP.NET. MS  originally had client side templates in ATLAS which were later pulled – a good thing this didn’t make it because the XML based markup script was painful to work with with a hideous repetitious and self referencing model that was confusing as hell. The new template engine looks a lot cleaner and is bound and generally follows the same principles that I’ve shown above with jTemplates or the John Resig’s MicroTemplate parser.

Dave Ward has a great blog post with a sample that shows the basics of client templates and Bertrand also has an article on these templates in MSDN in the current issue. BTW, if you like the jQuery content here make sure you also subscribe to Dave’s RSS feed – he’s got some kick ass content on jQuery and ASP.NET.

I haven’t had a chance to look at this stuff other than reading through the articles. While I think this stuff looks very promising I can’t I’m too excited about it – mainly because it still relies on the rest of the Microsoft Client Library. Just to do scripting that’s a bit much of a hit especially when I already have alternatives in this space. But if you’re already using ASP.NET AJAX then the new features are a natural path to take.

Client Templating – Great for pure Client Implementations

I’m glad to see that there are a few templating solutions available. Templating makes creating pure client interfacing much easier and it brings some of the concepts that you might be familiar with ASP.NET server applications closer to home. After all things like the ListView, Repeater and other repeating controls are essentially template engines and many similar concepts are used in the client engines. Templates make it possible to let you render rich UI on the client without server markup and yet still let you keep the markup in one place (the template) and even lets you edit the content in your favorite designer if you choose to place the template into a regular document element that maybe is merely invisible.

Personally I like the approach of the MicroTemplate best because it’s dead simple and hits the right notes for me. I don’t need a fancy templating language if I can use JavaScript in my template to perform simple structural blocks and looping. No need to learn some funky template markup language but rather use what you know.