May 2012
Volume 27 Number 05
Client Insight - Advanced JsRender Templating Features
By John Papa | May 2012
Templates are powerful, but sometimes you need more than the standard features a templating engine supplies out of the box. You might want to convert data, define a custom helper function or create your own tag. The good news is that you can use the core features of JsRender to do all of this and more.
My April column (msdn.microsoft.com/magazine/hh882454) explored the fundamental features of the JsRender templating library. This column continues the exploration of JsRender in more scenarios such as rendering external templates, changing context with the {{for}} tag and using complex expressions. I’ll also demonstrate how to use some of the more powerful JsRender features, including creating custom tags, converters and context helpers, and allowing custom code. All code samples can be downloaded from msdn.microsoft.com/magazine/msdnmag0512, and JsRender can be downloaded from bit.ly/ywSoNu.
{{for}} Variations
There are several ways the {{for}} tag can be an ideal solution. In my previous column I demonstrated how the {{for}} tag can help iterate through arrays using a block and how it can iterate through multiple objects at once:
<!-- looping {{for}} -->
{{for students}}
{{/for}}
<!-- combo iterators {{for}} -->
{{for teachers students staff}}
{{/for}}
The {{for}} (or any block tag) can be converted from a block tag (with content) to a self-closing tag by replacing the block content with an external template, which you point to declaratively as a tmpl property. The tag will then render the external template in place of inline content.
This makes it easy to adopt a modular approach to templates where you can reuse template markup in different places and organize and compose templates:
<!-- self closing {{for}} -->
{{for lineItems tmpl="#lineItemsDetailTmpl" /}}
Data is rarely flat, which is why diving into and out of object hierarchies is an important feature for templates. I demonstrated the core techniques to dive into an object hierarchy in my previous column using dot notation and square brackets, but you can also use the {{for}} tag to help reduce code. This becomes more apparent when you have an object structure where you’re diving into an object hierarchy and need to render a set of properties from a child object. For example, when rendering a person object’s address, you might write the template in the following way, where the “address” term in the path is repeated several times:
<div>{{:address.street1}}</div>
<div>{{:address.street2}}</div>
<div>{{:address.city}}, {{:address.state}} {{:address.postalCode}}</div>
The {{for}} can make code for rendering an address much simpler by eliminating the need to repeat the address object, as shown here:
<!-- "with" {{for}} -->
{{for address}}
<div>{{:street1}}</div>
<div>{{:street2}}</div>
<div>{{:city}}, {{:state}} {{:postalCode}}</div>
{{/for}}
The {{for}} is operating on the address property, which is a single object with properties, not an array of objects. If the address is truthy (it contains some non-falsey value), the contents of the {{for}} block will be rendered. The {{for}} also changes the current data context from the person object to the address object; thus it acts like a “with” command that many libraries and languages have. So in the preceding example, the {{for}} tag changes the data context to the address and then renders the contents of the templates once (because there’s only one address). If the person doesn’t have an address (the address property is null or undefined), the contents won’t be rendered at all. This makes the {{for}} block great for containing templates that should only be displayed in certain circumstances. This example (from the file 08-for-variations.html in the accompanying code download) demonstrates how the sample uses the {{for}} to display pricing info if it exists:
{{for pricing}}
<div class="text">${{:salePrice}}</div>
{{if fullPrice !== salePrice}}
<div class="text highlightText">PRICED TO SELL!</div>
{{/if}}
{{/for}}
External Templates
Code reuse is one of the big advantages of using templates. If a template is defined inside of a <script> tag in the same page that it’s used, then the template isn’t as reusable as it could be. Templates that should be accessible from multiple pages can be created in their own files and retrieved as needed. JavaScript and jQuery make it easy to retrieve the template from an external file, and JsRender makes it easy to render it.
One convention I like to use with external templates is to prefix the file name with an underscore, which is a common naming convention for partial views. I also prefer to suffix all template files with .tmpl.html. The .tmpl denotes that it’s a template and the .html extension simply makes it easier for development tools such as Visual Studio to recognize that the template contains HTML. Figure 1 shows the rendering of an external template.
Figure 1 Code for Rendering an External Template
my.utils = (function () {
var
formatTemplatePath = function (name) {
return "/templates/_" + name + ".tmpl.html";
},
renderTemplate = function (tmplName, targetSelector, data) {
var file = formatTemplatePath(tmplName);
$.get(file, null, function (template) {
var tmpl = $.templates(template);
var htmlString = tmpl.render(data);
if (targetSelector) {
$(targetSelector).html(htmlString);
}
return htmlString;
});
};
return {
formatTemplatePath: formatTemplatePath,
renderExternalTemplate: renderTemplate
};
})()
One way to retrieve the template from an external file is to write a utility function that the JavaScript in a Web app can call. Notice in Figure 1that the renderExternalTemplate function on the my.utils object first retrieves the template using the $.get function. When the call completes, the JsRender template is created using the $.templates function from the contents of the response. Finally, the template is rendered using the template’s render function and the resulting HTML is displayed in the target. This code could be called using the following code where the template name, the DOM target and the data context are passed to the custom renderExternalTemplates function:
my.utils.renderExternalTemplate("medMovie", "#movieContainer", my.vm);
The external template for this sample is in the _medMovie.tmpl.html sample file and contains just the HTML and JsRender tags. It isn’t wrapped with a <script> tag. I prefer this technique for external templates because the development environment will recognize that the contents are HTML, which makes writing the code less error prone because IntelliSense works out of the box. However, the file could contain multiple templates, with each template being wrapped in a <script> tag and given an id to uniquely identify it. This is just another way to handle external templates. The final result is shown in Figure 2.
Figure 2 The Result of Rendering an External Template
View Paths
JsRender provides several special view paths that make it easy to access the current view object. #view provides access to the current view, #data provides access to the current data context for the view, #parent walks up the object hierarchy and #index returns an index property:
<div>{{:#data.section}}</div>
<div>{{:#parent.parent.data.number}}</div>
<div>{{:#parent.parent.parent.parent.data.name}}</div>
<div>{{:#view.data.section}}</div>
When using the view paths (other than #view), they’re operating on the current view already. In other words, the following are equivalent:
#data
#view.data
The view paths are helpful when navigating object hierarchies such as customers with orders with order details, or movies in warehouses in storage locations (as shown in the code download sample file 11-view-paths.html).
Expressions
Common expressions are an essential part of logic and can be useful when deciding how to render a template. JsRender provides support for common expressions including (but not limited to) those shown in Figure 3.
Figure 3 Common Expressions in JsRender
Expression | Example | Comments |
+ | {{ :a + b }} | Addition |
- | {{ :a - b }} | Subtraction |
* | {{ :a * b }} | Multiplication |
/ | {{ :a / b }} | Division |
|| | {{ :a || b }} | Logical or |
&& | {{ :a && b }} | Logical and |
! | {{ :!a }} | Negation |
? : | {{ :a === 1 ? b * 2: c * 2 }} | Tertiary expression |
( ) | {{ :(a||-1) + (b||-1) }} | Ordering evaluation using parentheses |
% | {{ :a % b }} | Modulus operation |
<= and >= and < and > | {{ :a <= b }} | Comparison operations |
=== and !== | {{ :a === b }} | Equality and inequality |
JsRender supports expression evaluation but not assignment of the expression, nor the running of random code. This prevents expressions that could otherwise perform variable assignments or perform operations such as opening an alert window. The intention of expressions is to evaluate an expression and either render the result, take action based on the result or use the result in another operation.
For example, performing {{:a++}} with JsRender would result in an error because it attempts to increment the a variable. Also, performing {{:alert(‘hello’)}} results in an error because it tries to call a function, #view.data.alert, which doesn’t exist.
Registering Custom Tags
JsRender offers several powerful extensibility points such as custom tags, converters, helper functions and template parameters. The syntax for calling each of these is shown here:
{{myConverter:name}}
{{myTag name}}
{{:~myHelper(name)}}
{{:~myParameter}}
Each of these serves different purposes; however, they can overlap a bit depending on the situation. Before showing how to choose between them, it’s important to understand what each one does and how to define them.
Custom tags are ideal when something needs to be rendered that has “control-like” features and can be self-contained. For example, star ratings could be rendered simply as a number using data, like this:
{{:rating}}
However, it might be better to use JavaScript logic to render the star ratings using CSS and a series of empty and filled star images:
{{createStars averageRating max=5/}}
The logic for creating the stars could (and should) be separated from the presentation. JsRender provides a way to create a custom tag that wraps this functionality. The code in Figure 4 defines a custom tag named createStars and registers it with JsRender so it can be used in any page that loads this script. Using this custom tag requires that its JavaScript file, jsrender.tag.js in the sample code, is included in the page.
Figure 4 Creating a Custom Tag
$.views.tags({
createStars: function (rating) {
var ratingArray = [], defaultMax = 5;
var max = this.props.max || defaultMax;
for (var i = 1; i <= max; i++) {
ratingArray.push(i <= rating ?
"rating fullStar" : "rating emptyStar");
}
var htmlString = "";
if (this.tmpl) {
// Use the content or the template passed in with the template property.
htmlString = this. renderContent(ratingArray);
} else {
// Use the compiled named template.
htmlString = $.render.compiledRatingTmpl(ratingArray);
}
return htmlString;
}
Custom tags can have declarative properties such as the max=5 property of {{createStars}} shown earlier. They’re accessed in the code through this.props. For example, the following code registers a custom tag named sort that accepts an array (if the property named reverse is set to true, {{sort array reverse=true/}}, the array is returned in reverse order):
$.views.tags({
sort: function(array){
var ret = "";
if (this.props.reverse) {
for (var i = array.length; i; i--) {
ret += this.tmpl.render(array[i - 1]);
}
} else {
ret += this.tmpl.render(array);
}
return ret;
}}
A good rule of thumb is to use a custom tag when you need to render something a bit more involved (like a createStars or sort tag) and it could be reused. Custom tags are less ideal for one-off scenarios.
Converters
While custom tags are ideal for creating content, converters are better suited for the simple task of converting a source value to a different value. Converters can change source values (such as a Boolean value of true or false) to something completely different (such as the color green or red, respectively). For example, the following code will use the priceAlert converter to return a string containing a price alert based on the salePrice value:
<div class="text highlightText">{{priceAlert:salePrice}}</div>
Converters are great for changing URLs too, as shown here:
<img src="{{ensureUrl:boxArt.smallUrl}}" class="rightAlign"/>
In the following sample the ensureUrl converter should convert the boxArt.smallUrl value to a qualified URL (both of these converters are used in the file 12-converters.html and are registered in jsrender.helpers.js using the JsRender $.views.converters function):
$.views.converters({
ensureUrl: function (value) {
return (value ? value : "/images/icon-nocover.png");
},
priceAlert: function (value) {0
return (value < 10 ? "1 Day Special!" : "Sale Price");
}
});
Converters are intended for non-parameterized conversion of data to a rendered value. If the scenario calls for parameters, then a helper function or a custom tag is better suited than a converter. As we’ve seen previously, custom tags allow for named parameters, so the createStars tag could have parameters to define the size of the stars, their colors, CSS classes to apply to them and so on. The key point here is that converters are for simple conversions, while custom tags are for more involved turnkey rendering.
Helper Functions and Template Parameters
You can pass in helper functions or parameters for use during template rendering in a couple of ways. One is to register them using $.views.helpers, in a similar way to registering tags or converters:
$.views.helpers({
todaysPrices: { unitPrice: 23.40 },
extPrice:function(unitPrice, qty){
return unitPrice * qty;
}
});
This will make them available to all templates in the application. Another way is to pass them in as options in the call to render:
$.render.myTemplate( data, {
todaysPrices: { unitPrice: 23.40 },
extPrice:function(unitPrice, qty){
return unitPrice * qty;
}
});
This code makes them available only in the context of that particular template-rendering call. Either way, the helpers can be accessed from within the template by prefixing the parameter or function name (or path) with “~”:
{{: ~extPrice(~todaysPrices.unitPrice, qty) }}
Helper functions can do just about anything, including convert data, perform calculations, run app logic, return arrays or objects, or even return a template.
For example, a helper function named getGuitars could be created to search through an array of products and find all guitar products. It might also accept a parameter for the type of guitar. The result could then be used to render a single value or to iterate through the resulting array (because helper functions can return anything). The following code might get an array of all products that are acoustic guitars and iterate on them using a {{for}} block:
{{for ~getGuitars('acoustic')}} ... {{/for}}
Helper functions can also call other helper functions, such as calculating a total price using an array of an order’s line items and applying discount rates and tax rates:
{{:~totalPrice(~extendedPrice(lineItems, discount), taxRate}}
Helper functions that are accessible to multiple templates are defined by passing an object literal containing the helper functions to the JsRender $.views.helpers function. In the following example, the concat function is defined to concatenate multiple arguments:
$.views.helpers({
concat:function concat() {
return "".concat.apply( "", arguments );
}
})
The concat helper function can be invoked by using {{:~concat(first, age, last)}}. Assuming the values for first, middle and last are accessible and are John, 25 and Doe, respectively, the value John25Doe would be rendered.
Helper Functions for Unique Scenarios
You might run into a situation where you want to use a helper function for a specific template, but not reuse it in other templates. For example, a shopping cart template might require a calculation that’s unique to that template. A helper function could perform the calculation, but there’s no need to make it accessible to all templates. JsRender supports this scenario with the second approach mentioned earlier—passing the function in with the options in a render call:
$.render.shoppingCartTemplate( data, {
todaysPrices: { unitPrice: 23.40 },
extPrice:function(unitPrice, qty){
return unitPrice * qty;
}
});
In this case the shopping cart template is rendered, and the helper functions and template parameters it needs for its calculation are supplied directly with the render call. The key here is that, in this case, the helper function only exists during the rendering of this specific template.
Which to Use?
JsRender offers several options to create powerful templates with converters, custom tags and helper functions, but it’s important to know under which scenario each should be used. A good rule of thumb is to use the decision tree shown in Figure 5, which outlines how to decide which of these features to use.
Figure 5 A Decision Tree to Choose the Right Helper
if (youPlanToReuse) {
if (simpleConversion && !parameters){
// Register a converter.
}
else if (itFeelsLikeAControl && canBeSelfContained){
// Register a custom tag.
}
else{
// Register a helper function.
}
}
else {
// Pass in a helper function with options for a template.
}
If the function is only to be used once, there’s no need to create the overhead of making it accessible throughout the entire application. This is the ideal situation for a “one time” helper function that’s passed in when needed.
Allow Code
Situations might arise where it’s easier to write custom code inside the template. JsRender allows code to be embedded, but I recommend you do this only when all else fails as the code can be difficult to maintain because it mixes presentation and behavior.
Code can be embedded inside a template by wrapping the code with a block prefixed with an asterisk {{* }} and setting allowCode to true. For example, the template named myTmpl (shown in Figure 6) embeds code to evaluate the appropriate places to render a command or the word “and” in a series of languages. The full sample can be found in the file 13-allowcode.html. The logic isn’t that complicated, yet the code can be difficult to read in the template.
JsRender won’t allow the code to be executed unless the allowCode property is set to true (default is false). The following code defines the compiled template named movieTmpl, assigns it the markup from the script tag shown in Figure 6 and indicates that it should allowCode in the template:
$.templates("movieTmpl", {
markup: "#myTmpl",
allowCode: true
});
$("#movieRows").html(
$.render.movieTmpl(my.vm.movies)
);
Once the template is created, it’s then rendered. The allowCode feature can lead to code that’s difficult to read, and in some cases a helper function can do the job. For instance, the example in Figure 6 uses the allowCode feature of JsRender to add commas and the word “and” where needed. However, this could also be done by creating a helper function:
$.views.helpers({
languagesSeparator: function () {
var view = this;
var text = "";
if (view.index === view.parent.data.length - 2) {
text = " and";
} else if (view.index < view.parent.data.length - 2) {
text = ",";
}
return text;
}
})
Figure 6 Allowing Code in a Template
<script id="myTmpl" type="text/x-jsrender">
<tr>
<td>{{:name}}</td>
<td>
{{for languages}}
{{:#data}}{{*
if ( view.index === view.parent.data.length - 2 ) {
}} and {{*
} else if ( view.index < view.parent.data.length - 2 ) {
}}, {{* } }}
{{/for}}
</td>
</tr>
</script>
This languagesSeparator helper function is called by prefixing its name with “~.” This makes the template code that calls the helper much easier to read, as shown here:
{{for languages}}
{{:#data}}{{:~languagesSeparator()}}
{{/for}}
Moving the logic to a helper function removed behavior from the template and moved it into JavaScript, which follows good separation patterns.
Performance and Flexibility
JsRender offer a variety of features that go well beyond rendering property values, including support for complex expressions, iterating and changing context using the {{for}} tag and view paths for navigating context. It also provides the means to extend its features by adding custom tags, converters and helpers as needed. These features and the pure string-based approach to templating help JsRender benefit from great performance and make it very flexible.
John Papa is a former evangelist for Microsoft on the Silverlight and Windows 8 teams, where he hosted the popular “Silverlight TV” show. He has presented globally at keynotes and sessions for the BUILD, MIX, PDC, TechEd, Visual Studio Live! and DevConnections events. Papa is also a Microsoft Regional Director, a columnist for Visual Studio Magazine (Papa’s Perspective) and an author of training videos with Pluralsight. Follow him on Twitter at twitter.com/john_papa.
Thanks to the following technical expert for reviewing this article: Boris Moore