The goal of this blog post is to describe how templates work in the WinJS library. In particular, you learn how to use a template to display both a single item and an array of items. You also learn how to load a template from an external file.
Why use Templates?
Imagine that you want to display a list of products in a page. The following code is bad:
var products = [
{ name: "Tesla", price: 80000 },
{ name: "VW Rabbit", price: 200 },
{ name: "BMW", price: 60000 }
];
var productsHTML = "";
for (var i = 0; i < products.length; i++) {
productsHTML += "<h1>Product Details</h1>"
+ "<div>Product Name: " + products[i].name + "</div>"
+ "<div>Product Price: " + products[i].price + "</div>";
}
document.getElementById("productContainer").innerHTML = productsHTML;
In the code above, an array of products is displayed by creating a for..next loop which loops through each element in the array. A string which represents a list of products is built through concatenation.
The code above is a designer’s nightmare. You cannot modify the appearance of the list of products without modifying the JavaScript code.
A much better approach is to use a template like this:
<div id="productTemplate">
<h1>Product Details</h1>
<div>
Product Name:
<span data-win-bind="innerText:name"></span>
</div>
<div>
Product Price:
<span data-win-bind="innerText:price"></span>
</div>
</div>
A template is simply a fragment of HTML that contains placeholders. Instead of displaying a list of products by concatenating together a string, you can render a template for each product.
Creating a Simple Template
Let’s start by using a template to render a single product. The following HTML page contains a template and a placeholder for rendering the template:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Application1</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<!-- Application1 references -->
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
</head>
<body>
<!-- Product Template -->
<div id="productTemplate">
<h1>Product Details</h1>
<div>
Product Name:
<span data-win-bind="innerText:name"></span>
</div>
<div>
Product Price:
<span data-win-bind="innerText:price"></span>
</div>
</div>
<!-- Place where Product Template is Rendered -->
<div id="productContainer"></div>
</body>
</html>
In the page above, the template is defined in a DIV element with the id productTemplate. The contents of the productTemplate are not displayed when the page is opened in the browser. The contents of a template are automatically hidden when you convert the productTemplate into a template in your JavaScript code.
Notice that the template uses data-win-bind attributes to display the product name and price properties. You can use both data-win-bind and data-win-bindsource attributes within a template. To learn more about these attributes, see my earlier blog post on WinJS data binding:
http://stephenwalther.com/blog/archive/2012/02/26/windows-web-applications-declarative-data-binding.aspx
The page above also includes a DIV element named productContainer. The rendered template is added to this element.
Here’s the code for the default.js script which creates and renders the template:
(function () {
"use strict";
var app = WinJS.Application;
app.onactivated = function (eventObject) {
if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
var product = {
name: "Tesla",
price: 80000
};
var productTemplate = new WinJS.Binding.Template(document.getElementById("productTemplate"));
productTemplate.render(product, document.getElementById("productContainer"));
}
};
app.start();
})();
In the code above, a single product object is created with the following line of code:
var product = {
name: "Tesla",
price: 80000
};
Next, the productTemplate element from the page is converted into an actual WinJS template with the following line of code:
var productTemplate = new WinJS.Binding.Template(document.getElementById("productTemplate"));
The template is rendered to the templateContainer element with the following line of code:
productTemplate.render(product, document.getElementById("productContainer"));
The result of this work is that the product details are displayed:
Notice that you do not need to call WinJS.Binding.processAll(). The Template render() method takes care of the binding for you.
Displaying an Array in a Template
If you want to display an array of products using a template then you simply need to create a for..next loop and iterate through the array calling the Template render() method for each element.
(function () {
"use strict";
var app = WinJS.Application;
app.onactivated = function (eventObject) {
if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
var products = [
{ name: "Tesla", price: 80000 },
{ name: "VW Rabbit", price: 200 },
{ name: "BMW", price: 60000 }
];
var productTemplate = new WinJS.Binding.Template(document.getElementById("productTemplate"));
var productContainer = document.getElementById("productContainer");
var i, product;
for (i = 0; i < products.length; i++) {
product = products[i];
productTemplate.render(product, productContainer);
}
}
};
app.start();
})();
After each product in the array is rendered with the template, the result is appended to the productContainer element.
No changes need to be made to the HTML page discussed in the previous section to display an array of products instead of a single product. The same product template can be used in both scenarios.
Rendering an HTML TABLE with a Template
When using the WinJS library, you create a template by creating an HTML element in your page. One drawback to this approach of creating templates is that your templates are part of your HTML page. In order for your HTML page to validate, the HTML within your templates must also validate.
This means, for example, that you cannot enclose a single HTML table row within a template. The following HTML is invalid because you cannot place a TR element directly within the body of an HTML document:
<!-- Product Template -->
<tr>
<td data-win-bind="innerText:name"></td>
<td data-win-bind="innerText:price"></td>
</tr>
This template won’t validate because, in a valid HTML5 document, a TR element must appear within a THEAD or TBODY element. Instead, you must create the entire TABLE element in the template. The following HTML page illustrates how you can create a template which contains a TR element:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Application1</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<!-- Application1 references -->
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
</head>
<body>
<!-- Product Template -->
<div id="productTemplate">
<table>
<tbody>
<tr>
<td data-win-bind="innerText:name"></td>
<td data-win-bind="innerText:price"></td>
</tr>
</tbody>
</table>
</div>
<!-- Place where Product Template is Rendered -->
<table>
<thead>
<tr>
<th>Name</th><th>Price</th>
</tr>
</thead>
<tbody id="productContainer">
</tbody>
</table>
</body>
</html>
In the HTML page above, the product template includes TABLE and TBODY elements:
<!-- Product Template -->
<div id="productTemplate">
<table>
<tbody>
<tr>
<td data-win-bind="innerText:name"></td>
<td data-win-bind="innerText:price"></td>
</tr>
</tbody>
</table>
</div>
We discard these elements when we render the template. The only reason that we include the TABLE and THEAD elements in the template is to make the HTML page validate as valid HTML5 markup.
Notice that the productContainer (the target of the template) in the page above is a TBODY element. We want to add the rows rendered by the template to the TBODY element in the page.
The productTemplate is rendered in the default.js file:
(function () {
"use strict";
var app = WinJS.Application;
app.onactivated = function (eventObject) {
if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
var products = [
{ name: "Tesla", price: 80000 },
{ name: "VW Rabbit", price: 200 },
{ name: "BMW", price: 60000 }
];
var productTemplate = new WinJS.Binding.Template(document.getElementById("productTemplate"));
var productContainer = document.getElementById("productContainer");
var i, product, row;
for (i = 0; i < products.length; i++) {
product = products[i];
productTemplate.render(product).then(function (result) {
row = WinJS.Utilities.query("tr", result).get(0);
productContainer.appendChild(row);
});
}
}
};
app.start();
})();
When the product template is rendered, the TR element is extracted from the rendered template by using the WinJS.Utilities.query() method. Next, only the TR element is added to the productContainer:
productTemplate.render(product).then(function (result) {
row = WinJS.Utilities.query("tr", result).get(0);
productContainer.appendChild(row);
});
I discuss the WinJS.Utilities.query() method in depth in a previous blog entry:
http://stephenwalther.com/blog/archive/2012/02/23/windows-web-applications-query-selectors.aspx
When everything gets rendered, the products are displayed in an HTML table:
You can see the actual HTML rendered by looking at the Visual Studio DOM Explorer window:
Loading an External Template
Instead of embedding a template in an HTML page, you can place your template in an external HTML file. It makes sense to create a template in an external file when you need to use the same template in multiple pages. For example, you might need to use the same product template in multiple pages in your application.
The following HTML page does not contain a template. It only contains a container that will act as a target for the rendered template:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Application1</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<!-- Application1 references -->
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
</head>
<body>
<!-- Place where Product Template is Rendered -->
<div id="productContainer"></div>
</body>
</html>
The template is contained in a separate file located at the path /templates/productTemplate.html:
Here’s the contents of the productTemplate.html file:
<!-- Product Template -->
<div id="productTemplate">
<h1>Product Details</h1>
<div>
Product Name:
<span data-win-bind="innerText:name"></span>
</div>
<div>
Product Price:
<span data-win-bind="innerText:price"></span>
</div>
</div>
Notice that the template file only contains the template and not the standard opening and closing HTML elements. It is an HTML fragment.
If you prefer, you can include all of the standard opening and closing HTML elements in your external template – these elements get stripped away automatically:
<html>
<head><title>product template</title></head>
<body>
<!-- Product Template -->
<div id="productTemplate">
<h1>Product Details</h1>
<div>
Product Name:
<span data-win-bind="innerText:name"></span>
</div>
<div>
Product Price:
<span data-win-bind="innerText:price"></span>
</div>
</div>
</body>
</html>
Either approach – using a fragment or using a full HTML document — works fine.
Finally, the following default.js file loads the external template, renders the template for each product, and appends the result to the product container:
(function () {
"use strict";
var app = WinJS.Application;
app.onactivated = function (eventObject) {
if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
var products = [
{ name: "Tesla", price: 80000 },
{ name: "VW Rabbit", price: 200 },
{ name: "BMW", price: 60000 }
];
var productTemplate = new WinJS.Binding.Template(null, { href: "/templates/productTemplate.html" });
var productContainer = document.getElementById("productContainer");
var i, product, row;
for (i = 0; i < products.length; i++) {
product = products[i];
productTemplate.render(product, productContainer);
}
}
};
app.start();
})();
The path to the external template is passed to the constructor for the Template class as one of the options:
var productTemplate = new WinJS.Binding.Template(null, {href:"/templates/productTemplate.html"});
When a template is contained in a page then you use the first parameter of the WinJS.Binding.Template constructor to represent the template – instead of null, you pass the element which contains the template. When a template is located in an external file, you pass the href for the file as part of the second parameter for the WinJS.Binding.Template constructor.
Summary
The goal of this blog entry was to describe how you can use WinJS templates to render either a single item or an array of items to a page. We also explored two advanced topics. You learned how to render an HTML table by extracting the TR element from a template. You also learned how to place a template in an external file.