Channels ▼
RSS

.NET

Minimize Code by Using jQuery and Data Templates


Dan Wahlin (Microsoft Most Valuable Professional for ASP.NET and XML Web Services) is the founder of The Wahlin Group (www.TheWahlinGroup.com) which provides .NET, SharePoint, and Silverlight consulting and training services. Dan blogs at http://weblogs.asp.net/dwahlin.


I'm currently working on a heavily AJAX-oriented ASP.NET MVC web application for a business client and using jQuery to call controller actions, retrieve JSON data and then manipulate the DOM to display the data. Several of the pages have quite a bit of dynamic HTML that has to be generated once a JSON object is returned from an MVC controller action which generally leads to a lot of custom JavaScript. After working through my first page on the project I realized that I was creating a maintenance nightmare due to the amount of JavaScript being written and decided to look into other options.

The first thing I looked for was some type of JavaScript template that would work much like GridView templates in ASP.NET. I wanted to be able to define a template in HTML and then bind a JSON object against it. That way I could easily tweak the template without having to actually touch my JavaScript code much. I found several potential template solutions (and Microsoft will be releasing a nice option with ASP.NET 4.0 as well) that were nice but many were so CSS class centric that they ended up being a turn off since I felt like I had to learn yet another coding style just to use them.

I eventually came across one by John Resig (creator of jQuery and overall JavaScript genius) that was so small that I wasn't sure it would even be viable. I mean we're talking tiny as far as code goes…so tiny that I figured it wouldn't work well for what I needed. After doing more searching and research I came across a post by Rick Strahl that mentioned John's micro template technique and had a few tweaks in it. I tried it and was instantly hooked because it gave me the power to use templates yet still embed JavaScript to perform basic presentation logic (loops, conditionals, etc.) as needed.

The Template Engine

The first thing I did was add the template function as a jQuery extension so that I could get to it using familiar jQuery syntax. This isn't required, but makes the template convenient to use. I ended up going with Rick's slightly tweaked version and I only changed how the error was reported. I'm not going to go into how to extend jQuery in this article, but here's what the parseTemplate extension function looks like:


$.fn.parseTemplate = function(data)
{
    var str = (this).html();
    var _tmplCache = {}
    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, " ")
               .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.toString() + " # >";
}

That's all the code for the template engine. Unbelievable really. Runs on chipmunk power.

Creating a Template

Once the extension function was ready I had to create a template in my MVC view (note that this works fine in any web application, not just ASP.NET MVC) that described how the JSON data should be presented. Templates are placed inside of a script tag as shown next (I chopped out most of the template to keep it more concise):


<script id="MenuSummaryTemplate" type="text/html">
     <table style="width:100%;">
        <tbody>             
            <tr>
                <td class="OrderHeader">Totals:</td>                    
            </tr>                    
            <tr>
                <td style="font-size:12pt;">
                    <table style="width:400px;">
                        <tr>
                            <td style="width:50%;">Sub Total:</td>
                            <td>
                                 $<span id="FinalSubTotal">
                                   <#= FinalSubTotal #>
                                 </span>
                            </td>
                        </tr>
                        <tr>
                            <td>Sales Tax:</td>
                            <td>
                                $<span id="FinalSalesTax">
                                  <#= FinalSalesTax #>
                                 </span>
                            </td>
                        </tr>
                        <# if (DeliveryFee > 0) { #>
                        <tr>
                            <td>Delivery Fee:</td>
                            <td>
                               $<span id="DeliveryFee">
                                  <#= DeliveryFee #>
                                </span>
                            </td>
                        </tr>
                        <# } #>
                        <tr>
                            <td>Admin Fee:</td>
                            <td>
                               $<span id="AdminFee">
                                  <#= AdminFee #>
                                </span>
                            </td>
                        </tr>                                
                        <tr style="border-top:1px solid black;">
                            <td>Total:</td>
                            <td>
                               $<span id="FinalTotal">
                                   <#= FinalTotal #>
                                </span>
                            </td>
                        </tr>   
                        <tr>
                            <td colspan="2"> </td>
                        </tr>
                        <tr>
                            <td colspan="2">Will be charged to 
                              your credit card ending with 
                              <#= CreditCard #>
                            </td>
                        </tr>                     
                    </table>                        
                </td>
            </tr>
           <!-- More of the template would follow -->
        </tbody>
    </table>    
</script>

