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.

31 Comments

  • The code look great!

  • "the engine only accepts expressions for the full attribute and within text nodes"

    Why is that?

    "It would be interesting to hear your comments on that, by the way..."

    In this particular case, how about:

  • A possible solution to the comment block issue:

    I agree that using current solution is too verbose. Maybe we can use . As asp.net developers, we are use to using or , I think by adding an additional tag like the one I suggested from before would be a good idea.

    Just an idea!!

  • A server control might be a good way to handle the syntax complaints.

    The control could have declarative syntax similar to a Repeater, ListView, or DataList, but render as a proper template for the client-side DataView. It could accept a DataSource parameter on the server side, automatically set up something like a page method to serve that data as JSON, then (optionally) emit the corresponding set_data command on DataBind. Something like that might go a long way toward lowering the barrier to entry, while still encouraging the more efficient client-side rendering.

    Maybe even enhance the ListView to provide that functionality, depending on an EnableClientRendering flag. Now, that would be a great feature!

  • @gt1329a: yes, we are working on server integration of those features. Thanks for the feedback, this is very helpful.

  • @Todd: Sure, that's something we considered. Actually, quite a few template engines out there are using script tags with different types than text/javascript, which are also ignored by browsers and have pretty much the same effect as what you describe. The thing is, we don't want the markup to be ignored. Not by the browser (because we're leveraging the browser's HTML parser to our advantage), and not by HTML editors (because we want designers to be able to work on the template almost like they would on regular HTML code). Finally, people interested in validating their documents might appreciate if half the markup doesn't hide in comments.

  • It works great. Somehow, I cannot help thinking this now resembles very much the old asp way, where we interwine code and script together.

    Is it so?

    Can you kindly reply, sir?

  • @Xun: sure, and you can do that in ASP.NET too. If you don't like that, we have declarative syntax for controls and behaviors too. I'll show that in the next post.

  • thanks for the reply. Great

    Something off the topic: I happened to read somewhere there is even a "Annual Evil Empire Planning Meeting" from a tech blog, wondering if this is the same evil empire as yours.

    Another thought, since Google and Microsoft are archnemesis, is the "Evil" thing some sort of counterjoke against Googles' "no evil"?

    Sorry for the baseless speculation and tech irrelevance.

  • @Xun: I don't know about the EE planning meeting. But then again I'm only a henchman. Let me point out that we've been evil way before Google even existed. But yes, it has to do with that, but also with the fact that many people assume that whatever we do is always with some evil master plan in mind.

  • This looks great! I've been looking for a good JavaScript templating system. What would be REALLY cool is if I could use the same templating system on the server as a view engine in ASP.NET MVC. Where the server will replace the tokens. I'd still want the JS templating system. I'd just like to be able to parse templates on the server if I could.

  • How about:

    { if some-expression }
    ...
    { /if }

    or

    {{ if some-expression }}
    ...
    {{ /if }}

  • @jgd12345: text nodes like those are not allowed anywhere in XHTML so that doesn't work.

  • This is going to be a nice feature. When can we see this ship?

  • @Robbie: the preview is out there (first link in the post) and you may already use that. The actual shipping vehicle is ASP.NET 4.0, and we haven't announced a date for that yet.

  • It is interesting.

  • @Daniel: they want the template as a string, we want it as a DOM fragment. This leads to very different constraints and ideal solutions.

  • @Rasmus: it's interesting but it's quite verbose, not very readable and doesn't allow for multiple lines of code. Thanks for the suggestion.

  • Not sure if I missed it, but having the template embedded with the initial html makes it hard to change templates at runtime? also caching templates in the client would become impossible?

    I think it should be possible to load templates seperatly from the initial html to allow for advanced caching and runtime template change features.

  • @Tester: not at all. You can point the dataView's template property to any element in the DOM (so the template is not necessarily inside the dataview element) so it's easy to create an element, set its innerHTML and point to that.
    Why would caching templates be impossible? It's perfectly possible to load templates separately (although wé're not providing that feature at the moment out of the box) and set it at runtime.

  • >> so it's easy to create an element, set its innerHTML and point to that.

    Not so fast. With the innerHTML approach you could not have "container agnostic" templates such as '{{ bar }}' or '{{ foo }}'. For example the consumer of the former would have to know that the template required a 'ul' container.

    Not to mention innerHTML is borked in everyone's favorite browser for a number of elements (select, plus all the table related elements). So the '' example above would just simple error out in IE even if the developer DID know that the template required a 'table' container.

    Personally I find the requirement that all templates must be defined as valid DOM nodes a little annoying, as I often like to load extraneous templates on demand from the server in order to increase the percieved initial page load speed, in which case I get them back as a string. However I understand the desire to leverage the browser's natural DOM parsing. As an added benefit the context-sensitive DOM node creation approach MS has taken lessens the risk of injection-based attacks (eg xss) that other Templating engines can't easily achieve with a string-based approach.

  • @Crescent: good point, but in the same way that when creating the template inline you would include it in the right outer element for (X)HTML compliance, your string should also include a container. Once you've included it to the DOM using innerHTML, you can get the actual template element using getElementById.

  • @Steve: sure, templating is nothing new in itself. We had client templates in the first alpha of Atlas three years ago, and we've had them on the server pretty much forever. It looks at first like it is a simple problem, just put the pieces in an array with the data, join the array, set innerHTML, done. 30 minutes worth of work.
    Except that if you look closely at the other client template engines, they all have one or several of these problems (of which ours has none):
    * Using string concat to build the DOM leads to easy injection, and encoding often depends on context (for example encoding url is different from encoding a text node or a regular attribute).
    * Storing the template code in a string, a script tag or a similar text node of the DOM makes it undesignable and requires parsing. We leverage the native HTML parser.
    * Using their own expression language means one more thing to learn and one more parser to run. We use JavaScript, which means you already know the language and we can use the browser’s parser and interpreter.
    * Most of those engines do not have an easy mechanism to instantiate controls and behaviors on the generated markup.
    * None of them (that I know of) do live bindings and integrate them to the template markup.

  • @learner: that's a great point. I'm only showing a very simple case here, which is to repeat the whole contents of the dataview. This is the default. If you have a more complex template than that, we do support that also, through a pattern that is similar to what the server-side ListView control exposes (with a place holder in the template which is the place where the item template gets repeated). You can see an example of that in the fourth page of the samples that come with my PDC talk: http://channel9.msdn.com/pdc2008/PC32

  • Hello and sorry for my mistakes. My question is about how to send parameter to the method I call in the "query" statment:

    var peopleIKnowView = $create(Sys.Preview.UI.DataView, {serviceuri: theservicename, query: themethodname(parameters)}, {}, {}, $get("peopleIKnow"));

    all this because I need to paging the dataview... I don't have any clue and is a little bit frustrating...

    regards,

    Gabriela from Argentina

  • @Gabriela: if you have parameters, just do a regular web service call from code and set the data property of the DataView from the callback.

  • Hello again and tks for your answer. Well my new question is about hot to delete all data an re fill the list. I have one list that shows messages. Well, if a user enters a new message, the list must be reloaded. In my case,I call the load data method and the result is that all the registers are added to the list without erase the previous one? What do you think? Should I delete by javascript all the li elements generated in the previous call to the loaddata method and then reload data or there is a method in the template that can help me.

    Sorry again for all mistakes because, I'm still learning how to do this in English.

    Gabriela from Argentina

  • @Gabriela: if you call set_data with the new data, that should wipe out the old rendering and replace it with the new. Please check that your data does not still have the old data.

  • Thanks for the heads up on the src attribute of the img tag :)
    I was looking for a way to display images with ajax 4.0 templates and was wondering why it didn't work in IE7.
    Now, with the sys:src attribute, it does work :)

  • @JE: there is a number of problems with this approach. First, it's not script, it's markup. It contains script but so can ordinary markup (leaving aside the discussion on whether it's right). Second, it's killing all tool support on the markup in the template, and you're also not going to get free parsing from the browser, validation, etc.

  • @mcm: the comments are gone. The example in the post would now read:

    &lt;img sys:src=&quot;{{ 'Images/' + Photo }}&quot;

    &nbsp; &nbsp; sys:if=&quot;Photo&quot;

    &nbsp; &nbsp; alt=&quot;{{ FirstName + ' ' + LastName }}&quot; /&gt;

    http://www.asp.net/ajaxlibrary/HOW%20TO%20Perform%20Conditional%20Rendering%20in%20an%20Ajax%20Template.ashx

Comments have been disabled for this content.