Ajax Templates

This post describes a prototype for an Ajax templating solution, combined with an Ajax-friendly server-side solution that hints at what is coming down the road in ASP.NET Ajax.

Just over a month ago, at MIX08, I presented a talk on Real-World Ajax. One of the demos I included was an Ajax templating technique to demonstrate separation of presentation and content from behavior to help manage script complexity, and to help facilitate designer/developer workflow.

The demo was based on early thoughts around what we need to do in this area. What is presented here is still quite simplistic in terms of the range of options it provides to developers. The demo worked well with the audience, and I'd love to hear any feedback people have in this area that can be fed into the design process at this early stage (I alluded to this work in comments to my Ajax vs. Silverlight post).

Here is my scenario - in my Ajax app, script issues a web request to fetch a list of data, in this case a list of bookmarks, and constructs HTML to visualize the results. This is often implemented as follows:

<div id="bookmarkList"></div>
<script type="text/javascript">
function fetchBookmarks() { 
    // Issue an XMLHTTP request to retrieve user's bookmarks.
}
function onBookmarksAvailable(bookmarks) {
    // Update display using retrieved bookmarks. Each boomark has Url and Title properties.
    var sb = new Sys.StringBuilder();
    sb.append("<ul>");
    for (var i = 0; i < bookmarks.length; i++) {
        sb.append("<li>");
        sb.append("<a href=\"");
        sb.append(bookmarks[i].Url);
        sb.append("\">");
        sb.append(bookmarks[i].Title);
        sb.append("</li>");
    }
    sb.append("</ul>");
    $get('bookmarkList').innerHTML = sb.toString();
}
</script>

This works. However, there is a problem. The definition of the UI is now embedded in script. Its hard to design and change it. And its hard to preview it without running the page. The problem is worse if the structure of each item is more complicated than what the sample tries to do.

Here is where templating comes into the picture. Templates have been super common in server-side frameworks like ASP.NET, and are now popping up in client-side script frameworks, as Ajax apps start becoming more client-centric. With templating the developer or designer can define the structure of an item in regular markup, and the templating engine is then responsible for generating the script equivalent.

In the demo, I wrote a simple template engine, which I can use by to rewrite the script as follows:

<div id="bookmarkList">
  <ul id="itemContainer"></ul>
</div>
<div id="bookmarkTemplate" style="display: none">
  <ul id="itemContainer">
    <li><a href="{Url}">{Title}</a>
  </ul>
</div>

<script type="text/javascript">
function fetchBookmarks() {
    // Issue an XMLHTTP request to retrieve user's bookmarks.
}
function onBookmarksAvailable(bookmarks) {
    // Update display using retrieved bookmarks. Each boomark has Url and Title properties.

    var template = new Templating.Template($get('boomarkTemplate'));
    var itemContainer = $get('itemContainer', $get('bookmarkList'));

    for (var i = 0; i < bookmarks.length; i++) {
        var item = template.createInstance(bookmarks[i]);
        itemContainer.appendChild(item);
    }
}
</script>

The interesting lines are in bold. The template is defined using regular XHTML markup representing how items should appear. This can be independently edited/designed and previewed. You simply embed atomic tokens such {Url} etc. into the markup.

The script is responsible for creating the template, which effectively generates the script equivalent, (this is done once), and then as the script loops over the list of bookmarks, it uses the template to create new instances of the list items and anchors, and adds them into the DOM to generate the display.

With this we have a client-side templating mechanism. However web development is all about balancing and meeting several requirements (at once). In particular here are some of the things I also want to tackle as a developer.

  • Search Engine Optimization: Ideally the page rendered by the server isn't blank from the perspective of the search engine. I demonstrated a solution in my Ajax and SEO post over year ago.
  • Optimizing Networking: I don't want to spend one connection downloading a blank page, and then immediately having the script turn around to issue another connection to fetch the first page worth of data.
  • Reducing Page Load Time: Sending a blank page implies some delay as scripts are downloaded, loaded and data is loaded and processed to show data to the user. This should be avoided if at all possible.
  • Script-disabled browsers: If targeting an audience that might have script disabled (this is perhaps a shrinking number), then being able to still deliver a degraded but functional experience is nice.