You can see that the script block template container has a type of text/html and that the template uses <#= #> blocks to define placeholders for JSON properties that are bound to the template. The text/html type is a trick to hide the template from the browser and I suspect some may not like that -- you're call though. I'm just showing one option. The template supports embedding JavaScript logic into it which is one of my favorite features.

After a little thought you may wonder why I didn't simply update the spans and divs using simple JavaScript and avoid the template completely. By using a template my coding is cut-down to two lines of JavaScript code once the JSON object is created (which you'll see in a moment) and this is only part of the template. Here's another section of it that handles looping through menu items and creating rows:


<#
   if (MainItems == null || MainItems.length == 0)
   {
#>
    <tr>
        <td>No items selected</td>
    </tr>
<#
   }
   else
   {
       for(var i=0; i < MainItems.length; i++)     
       {      
         var mmi = MainItems[i]; 
#>
        <tr>
           <td>
                <#= mmi.Name #>:  <#= mmi.NumberOfPeople #> 
                ordered at $<#= mmi.PricePerPerson #> per person
           </td>
        </tr>
<# 
       }
   } 
#>

Binding Data to a Template

To bind JSON data to the template I can call my jQuery extension named parseTemplate(), get back the final HTML as a string and then add that into the DOM. Here's an example of binding to the template shown above. I went ahead and left the JSON data that's being bound in so that you could see it, but jump to the bottom of LoadApprovalDiv() to see where I bind the JSON object to the template. It's only two lines of code.


function LoadApprovalDiv()
{
    var subTotal = parseFloat($('#SubTotal').text());
    var salesTaxRate = parseFloat($('#SalesTaxRate').val()) / 100;
    var salesTaxAmount = subTotal * salesTaxRate;
    var deliveryFee = parseFloat($('#DeliveryFee').val());
    var adminFee = (subTotal + salesTaxAmount + deliveryFee) * .05;
    var total = subTotal + salesTaxAmount + deliveryFee + adminFee;
    var deliveryAddress = $('#Delivery_Street').val() + ' ' + 
      $('#Delivery_City').val() +
      " " + $('#Delivery_StateID option:selected').text() + ' ' + 
      $('#Delivery_Zip').val();
    var creditCard = $('#Payment_CreditCardNumber').val();
    var abbrCreditCard = '*' + 
      creditCard.substring(creditCard.length - 5);

    var json = {
                   'FinalSubTotal'  : subTotal.toFixed(2),
                   'FinalSalesTax'  : salesTaxAmount.toFixed(2),
                   'FinalTotal'     : total.toFixed(2),
                   'DeliveryFee'    : deliveryFee.toFixed(2),
                   'AdminFee'       : adminFee.toFixed(2),
                   'DeliveryName'   : $('#Delivery_Name').val(),
                   'DeliveryAddress': deliveryAddress,
                   'CreditCard'     : abbrCreditCard,
                   'DeliveryDate'   : $('#Delivery_DeliveryDate').val(),
                   'DeliveryTime'   : $('#Delivery_DeliveryTime 
                                         option:selected').text(),
                   'MainItems'      : GenerateJson('Main'),
                   'SideItems'      : GenerateJson('Side'),
                   'DesertItems'    : GenerateJson('Desert'),
                   'DrinkItems'     : GenerateJson('Drink')
               };

       var s = $('#MenuSummaryTemplate').parseTemplate(json);
       $('#MenuSummaryOutput').html(s);
}

You can see that I call parseTemplate(), pass in the template to use and JSON object and then get back a string. I then add the string into a div with an ID of MenuSummaryOutput using jQuery. Here's a sample of what the template generates:

Going this route cut down my JavaScript code by at least 75% over what I had originally and makes it really easy to maintain. If I need to add a new CSS style or modify how things are presented I can simply change the template and avoid writing custom JavaScript code. By using the template and AJAX calls I've been able to significantly minimize the amount of server code being written and meet the client's requirement of having an extremely fast and snappy end user experience.

If you're writing a lot of custom JavaScript currently to generate DOM objects I'd highly recommend looking into this template or some of the other template solutions out there. I can't say I've tested performance but can say that I'm working with some fairly large templates which are loading in less than 1 second. I personally feel it's the way to go especially if you want to minimize code and simplify maintenance. I think Microsoft's entry into this area with ASP.NET 4.0 further validates the usefulness of client-side templates.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video