A sneak peak at ASP.NET AJAX 4.0′s client-side templating
AJAX, ASP.NET, JavaScript, UI By Dave Ward on July 23rd, 2008
Hot on the heels of the recent ASP.NET AJAX roadmap, Bertrand and team have released a limited preview of the new AJAX functionality coming in ASP.NET 4.0.
To see how the new functionality stacks up, I decided to recreate my recent jTemplates example, using only ASP.NET AJAX and its new templating features. Eventually, I settled on using the DataView class, which offers more advanced, repeater-like functionality.
Having successfully completed the exercise, I thought it seemed like something that you might find interesting too. The solution boils down to four easy steps:
- Creating a page method to return JSON data.
- Setting up a ScriptManager to coordinate script and page method access.
- Defining the client-side template that will render the JSON data.
- Using JavaScript to render the template, using the page method’s return.
A familiar page method
The first order of business is obtaining a data source to render. To keep things simple, let’s reuse the RSS reader from my jQuery “repeater” example:
[WebMethod] public static IEnumerable GetFeedburnerItems() { XDocument feedXML = XDocument.Load("http://feeds.encosia.com/Encosia"); var feeds = from feed in feedXML.Descendants("item") select new { Date = DateTime.Parse(feed.Element("pubDate").Value) .ToShortDateString(), Title = feed.Element("title").Value, Link = feed.Element("link").Value, Description = feed.Element("description").Value }; return feeds; }
This page method will return a JSON array representing the latest ten items from my RSS feed. Specifically, their Date, Title, Link, and Description.
Setting up the ScriptManager
The ASP.NET AJAX 4.0 templating features depend on functionality in the ASP.NET AJAX client side framework. We could include MicrosoftAjax.js manually, but since we’re also using a page method, the ScriptManager will serve our needs well:
<asp:ScriptManager runat="server" EnablePageMethods="true"> <Scripts> <asp:ScriptReference Path="~/MicrosoftAjaxTemplates.js" /> <asp:ScriptReference Path="~/default.js" /> </Scripts> </asp:ScriptManager>
EnablePageMethods will generate a JavaScript proxy for calling our page method. This is not necessarily the only or most efficient way of calling page methods, but works well enough for our purposes here.
Creating the template
Next, we need to define a template to render the page method’s return upon. The ASP.NET AJAX 4.0 templating engine’s syntax is very straightforward:
<table> <thead> <tr> <th>Date</th> <th>Title</th> <th>Excerpt</th> </tr> </thead> <tbody id="RSSItem" class="sys-template"> <tr> <td>{{ Date }}</td> <td><a href="{{ Link }}">{{ Title }}</a></td> <td>{{ Description }}</td> </tr> </tbody> </table>
The bracketed field names correspond to the names of the properties in the anonymous type generated by the page method. It couldn’t be much easier.
You may have noticed the sys-template CSS class applied to the meat of the template. This is a convention for hiding the template until it has been rendered. The class should be defined somewhere as:
.sys-template { display: none; visibility: hidden; }
During the rendering process, the templating engine will automatically attempt to remove the sys-template class, making the final result visible.
Bringing it all together
Now that we’ve got our JSON data source and have defined our template, the final step is simply to connect those dots.
Sys.Application.add_init(AppInit); // Execute the page method when the page finishes loading. function AppInit(sender, args) { PageMethods.GetFeedburnerItems(OnSuccess, OnFailure); } function OnSuccess(result) { // Create an ASP.NET AJAX 4.0 DataView, targeted at our template. var dv = new Sys.Preview.UI.DataView($get('RSSItem')); // Pass the DataView our JSON result of RSS items to render. dv.set_data(result); // Render the DataView template, using the provided JSON array. dv.render(); } // Do not do this in production code! function OnFailure() { }
The OnSuccess function is where all of the magic happens. When the page method completes its execution, three things happen:
- A DataView object is created for our template, by passing its containing DomElement: $get(‘RSSItem’).
- The JSON result is assigned to the DataView, using its set_data method.
- The DataView’s render method is called to generate the end result.
Conclusion and a couple suggestions.
Comparing this implementation to my identical jTemplates example, I must say that I prefer the DataView solution. The syntax is impressively cleaner.
I just want to reiterate how happy I am with the transparency of the ASP.NET team lately. For those of us who aren’t MVPs or ASPInsiders, it’s nice to have a chance to offer constructive feedback and generally not be left in the dark.
In that spirit, I have two suggestions to improve the templating engine.
External templates. I don’t particularly enjoy mingling my template with the rest of the page’s HTML. This is one thing that jTemplates handles very well, with its createTemplateURL method. I would love to see this functionality in the ASP.NET AJAX templating engine and/or the DataView class.
To one-up jTemplates, it would be fantastic if there were a way to pre-load the template. During the second or two that the AJAX call executes would be an excellent time to get the template ready, so that there’s no HTTP delay after the data is ready.
Table issues. Ideally, there should be an automatic convention for hiding the entire table until the template is rendered. Maybe that’s already possible, but I wasn’t able to find a configuration that would do that while repeating only the tbody.
It would make sense if we could assign the entire table a CSS class of sys-template, which would be removed if any of its children were a template being rendered. I usually avoid special cases like this, but I expect that the table scenario will be a prevalent one. It makes sense to optimize for it.
Try it for yourself
Similar posts
What do you think? Your comments are welcome.
I appreciate all of your comments, questions, and other feedback, but please try to stay on topic. If you have a question unrelated to this post, I recommend posting on the ASP.NET forums instead.
If you're replying to an existing comment, please use the threading feature. To do this, click the "Reply to this" link underneath the comment you're replying to.


