Using client templates, part 1
Last week, we shipped the first preview for the Ajax work we're doing in ASP.NET 4.0 under the simple form of a simple script file (release and debug versions). This should show how much emphasis this release puts on the client-side. As a matter of facts, I'll use a plain HTML file here instead of an ASPX file to make it perfectly clear that everything here (except for the web service that provides the data) runs on the client.
One of the scenarios we're trying to improve is updating parts of the page with new data without a postback (in other words, Ajax). That's a scenario you could implement in a number of manners. First, you could put an UpdatePanel control around your server rendering of the data and be done with it. That works, but is a little more chatty than you'd wish. It's all the chattier if you're dealing with a relatively large page with a lot of ViewState (in which case your small partial update pays a tax for the rest of the page that won't get updated). Even without ViewState, HTML isn't necessarily the most compact representation of your data.
It's usually more efficient, at least in terms of network traffic, to transmit pure JSON data and do the rendering on the client. Now doing that without a proper template engine is clearly not the best use of your time. You could do it either with DOM APIs and a hard to maintain mess of document.createElement and appendChild calls, or you could concatenate strings. The latter solution isn't much easier to maintain (how do you explain your HTML designer how to modify that stuff?) and is quite dangerous, in the same way that building a SQL query using string concatenation is. Nobody in their right mind does that anymore, right? I mean, ...right?
A good template engine should be highly readable, as close to designable HTML as possible (bonus points if it *is* a version of HTML that can be validated), it must have a good expression language and should perform well.
Our engine is using markup that can be (but doesn't *have* to be) valid XHTML, it uses JavaScript as the expression language (which means you don't have to learn yet another language), and we're quite pleased with how it performs. Let's have a look.
The page we're building here is a very simple example of getting some data from a web service (names and photos) and rendering that on the client. Here's what the template looks like:
<div id="peopleIKnow" class="sys-template"> <fieldset> <legend>{{ FirstName }} {{ LastName }}</legend> <!--* if (Photo) { *--> <img sys:src="{{ 'Images/' + Photo }}"
alt="{{ FirstName + ' ' + LastName }}" /> <!--* } *--> </fieldset> </div>
And here's what it renders like:
A few things to notice here.
The syntax to insert data, be it into text nodes or attribute values, is {{ expression }}, where expression is any valid JavaScript expression. The most common case will be that you want to inject a column of the current data item. For that case, you won't have to use anything like the server-side's Eval("columnName") or anything like Container.DataItem.columnName or $T.row.columnName. You just use the column name and that's it: {{ FirstName }}. This will just inject the value of the FirstName column into the markup. And of course when you need something more complex, like a combination of columns or some additional formatting, you have all the power of JavaScript and the Ajax library at your disposal.
The alt attribute of the image is one example of that: here, we're combining the first and last name. You could be tempted here to do something like alt="{{ FirstName }} {{ LastName }}". This won't work as the engine only accepts expressions for the full attribute and within text nodes (in the same way that on the server-side, <%# %> blocks have to be the full attribute).
The src attribute is one of those attributes that you have to prefix with the system namespace sys if you want to bind them (together with id, checked and a few others). There are several reasons for that. First, the template is part of the initial markup of the page. If you used src as usual here, the browser would try to download an image named "{{ 'Images/' + Photo }}", which will of course result in an unnecessary network request, a 404 and some junk in your server logs. Second, an Internet Explorer bug prevents us from reading the binding expression from the DOM (the browser won't give you the actual value that you set, but will always give you what it thinks is the right value, in this case the server-resolved url, which is useless to us). Finally, it can help achieving XHTML compliance (for which, I know, there is a small violation in this template in that the imag tag is missing an actual src attribute, which you can fix -if you have to- by including a src="about:blank" attribute). Some attributes don't accept just any value in XHTML. To summarize, if binding an attribute directly doesn't work, chances are prefixing it with "sys:" just will.
Last thing is this weird comment block, which we're using to mix JavaScript code into the markup. More precisely, we're doing conditional rendering of the image depending on the existence of the Photo field. There has been much debate over the use of a special comment-based syntax for this. We don't especially like using comments here as we agree that you should ideally be able to remove comments from markup without any impact. We decided to use them anyway because there wasn't a better solution that was still compatible with our XHTML constraint. We did consider admitting both <!--* *--> blocks and something like <script type="application/ms-template"> but that just seemed too verbose and it's rarely a good thing to have two solutions for exactly the same problem. It would be interesting to hear your comments on that, by the way...
So how do you transform this template into markup? Depends whether you're a component developer, in which case you're going to use the template APIs directly from your component, or if you're an application developer. I'll focus on the latter in this post.
To render the template with data, you simply instantiate a DataView control and feed it data:
var peopleIKnowView = $create(Sys.Preview.UI.DataView, {}, {}, {},
$get("peopleIKnow")); PeopleIKnow.GetPeople(function(results) { peopleIKnowView.set_data(results); });
In this code, we're using $create to create and initialize an instance of DataView on the "peopleIKnow" div tag, then we invoke the web service, and set the data property on the Dataview control from the callback function of the web service call. That's it.
Next time, I'll get into more details into what really happens when you instantiate a template, and I'll give some debugging tips.
The full code for this can be downloaded here: http://weblogs.asp.net/bleroy/attachment/6461783.ashx. Included script files are subject to the licenses for the Ajax Library and ASP.NET Ajax 4.0 CodePlex Preview 1.
Part 2 can be found here: http://weblogs.asp.net/bleroy/archive/2008/09/02/using-client-templates-part-2-live-bindings.aspx.