jQuery Templates Proposal

Alberto La Rocca edited this page Jun 28, 2013 · 74 revisions
Clone this wiki locally

Contents

Note: If you are looking for the official jQuery Templates plugin, you can find it here: http://github.com/jquery/jquery-tmpl

Please note: This proposal, presented in February 2010, does not correspond to the actual jQuery Templates plugin design. For links to documentation on the current design, and for context on how the jQuery Templates plugin evolved subsequently to this proposal, see jQuery Templates is now an Official jQuery Plugin.

Introduction

This proposal describes how support for templates can be added to the jQuery core library. In particular, this proposal describes a new jQuery method – named render() – that enables you to render a single JavaScript object or array of JavaScript objects by using a fragment of HTML as a template.

The goal of this proposal is to enable plug-in developers to take advantage of a standard method for declaring and rendering templates. Having a standard method for declaring and rendering templates benefits everyone:

  • Plug-in developers can build rich, database-driven plug-ins such as DataGrid plug-ins. Plug-in developers can build on top of the support for templates in jQuery core and avoid re-implementing the code for rendering templates each time they need to develop a new plug-in.
  • Plug-in users can take advantage of a standard syntax for declaring templates. Plug-in users won’t need to learn a new way of creating a template each and every time they start using a new plug-in.

This proposal is divided into two main sections. The first section contains a brief survey of existing templating solutions. The second section contains our recommendation for how a standard method for declaring and rendering a template can be added to jQuery core.

Existing Templating Solutions

Indicating the popularity and need for a standard templating solution, there already are many existing JavaScript templating solutions. In this section, you are provided with a brief overview of four of the most popular and interesting ones. What are the existing templating solutions doing right? What features would make sense in jQuery core?

Micro-Templating

John Resig’s micro-templating engine is extremely small (only 2KB uncompressed). However, this tiny bit of code captures the core functionality needed to render a template.

Here’s an example of how you can use the micro-templating engine to display a single JavaScript product object:

<script src="../jquery-1.4.1.js" type="text/javascript"></script>
<script src="MicroTemplating.js" type="text/javascript"></script>

<script type="text/javascript">

    var product = { name: "Laptop", price: 788.67 };

    $(showProduct);

    function showProduct() {
        $("#results").html( tmpl("productTemplate", product) );
    }

    function formatPrice(price) {
        return "$" + price;
    }
    
</script>

The tmpl() method is used to generate a string from a product template and product object. The result is assigned to the innerHTML of a DIV element named results.

The product template is defined in the body of the page within a SCRIPT element:

<script type="text/html" id="productTemplate">
    Product Name: <%= name %>
    <br />
    Product Price: <%= formatPrice(price) %> 
</script> 

<div id="results"></div>

Notice that the SCRIPT element’s type attribute has the value “text/html”. Web browsers ignore the contents of SCRIPT elements declared in this way and treat the contents of the SCRIPT element as a string.

Notice that the template includes expressions that represent the product name and price properties. The JavaScript formatPrice() method is called to format the product price. You can call any JavaScript function within a template.

Here’s how you would render an array of JavaScript product objects:

function showProducts() {
    // parse the template
    var template = tmpl("productTemplate");

    // loop through the products
    var results = '';
    for (var i = 0; i < products.length; i++) {
        results += template(products[i]);
    }

    // show the results
    $("#results").html(results);
}

The tmpl() method supports currying. If you call the tmpl() method without supplying data, then the method returns a JavaScript function that represents the parsed template. The returned method accepts a single data parameter.

In the code above, the template is parsed, and then the template method is called for each product to build a string. Finally, the string is assigned to the innerHTML of a DIV element named results.

Handlebars.js

The Handlebars templating engine can be used to render templates to jQuery objects using a `render` method through the handlebars plugin.

The plugin loads template files though AJAX and lazily precompiles and caches them. Support for partials is also provided.

While using this plugin you can still use the global `Handlebars` object so as to access all of handlebars.js’s features.

jTemplates

jTemplates is a feature rich templating engine that is implemented as a jQuery plug-in. jTemplates supports a number of advanced features including:

  • External Templates –You can load a template from an external file by supplying a URL. The external file can contain multiple templates.
  • External Data – You can load data from an external URL.
  • Live Refresh – You can update the content of a template on a regular interval automatically.
  • HTML Encoding – You can prevent JavaScript injection attacks by encoding characters such as < and >.
  • Includes – You can include one template in another template.
  • Debug Mode – You can cause the templating engine to break on errors and display error messages.