Wow you were busy yesterday :-D Looks like a nice step forward for ASP.NET AJAX, I’ll have to look at what else is in this release. If ASP.NET AJAX can find more ways to enhance the typical add/edit/delete grid scenario beyond wrapping the whole thing in an UpdatePanel, they will have a nice advantage over other JS libraries.
Hey thanks for the feedback :)
Two things — you know you can declaratively use the dataview?
Then on the body tag, put xmlns:dv=”javascript:Sys.Preview.UI.DataView”, and xmlns:sys=”javascript:Sys”, and sys:activate=”[id of the table or just the tbody]”
Then your callback only has to set the data of the existing dataview. Also, all you need to do is set the data, you dont need actually call render().
Note that you could run into an error when using tbody as the template root, a known issue.
FYI, when I remove the explicit render() call from the code above, it doesn’t display anything at the end of the callback.
Thats because you are creating the dataview yourself without using the usual flow of components. Since you aren’t using $create, it isnt initialized for you or added to Sys.Application for use with $find. You can initialize it yourself, of course, but you have to call initialize().
What would the $create usage be in this example?
$create(Sys.Preview.UI.DataView, {data: result}, null, null, $get(‘RSSItem’));
We are making render private in the next release to avoid the confusion.
Arg, the comment gobbled up the html I typed. After the 2nd statement should be (with correct type of bracket):
[tbody sys:attach="dv" ... ]
Here’s a stupid question. In using the declarative markup, how do you set the data to the dataview? I assume this is done in the callback method as shown in this post, so the underlying question is how to I get a reference to the dataview? Obviously getting a reference to the template would be done with $get(‘RSSItem’) – but that’s not the dataview.
Jeremy — components created declaratively behave no different than those created imperatively such as with $create, so you can get it using the Sys.Application.findComponent method, or $find for short, passing the id.
e.g.,
..
can be referenced with:
var dataView = $find(‘dv1′);
Thanks for the quick reply, Dave. That’s how I was referencing it so the error must be how I’m wiring up the declarative markup.
If I’m following your notes, I should be able to use the following:
And then, in my call back, simply say:
When I do this, however, I get a null reference on $find(); this works perfectly, however, if I attach the dataview using $create() instead of the declarative markup.
sys:activate is only supported on the body element, move it there (may as well move the ns definitions too, only sys:attach is needed on the div). So your problem is the dataview simply isnt being created.
This works perfectly; thanks, again.
In a real world setup it should be noted that the element will often be in a master page where as data bound items will quite likely be in a user control. Setting up a passthrough from control to page to master template is definitely possible, but it’s a lot of wiring for a common task. For this reason, I really liked the fact that other namespaces could be defined at the local level. This might be worth accounting for in future versions.
Jeremy — sure, namespaces are fine at the local level (not anywhere though — only on the root of the template). sys:activate isn’t, but we’re looking at ways of making that more accessible from content pages and user controls, too. Thanks for the feedback.
I’m not the biggest fan of the declarative syntax (see this post for more on that). So, I was more more specifically trying to determine if the new templating features will work the programmatic way that I prefer.
Thanks for the tip on not needing the render() call. That’s good to know.
If using the tbody as template root is iffy, is there a way to specify the parent table element as the template root, but have the DataView only “repeat” the tbody? I went with the tbody because using the table caused it to repeat the thead for every array element, in addition to the tbody.
If that were possible, it might be the ideal solution to my suggestion about table handling.
Of course you can do it imperatively if thats what you prefer. I think it is arguable though whether its an argument for or against the declarative hookup — DataView is more obvious that it is behavior injected, but some things aren’t so obvious, like a DatePicker. The fact an input is a date picker may not matter what-so-ever to any logical aspects of your application, its a view detail. Allowing for the markup in the view depends on who you are and who is designing it, wrt how easy or hard it is to understand for that person, or whether their tools if any can deal with it.
As for your comments on TABLE, we will definitely consider the usage and find the right thing. One possibility is a “initially hidden” feature of the dataview. You would attach the DataView to the table, which is initially hidden, but point its template at the tbody (template and dataview dont have to be the same). DataView would then become visible once rendered for the first time.
I’m not sure if anyone is still monitoring this thread or not but just in case.
I am having trouble binding a DataView where the template is inside a table. What I want to be able to do is have the template for the DataView be a tbody that will repeat for the number of search results returned by the DataSource assigned to my DataView. I therefore don’t want any of the table visible should zero results be returned.
In Dave’s comment he mentions that I should attach the DataView to the table but point the template at the tbody.
I have tried a number of different things here but may just be doing something obviously wrong.
What I have so far is:
searchResultsListView = $create(Sys.UI.DataView, { dataSource: searchResultsDataSource }, { itemCreated: onSearchResultItemCreated }, {}, $get(‘results’));
searchResultsListView.set_itemTemplate(‘searchResultItems’);
Where ‘results’ is the whole table and is ‘searchResultItems’ what should be the repeating tbody.
The error that I get is being thrown from Sys$UI$DataView$_getTemplate() where it does this:
if ((e !== template) && this._elementContains(e, template, true)) {
throw Error.invalidOperation(Sys.TemplateRes.misplacedTemplate);
}
And the error is: ‘DataView item template must not be a child element of the DataView.’
The template is searchResultsItem and e (element) is the results (the table that will contain the repeated items).
Steve — in the earlier bits, if you set the item template of a DataView, it was ok if it was contained within the DataView, because we implied that the item placeholder is where the template is. For various reasons, we changed that in the next preview release. Now, if you set itemTemplate, the template can’t be a child of the DataView. So, actually you get one little feature out of that, in that the stuff initially inside the table of the DataView is what shows up prior to any data being loaded (you may even have a cell or something that says ‘now loading…’).
So you will need to build a table that contains a placeholder describing where the template instances should go, and seperately define a template table. Something like this:
Then do what you are doing except also set dataView.set_itemPlaceholder to the ph element. When the DataView renders, the placeholder element (the TR containing the TD “now loading”) is completely removed, and the instances of the template are inserted into the tbody at the point where it was. So that also means, by the way, you can define header/footer rows, like so:
Hope that helps.
Dave, thanks for the update regarding this.
I think that while the example you provided doesn’t quite fit what I want to do, in that I have table headings that I also don’t want to be visible unless some results are returned, it certainly is useful to know about using the itemPlaceHolder property of the DataView.
I guess what I’ll need to do is to make the whole table not visble prior to binding and then once the DataBinding has occurred look to see if data was retrieved and then just make the table vsisible or not.
Thanks again.
I like it. But is there any concern/limitations with adding complex html, or DOM events, to the newly created template. A nice way to wire up onclick or onkeydown might be a cool feature to have incorporated as well.
No limitations in particular. You can hookup an event handler the way you would expect. onclick=”foo()” or onclick=”{{ ‘foo(\” + id + ‘\’)’ }}” where id is a field on the data item. Lots of people don’t like that sort of thing (mixing script and markup), so you can use the item created event on the dataview to find the particular element and hook it up with code. Or just use event bubbling and hook it on the dataview itself instead of each element.
very cool thanx.
Has anyone tried this in IE6? It seems to work fine in FF but we’re still on IE6 at work and it throws an ‘Unknown runtime error’
I’m seeing the same thing when testing under IIS6 and IE6. For what it’s worth (Dave & Co), the error is thrown on this line:
Who’d have though it, MS stuff working in FireFox but not in IE :) I presume it works okay in IE7?
Yeah, it definitely works in IE7.
This works fine for me in IE6. However, I had to make some edits. There are a number missing ‘;’ at the end of functions and just before the next function. i.e.,
}
Sys.Application._disposeElementRecursive = function Sys$Application$_disposeElementRecursive(element) {
to
};
Sys.Application._disposeElementRecursive = function Sys$Application$_disposeElementRecursive(element) {
There are probably around 50-60 semicolons that I added.
Dave — I believe that error you are getting is due to the known issue with attaching a DataView to a tbody. You can fix that by wrapping the call to innerHTML in a try/catch and remove all the child elements with removeChild instead in the catch.
Phil — I am confuzzled by your findings. It works in IE6 without such edits for me, and apparently for Dave as well. Are you by chance running the script through an additional minimizer or other tool?
Dave – perhaps the edits are not required. The reason I edited my scripts as a test due to some of the JSLint (http://www.jslint.com/) recommendations. I’ll have to retest with the with unmodified scripts.
As far as I know you don’t need semi-colons there. Noticed yesterday that the readme.txt only states it works in IE7 too. I’ll wait until the Sep release to see if it’s fixed
Dave, you are correct. The semi-colons are not required. Section 7.9 of the ECMAScript Language
Specification talks about ‘Automatic Semicolon Insertion’. However, since there are certain restrictions that based on formatting, such as
“… then a semicolon is automatically inserted
before the offending token if one or more of the following conditions is true: The offending token is separated from the previous token by at least one LineTerminator.”
In the minimalized version, the semi-colons are added to delimit the statements.
Hi, I just tried this and it worked quite well, very fast and easy to do. I then compared it to a normal page without using ajax and binding to a listview control with the same markup. The listview version was far quicker simply because it didn’t have to download the JS library. When would you say is best to use this approach over say a normal server based approach?
You’re right, this definitely isn’t the fastest way to render the data on an initial page load. However, deferring ancillary content until after the initial load can sometimes boost perceived performance. For example:
http://encosia.com/2008/02/05/boost-aspnet-performance-with-deferred-content-loading/
The real application for the client-side rendering technique is in continually updating data after the page has loaded. Basically, doing this in a much more efficient manner:
http://encosia.com/2007/07/25/display-data-updates-in-real-time-with-ajax/
Hi,
This is a nice article.Helped very much with ajax preview 2 experimenting.
Thanks,
Thani
The sample didn’t work on my end.
It didn’t work in IE on my end.
Nice blog…
Can you help me translate this to VB? I used codechanger.com but it fails to convert properly.
[WebMethod]
public static IEnumerable GetFeedburnerItems()
{
XDocument feedXML =
XDocument.Load(“http://feeds.encosia.com/Encosia”);
var feeds =
from feed in feedXML.Descendants(“item”)
select new
{
Date = DateTime.Parse(feed.Element(“pubDate”).Value)
.ToShortDateString(),
Title = feed.Element(“title”).Value,
Link = feed.Element(“link”).Value,
Description = feed.Element(“description”).Value
};
return feeds;
}
PS: I got error message when VB web service returns IENumerable datatype: Can’t serialize IENumerable.
All works fine, but what if I have some HTML markup in JSON data, such as B, I, U, etc tags? As I can see all markup is escaped :(
Does anyone has a solution? May be i’m doing something wrong?
You can use:
Just wondered if you had an updated version that worked with the release version of Visual Studio 2010?
Finally managed to track down the MicrosoftAjaxTemplates.js being as an embedded resource in 4.1.40412.0 version of AjaxControlToolkit.dll.
Although this is not referenced from the ToolScriptManager necessitating an extract of the js file using Reflector.
Is it really this hard?
Officially, this DataView stuff was deprecated before it was even released. However, as you found, there’s a “release” version of it that was bundled with the ACT. You can get access to that version easier on the CDN (this is the script loader, but the rest of the files are there too): http://ajax.microsoft.com/ajax/act/40412/start.js
However, watch out because this version of MicrosoftTemplates.js doesn’t add dataView to sys.create. So, typical code that worked with the September beta won’t work now without a little modification.
You can either use $create:
Or, use the nice jQuery plugin syntax they added at the last minute:
Ultimately though, I wouldn’t suggest writing new code against the DataView. Everyone’s efforts are going into jQuery.tmpl now, and it will be the preferred method going forward.
Thanks again Dave…once again your words are more than worth their weight in gold. In a couple of brief sentences you have both answered by original question and sucessfully pointed me in the correct direction for templating.
In retrospect, I should have personally questioned earlier the difficulty in finding the MicrosoftAjaxTemplates.js library.
Is it just me or is the AJAX client library stuff hard to keep a handle on from a version and best practice in usage perspective? Is there actually a “final” released version of this?
This may be going off topic slightly, but:
I appreciate that things are “simplified” by using the CDN but I’m not clear on best practice within an intranet environment (we have a number of web apps deployed within our organisation). Surely it is quicker to get the scripts from an internal web server rather than to go out to the internet to CDN?
Best wishes.
Robin
Yes, it has definitely been hard to keep up with. In terms of best practices, I think the recommendation from Microsoft is not to use this stuff at all unless you’ve already built something on the beta. They’re putting all their energy into jQuery-centric versions of similar functionality, going forward.
You’re correct to question using the CDN on an intranet. I wouldn’t use it internally either.
What you can do is save the CDN’s files locally and host them on your intranet server. To use the DataView, I believe just these scripts are needed:
http://ajax.microsoft.com/ajax/act/40412/start.js
http://ajax.microsoft.com/ajax/act/40412/MicrosoftAjaxCore.js
http://ajax.microsoft.com/ajax/act/40412/MicrosoftAjaxComponentModel.js
http://ajax.microsoft.com/ajax/act/40412/MicrosoftAjaxSerialization.js
http://ajax.microsoft.com/ajax/act/40412/MicrosoftAjaxTemplates.js
Save those all in the same directory, and then reference that local copy of start.js in your page. Sys.require() will load your local copies instead of the ones on the CDN then. Insert a .debug before the .js to retrieve the uncompressed version of any file (e.g. http://ajax.microsoft.com/ajax/act/40412/start.debug.js).
For expanded functionality, like auto-fetching data from web services, the DataContext, oData, etc, get these as well:
http://ajax.microsoft.com/ajax/act/40412/MicrosoftAjaxNetwork.js
http://ajax.microsoft.com/ajax/act/40412/MicrosoftAjaxWebServices.js
http://ajax.microsoft.com/ajax/act/40412/MicrosoftAjaxDataContext.js
http://ajax.microsoft.com/ajax/act/40412/MicrosoftAjaxOpenData.js