These can be addressed through some form of server-side rendering, sort of how you've been doing using server-side data-bound controls for years. The goal however, in the context of Ajax, is to leverage a single template definition for both server-side and client-side processing. This is where server controls really shine. They allow you to do Ajax better by providing end-to-end solutions.

I've written an AjaxRepeater server control as part of the prototype. It does server-side data-binding (using the same token format we've seen above), renders the list on the server, but also generates a client-side Repeater control (using the ASP.NET Ajax UI pattern for writing controls). The client-side Repeater control consumes the same template definition, and provides the ability to dynamically add rows on the client, or reset the list of items to be displayed. The sample below illustrates using this control, along with both server-side code and client-side script working against it.

<n:AjaxRepeater runat="server" id="bookmarkList">
  <ul id="itemContainer">
    <li><a href="{Url}">{Title}</a>
  </ul>
</n:AjaxRepeater>

<script runat="server">
void Page_Load() {
    bookmarkList.DataSource = new Bookmark[] {
            new Bookmark { Title="...", Url="..." }, ...
        };
    bookmarkList.DataBind();
}
</script>

<script type="text/javascript">
function addBookmark(title, url) {
    var bookmark = { Title: title, Url: url };
    $find('bookmarkList').addDataItem(bookmark);
}
</script>

The current implementation is pretty much based on what can be done via String.Format to perform substitutions of tokens in the template with actual data (I used a tweaked version of James' FormatWith code). Eventually the hope is to productize this feature, and at that point we'd like you to be able add expressions in the template as well as express conditional segments within the template. For example, one might be able to write this:

<n:AjaxRepeater runat="server" id="bookmarkList">
  <ul id="itemContainer">
    <li>
      <a href="{Url}" class="{ IsShared ? 'bookmark shared' : 'bookmark' }">{Title}</a>
      <!--- if (!IsShared) { --->
      <button type="button" onclick="...">Share</button>
      <!--- } --->
    </li>
  </ul>
</n:AjaxRepeater>

As we extend the functionality of those templates, it is important to preserve the server-side rendering capabilities. One way to accomplish this is to use the DLR and JavaScript on the server. This can actually be done quite efficiently by doing the script-generation and compilation using the DLR at page parse time, once for the lifetime of the application, in much the same way that regular ASP.NET template markup is parsed and compiled once into a delegate that can be invoked multiple times. For ongoing prototyping at this moment, I am starting to look at JScript.NET, until the DLR is part of the framework.

One key realization here is that targeting the mainline scenarios that Ajax developers encounter all the time keeps the overall solution simple and elegant for both the developer and the framework. Combining that with the right server-side support makes the solution much more interesting, as it starts to address the end-to-end scenarios with a single set of concepts and a consistent approach.

I'll reiterate that this is a prototype that I partly demo'd at MIX, and isn't meant to represent exactly what might eventually make it into the product (as we all know, things change as we iterate on ideas, and put together feature plans)... but as I mentioned earlier, if you do have thoughts you'd like to share, then go ahead, and post them below.

For those who are interested at poking around in the code for either the templating engine (which is really quite small), or the server-side control, you can download the prototype as it exists today. If you delve in deeper, you might also notice that the included script was generated using Script# (as you might have come to expect of me by now) and comes along with the originating C# source as well. If you haven't installed Script#, then simply ignore the 2nd project in the solution when it prompts you. I am hoping to get a client-side templating engine added to Script# as well a little further down the road.


[ Tags: | | ]
Posted on Saturday, 4/12/2008 @ 10:44 PM | #ASP.NET


Comments

27 comments have been posted.

Kirill Chilingarashvili

Posted on 4/13/2008 @ 4:35 AM
Thanks for a great work .
I will definitely use the templating engine!

Kirill Chilingarashvili

Posted on 4/13/2008 @ 4:37 AM
Just today I submitted post (http://blog.devarchive.net/2008/04/advanced-tooltip-control-aspnet-ajax.html) where I created Tooltip control for ASP.NET AJAX.
The control has property HtmlTeplate of type ITemplate, where the developer can write some markup and use placeholders {0} and {1} to point where he want tooltip title and tooltip text is placed. (This can be done also in a Skin file)
Corresponding Sys.UI.Control then uses String.format to generate inner HTML of a tooltip div for different controls.

Of course using {title} and {text} can enhance a control and make it more intuitive to use then using {0} and {1}.

Thanks for a great article again.

Mike

Posted on 4/13/2008 @ 4:51 AM
So is this going to be in the official ASP.NET Ajax release, and if so, when? Sooner rather than later, because this is pretty sweet.

Matt

Posted on 4/13/2008 @ 7:40 AM
This is definitely some nice stuff! Keep up the good work and make sure this makes it into a near-future official release.

I must comment on the idea of script-disabled browsers. While I do agree that the number of browsers with no script capabilities is dwindling to an insignificant number, there are still plenty of corporations out there that disable scripting across all computers on the network. I know of several government offices, tel-cos, and other large and small businesses that have a security plan in-place that prevents the use of scripting in their employees browsers.

So, degrading to a pure server-side solution automatically (so we don't have to worry about it) is still very very important.

Keep up the good work!

Vish

Posted on 4/13/2008 @ 9:25 AM
Hi Nikhil,

The templating issue in javascript is something that I have struggled with in the past. I have tried different existing javascript templating engines before and here are my observations.

http://Vishcio.us/2008/01/23/json-over-xml/

Alos, the above syntax is kind of confusing to me, i may be totally wrong on this but here are my thoughts. The lines

var item = template.createInstance(bookmarks[i]);
itemContainer.appendChild(item);

template.createInstance(...) lead me to think that it will generate a <ul> tag instead of a <li> tag like above since <ul> is the direct child of the template.

Thank You,
Vish

Nikhil Kothari

Posted on 4/13/2008 @ 9:37 AM
Matt - I certainly think addressing the script-disabled scenario is somewhat important, and its interesting to hear specific scenarios where this is a very real situation.

Vish - point taken about the confusion as to whether createInstance returns the <li> or the <ul>. In my first incarnation, the template was just the <li>. It works, however, the resulting page is not valid XHTML (since a free-floating <li> isn't valid). There might need to be a flag in a more generic createTemplate function as to whether the entire markup is the template, or the markup represents an actual template surrounded by HTML to give it valid structure.

Geoffrey

Posted on 4/13/2008 @ 10:20 AM
Hey Nikhil,
I am so glad you posted this. I remember seeing you talk about this at mix and it was great to see the post come back to life. Love the fact that the blog posts are rolling out more frequently.. Keep them coming!
Thanks,
Geoff

Andrew Deakins

Posted on 4/14/2008 @ 7:10 AM
Hi,

I already use something similar to this. dont know if you have seen it but Trimpath along with json.net and JQuery seems to do a pretty good job of this. we have a base object which can be serialized as a json object and use trim path to take the javascript object and template it on the client side...

Andrew.

Joel Rumerman

Posted on 4/14/2008 @ 9:01 AM
Hi Nikhil,

This looks promising. I'm glad you're looking at something like this.

The one thing I'll add, though, is don't forget about the server control developers that want to use something like this. It's hard enough dealing with client Ids in normal JS dev, but when you start binding in JS, which is essentially what you're doing, you're going to want to be able to wrap all the binding functionality in a server control as well. We also don't want to spit out binding JS code for each server control, but rather use a general data binding mechanism.

I've implemented something like this at work that runs along the same lines. Maybe we want to take this off line and see what I've done with it. I've been meaing to do a blog post about it but can't seem to get started on it ...

Regards, Joel

Morten

Posted on 4/14/2008 @ 10:15 AM
I've done something similar but I used two types of "tags"
{@name} //Binds the name attribute from the object
{eval(...)} //Does an eval on a JS expression

The nice thing about the eval is more obvious with an example:
"{eval(Math.round({@ratio}*100)}) %" //Convert to percent and round it
"{eval(String.left('{@description}',100)}) ..." //Show the first 100 characters of a description

Nikhil Kothari

Posted on 4/14/2008 @ 10:57 AM
Joel - would love to continue the discussion offline... you can send email directly or via my contact form.
One interesting constraint in this prototype is the contents of the template need to be static HTML... this is so the template can be reused on the client as-is. I had my own template parsing code, but I suspect, this should be promoted to a general purpose new kind of template, esp. once we add in support for running script expressions etc. Is this what you meant by server control developers? Given it doesn't have server controls, there aren't any ID issues. But I suspect there is more you're thinking around the server control angle, so I look forward to hearing from you.

Morten - The expressions capability I alluded to would allow you to tackle similar scenarios, without requiring the use of eval. The key constraints would be your expressions refer to only to fields on data associated with the template instance, and are valid script expressions, so we can evaluate them either in the browser or in the DLR script engine. The thing to keep in mind, is that the template is converted to regular script, and your expressions just become part of the script that is generated, so no need for additional eval calls, which is really nice, and quite elegant.

ash

Posted on 4/14/2008 @ 3:30 PM
thanks for the article.
i use the following code if i deal with single control, this'll avoid javascript dom code in the client. if we talking about more complex UI (multiple control i would use a user control and render them as string)

<div id="bookmarkList"></div>
<script type="text/javascript">
function fetchBookmarks() {
// Issue an XMLHTTP request to retrieve user's bookmarks.
}
function onBookmarksAvailable(bookmarks) {
$get('bookmarkList').innerHTML = bookmarks;
}

// server code ..
public string GetControl()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
using (System.IO.StringWriter sw = new System.IO.StringWriter(myWebFormControl))
{
using (HtmlTextWriter htw = new HtmlTextWriter(sw))
{
lb.RenderControl(htw);
htw.Close();
}
sw.Close();
}

return sb.ToString();
}

Nikhil Kothari

Posted on 4/14/2008 @ 4:36 PM
Ash - the motivation behind this is that you're running the logic on the client, by transferring data back/forth, and not pre-generated HTML. If you're doing pregenerated HTML, this and several other options already exist.

Salman

Posted on 4/15/2008 @ 6:52 AM
Will search engines be able to parse the bookmark list though? Showing an empty template doesn't really do much in terms of spidering content.

Nikhil Kothari

Posted on 4/15/2008 @ 7:52 AM
Salman - if you see the rendered HTML from the server control (which I didn't write down in the post for brevity), you'll see its not just the template, but actual data... because like any server-side control or repeater, it does data-binding on the server to produce a list of items.

Alex O

Posted on 4/15/2008 @ 10:28 AM
Nikhil -

Interesting stuff as usual. Have not gotten a chance to play with the code yet but will get to it when I can.

For now, a scenario where server side composition is very important is NOT where script is disabled on the client. Server side composition is beneficial in scenarios where the page complexity is high and hardware (CPU/memory) resources are scarce. Terminals (thin clients with a browser) and mobile devices are two such platforms where the time to render is very high. Older machines (with more and more bloatware on them) are also starting to behave as terminals.

Putting aside the argument of "proper" design, specifically for a mobile devices/slow machines, server side composition really shines in this scenario. I have seen scenarios where client render times can be ridiculous (say 1 – 2 minutes), whereas sending ready to use HTML can reduce overall ready for consumption times to 5 seconds (despite higher bandwidth usages) . Combinations of server side initial page composition and client side progressive updates are very useful as well. Windows Mobile/PPC IE, iPhone Safari, Windows IE running on thin clients tend to be very slow for anything more than a simple page. A lot of usual problems apply but you should definitely take this scenario into account.

Pauly

Posted on 4/15/2008 @ 12:09 PM
As I was reading this article the comment "...but also generates a client-side Repeater control (using the ASP.NET Ajax UI pattern for writing controls)" caught my attention. Perhaps I am just to new to ASP.NET Ajax control creation, but could you provide a link or two to some articles on the subject or a design pattern that decribes the proper method for developing custom server/client Ajax controls. Thanks in advance, great post!

Nikhil Kothari

Posted on 4/16/2008 @ 7:41 AM
Alex - yes, low-powered mobile devices and clients are another good reason for server-side rendering. I fully agree. Some of that is factored into the "reducing page load time" which includes not only download time, but time to first rendering.

Pauly - I don't have a link handy on writing such controls. I know there will be a book soon... I am just seeing the preview of it. I have posted various samples of things and the code associated with them illustrate writing asp.net script-enabled controls that work with asp.net ajax.

Cliff Cole

Posted on 4/16/2008 @ 10:12 AM
Hi Nikhil,

Kazi Rashid over on DotNetSlacker has put together a great set of client-centric data controls (Ajax Data Controls). They mirror the server-centric controls currently found in asp.net ie. GridView, DataList, Repeater etc

www.dotnetslackers.com/projects/AjaxDataControls/Default.aspx

They are definitely worth giving a look.

Miguel Madero

Posted on 4/16/2008 @ 11:40 AM
Nikhil:

Great work, as always. It looks like you just found a use for managed JS hehehe, jk. It seems weird tough, than you're using C# code to build the library with Script# to generate JavaScript, but you would use JavaScript for code in the template to generate IL on the server. It would be cool if we could use C# for the templates and evaluate it in the server as you would with managed JS and use script# to generate the necessary JS to have the code running on the client.
Well all this C#-JS-IL thing makes me think of Volta. Some of the issues, regarding different devices and generate JS or HTML depending on client capacity is precisely one of the problems Volta is looking to solve.
Well obviously the cool part about this is that Script#, these controls and other things like retargetting JS and the SL 2.0 with the HTML bridge allows us to solve this issues now (or at least in a near future) while we will have to wait longer for Volta. Anyway, what do you think about Volta related to the impact it might have on these kind of things you're talking.

Robert Barth

Posted on 4/16/2008 @ 3:53 PM
I notice the ID of the HTML elements is set once, meaning that every template row is going to have the same ID. Is there a plan to be able to set the ID's of the rendered template items so that they can be referred to later in JS code? E.g. one may have a file upload control and have a template for the row consisting of the upload control, a title for the file, and a remove button (in the case where the developer allows the user to add more file upload controls to the page). The developer would need to be able to refer to those elements later on in JS code uniquely so that the remove button worked, etc.

Nikhil Kothari

Posted on 4/16/2008 @ 5:58 PM
Miguel - yes its a bit of irony - the JS thing on the server. Its definitely possible to use script# to convert the c# into script... it should work quite well actually, and has in fact been discussed somewhat here in the asp.net hallways. Another thing to add to my list. :-) ... I am not sure about Volta - its focus is on tier splitting, and not specifically on conversion to script... whereas Script# is very much focused on pragmatic conversion to efficient, compact script.

Robert - you reference the ID'd elements by using getElementById off of some root element, such as each repeated item, and not off of the entire document. This lets you get away with duplicate IDs, i.e. not introduce the naming-container induced unique IDs (that get in the way of things like CSS).

Nikhil Kothari

Posted on 4/16/2008 @ 9:28 PM
Miguel - one other thought - the expressions and statements I'd expect to be embedded in a template are quite minor fragments of script (and theoretically identical to little bits of code), and when combined with the compiled usage model, its not so bad. However building entire classes of code, and possibly controls and frameworks... this is where script# and using all of c# tooling to target script development really helps. Just a thought I had around the seeming irony of using script on the server.

Eric

Posted on 4/21/2008 @ 1:43 AM
What would be really great is if the less common browsers out there, like Safari & Opera, would get their act togeher and support xslt transformation through javascript. One doesn't support it at all, and the other only supports it at pageload.

Greg

Posted on 4/29/2008 @ 11:33 AM
I have a javascript template engine that uses a subset of WPF XAML as the template markup. I also have a set of ASP.NET server controls that mimick XAML markup by exposing properties like ItemsSource and ItemTemplate. This is really useful if you want your application to run in multiple environments, such as desktop, web, mobile, etc.

Please contact me if you want to go over the details, I think it would be an added benefit to the ASP.NET library.

Andrey Shchekin

Posted on 5/14/2008 @ 11:38 AM
Nice. I have done a similar thing in a current project (except for server part), unfortunately, it is unshareable due to the project nature.

The interesting challenges for me were:
1. Support a dynamic binding to a JavaScript ObservableCollection-analog class. So I can actually change a data collection and repeater automagically changes with it. It was easy, and makes auto-synchronisation of different page parts so much straightforward.
2. Support client repeating of ASP.NET AJAX controls -- this is quite a challenge, and the version I have right now is working, but not very beautiful.
3. Dynamically create client js data binding function based on the Template expressions. That was really fun to do, but now I think it would make much more sense to do it on server.

I will now have to research your soution, it would be great if I could throw our one out -- less code to maintain.

Pete Hurst

Posted on 7/18/2008 @ 7:27 AM
This is a great project. I'd been thinking similar ideas, nice to see it implemented. The syntax is actually simpler and more readable than standard <%# Eval() %> databindings, so well done. Why couldn't ASP.NET work like this to begin with? ;)
Post your comment and continue the discussion.
 
 
 

 

 
Refresh this form if the spam-protection code is not readable, or has expired. (Your input will be preserved)