Here is sample code that demonstrates how you can display a list of products using jTemplates:

<script src="../jquery-1.4.1.js" type="text/javascript"></script>
<script src="jquery-jtemplates_uncompressed.js" type="text/javascript"></script>

<script type="text/javascript">

    var data = { 
        products: [
            { name: "Laptop", price: 788.67 },
            { name: "Comb", price: 2.50 },
            { name: "Pencil", price: 1.99 }
        ]};

    $(showProducts);

    function showProducts() {
        // attach the template
        $("#results").setTemplateElement("template");
    
        // process the template
        $("#results").processTemplate(data);
    }

    function formatPrice(price) {
        return "$" + price;
    }
    
</script>

The setTemplateElement() method assigns a template to an HTML element. The processTemplate() method processes the template using the supplied data.

In the code above, the template is loaded from a TEXTAREA element named template. Here is what the template looks like in the body of the page:

<textarea id="template" style="display:none">
    {#foreach $T.products as product}
    <div>
        Product Name: {$T.product.name}
        <br />
        Product Price: {formatPrice($T.product.price)} 
    </div>
    {#/for}
</textarea>

Notice that a jTemplate template can contain special commands such as #foreach, #for, and #if. As the call to formatPrice() illustrates, a template also can contain calls to arbitrary JavaScript functions.

By default, to prevent JavaScript injection attacks, jTemplate HTML encodes special characters included in the data passed to a template. For example, if the name of a product is “<b>Laptop</b>” then the name is converted to “&lt;b&gt;Laptop&lt;/b&gt;”.

jTemplates enables you to load both templates and data from an external URL. For example, the following code loads a template from a file named MyTemplates.htm and a set of data from a file named MyData.htm:

function showProducts() {

    $.jTemplatesDebugMode(true);

    // attach the template
    $("#results").setTemplateURL("MyTemplate.htm");

    // process the template
    $("#results").processTemplateURL("MyData.htm");
}

The MyTemplate.htm file looks like this:

{#foreach $T.products as product}
<div>
    Product Name: {$T.product.name}
    <br />
    Product Price: {formatPrice($T.product.price)} 
</div>
{#/for}

Although this feature is not illustrated by the MyTemplate.htm file, jTemplates also enables you to define multiple templates in a single file.

Finally, the MyData.htm file looks like this:

{"products": [
  { "name": "Laptop", "price": "788.67" },
  { "name": "Comb", "price": 2.50 },
  { "name": "Pencil", "price": 1.99 }
] }

The contents contained in MyData.htm, of course, could be generated from a database dynamically.

PURE (Pure Unobtrusive Rendering Engine)

The PURE template engine is designed to enable developers to declare templates without using any special markup. There are two ways to use PURE: using the autoRender() method or using the render() method.

When you use the autoRender() method, PURE maps JSON property names to Cascading Style Sheet class names automatically. For example, here’s how you can write code to display a single product:

$(showProduct);

function showProduct() {
    var product = { name: "Laptop", price: 788.67 };

    $('div.product').autoRender(product);
}

The autoRender() method displays the product by mapping the product name property to the CSS name class and the product price property to the CSS price class:

<div class="product">
    Product Name: <span class="name"></span>
    <br />
    Product Price: <span class="price"></span>        
</div>

Notice that there are no special characters in the template. PURE does everything with pure HTML.

The autoRender() method relies on a convention that JSON property names map to CSS class names. If you don’t want to rely on this convention, then you can use the render() method instead:

function showProduct() {
    var product = { name: "Laptop", price: 788.67 };
    var directives = { 'span#name' : 'name', 'span#price': 'price'};

    $('div.product').render(product, directives);
}

Notice that a set of directives is passed to the render() method with the item to be displayed. The directives map selectors to JSON property names. The first directive maps the SPAN element with an ID of name to the name property and the second directive maps the SPAN element with an ID of price to the price property. Here’s what the HTML looks like:

<div class="product">
    Product Name: <span id="name"></span>
    <br />
    Product Price: <span id="price"></span>        
</div>

PURE also can be used to render an array of JavaScript objects. For example, the following code renders an array of products:

function showProducts() {

    var data = { "products": [
            { name: "Laptop", price: 788.67 },
            { name: "Comb", price: 2.50 },
            { name: "Pencil", price: 1.99 }
        ]};

    $('#productTemplate').autoRender(data);
}

Because the array is named products, PURE maps each product to a CSS class named products. The following template displays all three products:

<div id="productTemplate">
<div class="products">
    Product Name: <span class="name"></span>
    <br />
    Product Price: <span class="price"></span>        
</div>
</div>

ASP.NET Ajax Templates

The ASP.NET Ajax library includes support for client templates. This library supports a number of advanced features:

  • Pseudo variables ‚Äì You can use a special set of variables in a template such as the $index variable that represents the current index of a template instance.
  • Dynamic templates ‚Äì You can change the template dynamically when rendering an array of JavaScript objects.
  • Dynamic placeholders ‚Äì You can change the template placeholder dynamically when rendering an array of JavaScript objects.

For example, you can use the following code to render a list of products in a template:

var products = [
    { name: "Laptop", price: 788.67 },
    { name: "Comb", price: 2.50 },
    { name: "Pencil", price: 1.99 }            
];


Sys.require([Sys.components.dataView], function () {

    $("#products").dataView(
    {
        data: products
    });

});

The dataView() method is used to display the products array in a template. The template is contained in a DIV element named products:

<div id="products" class="sys-template">
    <div id="{{ $id('product') }}">
        Product Name: {{ name }}
        <br />
        Product Price: {{ formatPrice(price) }} 
    </div>
</div>

Notice that an ASP.NET Ajax template is just a DOM element. Because a template is just a DOM element, the template does not need to be wrapped in a SCRIPT or TEXTAREA tag or HTML comments. In this case, a new instance of the inner DIV element is created for each product.

Notice, furthermore, the $id() pseudo-variable. The $id() variable solves a problem related to templates and element IDs. If you add an element with an ID to a template, and the template is used with a collection of items, then you will end up with duplicate IDs. The $id() variable enables you to avoid this problem by generating unique IDs for each template instance.

The ASP.NET Ajax library also supports something called dynamic templates. For example, imagine that you have created one template for new products and one template for normal products that look like this:

<!-- New template -->
<div id="newTemplate" class="sys-template">
    <div>
        <img src="new.gif" />
        Product Name: {{ name }}
        <br />
        Product Price: {{ formatPrice(price) }} 
    </div>
</div>

<!-- Normal template -->
<div id="normalTemplate" class="sys-template">
    <div>
        Product Name: {{ name }}
        <br />
        Product Price: {{ formatPrice(price) }} 
    </div>
</div>

The two templates are exactly the same except the new template includes an image that displays a New! icon.

You can create an itemRendering event handler that executes right before each template instance is displayed. Within the itemRendering event handler, you can specify the template that should be used to display a data item programmatically:

function itemRendering(dataView, args) {
    // Get the current data item
    var product = args.get_dataItem();

    // Set the template dynamically
    if (product.dateCreated.getFullYear() == 2010) {
        args.set_itemTemplate("#newTemplate");
    } else {
        args.set_itemTemplate("#normalTemplate");
    }
}

The code above uses one of two templates to display each product. If a product is new (it was created in the year 2010) then the product is displayed with the newTemplate template. Otherwise, the product is displayed with the normalTemplate template.

The ASP.NET Ajax library also supports something called dynamic placeholders. Dynamic placeholders enable you to display different items in different locations in a document. For example, you might want all of your new products to appear in a “new products” area of your document:

<h1>New Products</h1>
<div id="newPlaceholder"></div>

<h1>All Products</h1>
<div id="normalTemplate" class="sys-template">
    <div>
        Product Name: {{ name }}
        <br />
        Product Price: {{ formatPrice(price) }} 
    </div>
</div>

Notice the DIV element named newPlaceholder. You want all of your new products to appear here.

Here’s how you would write your itemRendering handler to place new products – products created in 2010 – in the new placeholder:

function itemRendering(dataView, args) {
    // Get the current data item
    var product = args.get_dataItem();

    // Set the template dynamically
    if (product.dateCreated.getFullYear() == 2010) {
        args.set_itemPlaceholder("#newPlaceholder");
    } 
}

jQuery Smarty

Project Page
Demonstration Page

The jQuery Smarty project supports the Smarty templating language (one of the most popular with PHP). This project supports a number of advanced features:

  • Pseudo variables ‚Äì You can use a special set of variables in a template such as the $smarty.foreach.theForeach.first variable that represents the current index of a template instance.
  • Ajax templates ‚Äì You can include template files automatically with AJAX by using {include file='include.tpl'}
  • OnChange: Auto Update Modifier ‚Äì By using the auto_update modifier, it will update the corresponding template block if any of the variables have changed. For example: {$title|default:"No title, so expect the rest to be blank as well..."|capitalize|auto_update}. or specify a selector to update: {$title|capitalize|auto_update:"title_span"}
  • OnChange: Event – You can hook into variable onchange events and add your custom handler – for instance if the $title variable updates, you can have a onchange handler to update document.title with the new value if the title variable changes.

Here is some sample usage demonstrating assigning, rendering, and onchange events.

{foreach from=$tuples key=theKey item=theItem name=theForeach}
	{cycle values='odd,even' assign='class'}
	{if $smarty.foreach.theForeach.first}
		 {assign var='class' value=$class|cat:' first'}
	{elseif $smarty.foreach.theForeach.last}
		{assign var='class' value=$class|cat:' last'}
	{/if}
	<p class="{$class}">{$smarty.foreach.theForeach.index}|{$smarty.foreach.theForeach.iteration} : {$theItem.custid}, {$theItem.name}, {$theItem.address}</p> 
{/foreach}

$(function() {

var data = {
“custids”: [1001,1002,1003],
“names”: [‘John Smith’,‘Jack Jones’,‘Jane Munson’],
“addresses”: [‘253 Abbey road’, ‘417 Mulberry ln’, ‘5605 apple st’],
//
“now”: “now”,
“date”: ‘%I:%M %p’,
“time”: ‘%H:%M:%S’,
“yesterday”: “-1 day”,
//
“tuples”: {
“1001”: {
“custid”: 1001,
“name”: ‘John Smith’,
“address”: ‘253 Abbey road’
},
“1002”: {
“custid”: 1002,
“name”: ‘Jack Jones’,
“address”: ‘417 Mulberry ln’
},
“1003”: {
“custid”: 1003,
“name”: ‘Jane Munson’,
“address”: ‘5605 apple st’
}
}
}
// console.log(‘before assign’);
$.Smarty.assign(data);
// console.log(‘after assign’);

// Copy templates over
$(‘.example’).each(function(){
var $this = $(this);
$this.find(‘.details .result .content.smarty’).html(
$this.find(‘.details .code .content’).html()
);
});

// Populate
$(‘.smarty’).populate();

// Add onchange hanlders
$.Smarty.onchange(‘title’, function(old_value, new_value){
$(‘html > head > title’).html(document.title = new_value);
alert(‘Title has changed from [’+old_value+’] to [‘new_value’]’);
});
});

Adding Templating Support to jQuery Core

This section contains a proposal for adding a standard method for declaring and rendering templates to jQuery core. The section contains a description of the templating API, code samples, and discussion points.

The API

jQuery.fn.render

Generates DOM elements by applying a template to a single data item or array of data items.

jQuery("#template")
   .render(arrayOrObject, options)
   .appendTo("selector");

Parameters

Parameter Type Description
data any data object. If an array, the template is instantiated once for each item. Otherwise, the template is instantiated only once.
options Object An object literal representing optional settings. You can set a rendering and rendered event handler. You also can pass any value and the value will be passed to the template.

Example

Here is a simple example of using the render() method. In the following code, the render() method is used to display a list of product names and prices in a bulleted list.


<script type="text/javascript">

jQuery(function(){
  var products = [
        { name: "Product 1", price: 12.99},
        { name: "Product 2", price: 9.99},
        { name: "Product 3", price: 35.59}
  ];

  $("#template")
     .render(products) 
     .appendTo("ul");
});
</script>

<script id="template" type="text/html">
	<li>{%= name %} - {%= price %}</li>
</script>


<ul></ul>

Executing the code above results in the following bulleted list:

  • Product 1 – 12.99
  • Product 2 – 9.99
  • Product 3 – 35.59

jQuery DOM manipulation

jQuery DOM manipulation methods support specifying a template. For example, you can use jQuery.fn.append:

jQuery("selector")
   .append("#template", arrayOrObject, options);

Parameters

Parameter Type Description
selector string. A string that represents a template selector.
data any data object. If an array, the template is instantiated once for each item. Otherwise, the template is instantiated only once.
options Object An object literal representing optional settings. You can set a rendering and rendered event handler. You also can pass any value and the value will be passed to the template.

Example

The following code uses the append() instead of the render() method to display a a list of product names and prices in a bulleted list.

<script type="text/javascript">

jQuery(function(){
  var products = [
        { name: "Product 1", price: 12.99},
        { name: "Product 2", price: 9.99},
        { name: "Product 3", price: 35.59}
  ];

  $("ul").append("#template", products);
});
</script>

<script id="template" type="text/html">
	<li>{%= name %} - {%= price %}</li>
</script>


<ul></ul>

jQuery.templates

You can assign one or more compiled templates to the jQuery.templates settings object. This is useful when you want to provide a template with a semantic name so that you can easily use the same template multiple times in a document.

You compile a template with the jQuery.tmpl function.

Example

<script type="text/javascript">
    // Assign compiled template
    jQuery.templates.foo = jQuery.tmpl("<li>{%=name%}</li>");
    
    // use name foo as template in append() method:
    jQuery("#container").append("foo", products);
    
</script>

jQuery.tmplFn

Within a template instance, you can use the two built-in functions text() and html() to render a data item. You can extend the set of functions available within a template instance by assigning new functions to the jQuery.tmpFn object.

Example

The following code sample illustrates how you can create a custom even() function. The even() function returns true for alternating instances of a template. In the following sample, the even() function is used within the template to display alternating rows in bold.

<script type="text/javascript">

    $(function() {

        var products = [
            { name: "Product 1", price: 12.99 },
            { name: "Product 2", price: 9.99 },
            { name: "Product 3", price: 35.59 }
        ];

        $.tmplFn.even = function() {
            var context = jQuery._.context;
            return (context.index % 2 === 0);
        };

        $("div").append("#template", products);

    });
</script>

<script id="template" type="text/html">
  <div>
	
  {% if (even()) { %}
     <b> {%= name %} </b>
  {% } else { %}
     {%= name %} 
  {% }; %} 	
	
  </div>
</script>

<div>
</div>

Template Syntax

Inline expressions

Expressions can be inserted using the {%= ... %} syntax. This delimiter minimizes the chance of needing to escape the markup, while avoid collisions with existing server-side and client-side markup extensions (for example, <%= %> would conflict with ASP and ASP.NET).

Examples

Simple injection of data:

<script type="text/html" id="tmp1">
    <li>{%= last %}, {%= first %}</li>
</script>

The expression is JavaScript, so you may call any JavaScript function available or use more complex expressions. Note, however, that keeping the template as simple as possible is preferred, and the two callbacks described later help enable that.

<script type="text/html" id="tmp1">
    <li>{%= last + " " + first %}</li>
</script>

HTML Injection:

By default, data items are not HTML encoded when they are rendered with a template. If you are displaying user submitted data in a template then a malicious user could perform a cross-site scripting (XSS) attack.

Notice the name of the first product in the following code. The first product contains an onclick handler that does something evil. When this data item is displayed, and someone clicks the product name, the JavaScript is executed.

<script type="text/javascript">

jQuery(function(){
  var products = [
        { name: "<a onclick='alert(\"do evil\")'>click here</a>", price: 12.99},
        { name: "Product 2", price: 9.99},
        { name: "Product 3", price: 35.59}
  ];

  $("ul").append("#template", products);
});
</script>

<script id="template" type="text/html">
	<li>{%= name %} - {%= price %}</li>
</script>

<ul></ul>

In order to make it easier to HTML encode the data that you display in a template, and enable you to avoid these types of XSS attacks, there is a built-in function named text() that is available within a template instance. The text() function converts a data item into a text node. Here’s how you would use the text() function.

<script type="text/javascript">

jQuery(function(){
  var products = [
        { name: "<a onclick='alert(\"do evil\")'>click here</a>", price: 12.99},
        { name: "Product 2", price: 9.99},
        { name: "Product 3", price: 35.59}
  ];

  $("ul").append("#template", products);
});
</script>

<script id="template" type="text/html">
	<li>{% text(name) %} - {%= price %}</li>
</script>

<ul></ul>

Code Blocks

In addition to expressions, you can insert code within the template to perform custom logic, conditionals, or loops. Code blocks are delimited by the {% ... %} syntax (without the “=”).

Example

This example displays a list of the names of a products, and a list of their available ‘specials’, if any.

<script type="text/html" id="tmp1">
<li>
    {%= name %}
    {% if (specials.length) { %}
    <ul>
    {% for (var i = 0, l = specials.length; i < l; i++) { %}
        <li>{%= specials[i].details %}</li>
    {% } %}
    </ul>
    {% } %}
</li>
</script>

Context

You can take advantage of a special variable named $context within templates. This is the same object as described later in this proposal in the sections on the rendering and rendered callbacks. The $context variable has the following properties:

Property Name Description
data The array or object parameter passed to the render() or append() function.
dataItem The current dataItem from the data parameter passed to template(). If that was an array, this is each item as the template is rendered for each item. Otherwise, it is the same object.

However, this is also put into a with() block, making the fields of each data item available directly. For example, if the data is [{name:"dave"},{name:"bob"}] then {%= name } may be used. Note however that it is invalid to refer to a field that may not exist. With the data [{name:"dave"},{firstname:"bob"}], the expression ‘name’ will be invalid for the 2nd entry.

index The index of the current data item in the array given to template(). If an object was given, the index is 0.
options The option parameter passed to template(). This provides a way of having extra parameters passed to the template.

Modifications that you make to the $context variable will persist in the template. For example, you can add calculated fields to the $context variable within the rendering() function:

function rendering(context) {
  context.tax = context.dataItem.price * 0.23;
}

You can use the calculated tax field within a template, keeping the template simpler and without inline expressions that may need debugging.

<script type="text/html" id="tmp1">

  The product with tax costs {%= $context.tax %}

</script>

You can use the $context.options variable to refer to any options passed to the render() or append() function. The following sample illustrates how you can display either the normal price or the sale price in a template depending on the value of a showSalePrice parameter passed to the append() function:


<script type="text/javascript">

    jQuery(function() {
        var products = [
            { name: "Product 1", price: 12.99, salePrice: 10.99 },
            { name: "Product 2", price: 9.99, salePrice: 7.99 },
            { name: "Product 3", price: 35.59, salePrice: 33.59 }
        ];

        $("ul").append("#template", products, { showSalePrice: true });
        
    });
</script>

<script id="template" type="text/html">
	<li>
	    {%= name %} - 
	    {%= $context.options.showSalePrice ? salePrice : price %}
	</li>
</script>


<ul></ul>

Notice how the $context.options.showSalePrice property is used in the template to display either the normal price or the sale price.

Rendering and Rendered Template Callbacks

You can take advantage of the options parameter that can be passed to either the render() or append() method to specify template callback functions. There are two callback functions:

  • rendering – The rendering function is called immediately before each template is rendered. It takes as a parameter the same $context object available within the template itself. Returning false from this callback prevents rendering of the item.
  • rendered – The rendered function is called immediately after each template is rendered, but before the resulting DOM element has been added to the document. It also takes as a parameter the same $context object available within the template itself.

Several scenarios for using the rendering and rendered callbacks are discussed in the following sections.

Performing Complex Calculations

Imagine that you want to calculate the tax for each product that you display in a template and you don’t want the application logic for calculating the tax to appear in the template itself. In that case, you can perform the tax calculation within the rendering function like this:


<script type="text/javascript">

    jQuery(function() {

        var products = [
          { name: "Product 1", price: 12.99 },
          { name: "Product 2", price: 9.99 },
          { name: "Product 3", price: 35.59 }
        ];

        $("ul").append("#template", products, { rendering: rendering });


        function rendering(context) {
            var item = context.dataItem;

            // setup additional information to be used more clearly within the template
            // (avoids complex expressions)
            item.tax = Math.floor(item.price * 8.75) / 100;
        }

    });

</script>

<script id="template" type="text/html">
	<li>{%= name %} - price with tax {%= price + tax %} </li>
</script>

<ul></ul>

Canceling Template Rendering

You also can take advantage of the rendering callback to cancel the rendering of a particular template instance. The following code sample illustrates how you can render only those produces that are not marked as deleted.


<script type="text/javascript">

    jQuery(function() {
        var products = [
            { name: "Product 1", price: 12.99 },
            { name: "Product 2", price: 9.99, deleted: true },
            { name: "Product 3", price: 35.59 }
        ];

        $("ul").append("#template", products, { rendering: rendering });

        function rendering(context) {
            if (context.dataItem.deleted) {
                return false;
            }
        }


    });
    
   
</script>

<script id="template" type="text/html">
	<li>{%= name %} - {%= price %}</li>
</script>


<ul></ul>

When the rendering function returns the value false, the template is not rendered. In the code sample above, the template is not rendered when a product has been marked as deleted.

Nested Templates

By taking advantage of the rendered callback, you can created nested templates. For example, the following code illustrates how you can display a list of contacts. Each contact has one or more phone numbers. The contactTemplate is used to display the list of contacts. The phoneTemplate is used to display each phone number.

<script type="text/javascript">

    $(function() {
    
        var contacts = [
            { name: "Dave Reed", phones: ["209-989-8888", "209-800-9090"] },
            { name: "Stephen Walther", phones: ["206-999-8888"] },
            { name: "Boris Moore", phones: ["415-999-2545"] }
        ];

        $("div").append("#contactTemplate", contacts, {rendered:rendered});
    });

    function rendered(context, dom) {
        $("div.phones", dom)
            .append("#phoneTemplate", context.dataItem.phones);
    }
    
</script>

<script id="contactTemplate" type="text/html">
	<div>
	    {%= name %}
	    <div class="phones"></div>
	</div>
</script>

<script id="phoneTemplate" type="text/html">
	<div>
	    Phone: {%= $context.dataItem %}
	</div>
</script>

<div></div>

Creating Plug-ins

Imagine that you want to use a plug-in in a template — such as the jQuery UI Datepicker. Imagine, futhermore, that you want the plug-in to display the value of a data item. For example, you want the DatePicker to default to displaying a date property of the data item. In that case, you need the rendered() callback to create the plug-in and initialize the plug-in with the value of the data item property.

For example, the following code displays a list of contacts. The rendered() callback is used to create a jQuery UI Datepicker and associate the Datepicker with an input element in the template. The DatePicker defaults to the contact’s birthdate.

<script type="text/javascript">

    $(function() {

        var contacts = [
            { name: "Dave Reed", birthdate: new Date("12/25/1980") },
            { name: "Stephen Walther", birthdate: new Date("12/25/1977") },
            { name: "Boris Moore", birthdate: new Date("12/25/1934") },
            { name: "Eilon Lipton", birthdate: new Date("12/25/2004") },
            { name: "Assad Safiullah", birthdate: new Date("12/25/1954") }
        ];

        $("div").append("#template", contacts, { rendered: rendered });
    });

    function rendered(context, dom) {
        $("input", dom)
            .datepicker({ defaultDate: context.dataItem.birthdate })
    }
    
</script>

<script id="template" type="text/html">
	<div>
	    {%= name %}
	    <br />
	    birthdate: <input />
	</div>
</script>

<div></div>

Expressionless Templates

If you don’t want any expressions such as {%= ... %} to appear in a template then you can use the rendered callback to programmatically assign values to elements in a template instance. For example, the following template displays a list of products. Notice that the template contains only HTML markup and no expressions.

<script type="text/javascript">

    $(function() {

        var products = [
            { name: "Product 1", price: 12.99 },
            { name: "Product 2", price: 9.99 },
            { name: "Product 3", price: 35.59 }
        ];

        $("ul").append("#template", products, { rendered: rendered });

        function rendered(context, dom) {
            $("span.name", dom).html(context.dataItem.name);
            $("span.price", dom).html(context.dataItem.price);
        }

    });
</script>

<script id="template" type="text/html">
	<li><span class="name"></span> - <span class="price"></span></li>
</script>

<ul></ul>

Discussion

These are points raised for discussion in the forums.

HTML Encoding

In the current proposal, data items are not HTML encoded by default. That means that unless you take additional action, documents that use a template to display user submitted date will be open to Cross-Site Scripting (XSS) attacks. In an earlier proposal, we discussed creating a special delimiter for HTML encoding content such as {%! productName %}. In the current proposal, we suggest using the text() template function instead like this {%= text(productName) %}.

Script versus Style Template Container

In the current proposal, we recommend wrapping templates in a <script id="template" type="text/html"></script> element. It has been suggested that a better option would be to use the <style id="template" type="text/html"></style> element instead because a Style element better represents the presentational nature of a template.

The disadvantage of a Style element is that it is not allowed within the Body of a document. If you cannot modify the contents of the Head element — for example, you are working within a Content Management System — then you could not create templates.

Technically, the current proposal is compatible with wrapping templates in either a Script or a Style element. So, you could use Style elements, and alternative values for the type, if you prefer.

Templates should be Real DOM Elements

A couple of people have recommended that templates be represented with real DOM elements similar to the way that templates are created in the ASP.NET Ajax Library. In other words, use standard DOM elements for a template and hide the template with display:none.

The main disadvantage of using real DOM elements for templates is that using real DOM elements results in bad side effects. For example, consider the following template:


<div id="template" style="display:none">
  <img src="{%= imageUrl %}" />
  <br /> {% imageTitle %}
</div>

In this case, a browser will attempt to load an image located at {%= imageUrl } which is not what you want to happen. There are many other examples like this in which an expression will result in unintended browser side effects or invalid HTML. For example, an <input> within a template may actually post a value if it is within a form. Another good example of this is as in <div id="{= foo %}"/>, where the id attribute contains invalid characters per the standard.

Revision History

  • 2/27/2010 — Initial proposal published
  • 3/02/2010 – Proposal updated in response to community feedback.
    • Renamed renderTemplate() to template()
    • Changed delimiters from {{ ... }} to [[ ... ]].
    • Added new [[! ... ]] delimiters for displaying unescaped HTML.
    • Removed context variables and methods such as $id(), $index, and writeHtml(), write()
    • Added rendering() function
    • Added templateSetup() method
  • 3/09/2010 – Proposal updated in response to John Resig prototype and community feedback.
    • Renamed template() to render()
    • Added DOM manipulation (e.g. append()) section
    • Changed delimiters to {%= ... %} for expressions and {% ... %} for code.
    • Removed templateSetup() method
  • 3/16/2010 – Added the Comments section below — Steven Black

Comments

  • (3/16/2010) Wondering if “$.fn.render” is best for this. Concerned about opportunities for namespace conflicts in using this generic name in view of the vast depth and breadth of possible implementations, of which the rendering is but one step, typically the final one.
  • (3/16/2010) Agreed; “render” is not clear, particularly given the "$(subject).verb(‘#object’, object) order of the proposed implementation. It is more natural to interpret that as rendering the subject, where in reality the proposal would be rendering the objects. I also find the “rendering” and “rendered” callbacks to be unhelpfully named. May i suggest $.fn.fill, as a verb with a better grammatical fit. It is also shorter, less jargon-y and aptly describes what is happening (the subject is being filled with the object(s)). Likewise, “onFill” and “postFill” would be shorter, more explicit and less intimidating than “rendering” and “rendered”. —Nathan Bubna
  • (3/16/2010) Fixed a typo using old style delimiters
  • (3/17/2010) Having an issue with the chosed template syntax, django templating uses {% .. %} which could cause issues or the need to extra escaping.
  • (7/7/2010) Yes, both {{ … }} and {% … } are Django template syntax, and hence wouldn’t even make it to the browser w/out escaping. Suggest that either, a) there must be a list of valid template delimiters (user chooses between <, {%, etc.. ), or we use something SGML-like, eg: <jQt>...</jQt>. EDIT: OR we look more at the approach taken by PURE, which doesn’t introduce any new syntax at all.
  • (8/20/2010) If using the PURE-style templates (no new syntax), we might consider using data-template-element-name or some other similar attribute on the nodes within the template instead of classes. Also, regardless of template language style, we might consider looking for all elements with a data-template-name attribute and storing those in jQuery.templates automatically.
  • (9/8/2010) Please note: This proposal, presented in February 2010, does not correspond to the actual design of the current implementation at http://github.com/nje/jquery-tmpl. More relevant (though not very complete) documentation can be found here: http://wiki.github.com/nje/jquery-tmpl.
    We are working on complete documentation on a separate site, which should soon become available publically.
    — Boris Moore