How to render the same template on the server and client with minimal redundancy

(c) 2005 Bertrand Le Roy

Last week, I wrote a post about how the new Microsoft Ajax Library Preview 6 made it a lot easier to write unobtrusive and imperative data-driven applications. Because for the previous preview, I had written a cool little class browser using a declarative style, I thought it would be nice to rewrite this in a completely imperative way. The mistake I made though was to call it unobtrusive. Never mind that ‘unobtrusive’ is a perfectly well-defined word that actually existed way before JavaScript. ‘Unobtrusive JavaScript’ has a very specific meaning that people feel strongly about. To be worthy of that label, an application must basically conform to (at least) those two requirements:

  1. Markup and behavior are strictly separated. That means no DOM-0 event handlers, no custom attributes or tags, and even no microformats imo.
  2. Graceful degradation / progressive enhancement. This means that the application’s script is only used to improve what would work without script, in other words that the application is entirely usable without script.

While my little sample strictly conformed to (1), it didn’t conform to (2). Not because I’m too dumb or ignorant, mind you, but because for this application, it didn’t make any sense at all: the application’s whole point is to display client-side script objects. The server does not have the first clue about the data that needs to be rendered. Which makes it impossible for anything to render without script. Which in turn triggered some unpleasant comments on the post: this was not really unobtrusive JavaScript in the full sense of the term. Not the library’s fault though, just my own for using the wrong example.

The right example

So I thought the best thing to fix this is to provide a more relevant example, one where the server could actually be a fallback scenario.

The new sample code is a fairly simple master-details view on a silly data set: jedi data. We have a WCF web service that is returning a list of jedis and their associated data, which the application can render on the server-side or on the client-side.Jedis Once you  have data that is available from both the server and client sides, your best and simplest tool to achieve progressive enhancement is the plain <a> tag. You can build the links in your application so that without script, they do something meaningful:

<a href="?jedi=all" id="expandButton">+</a>

What you see above is the + link on the top-left of the screen that will expand the list of jedis. To make it work client-side instead of the server-side, you use script to add a click handler that suppresses the default behavior of the link and replaces it with the equivalent client-side action:

Sys.UI.DomEvent.addHandler(
Sys.get("#expandButton"), "click", function(e) { e.preventDefault(); jediList.fetchData(); return false; });

That’s fairly easy and has been possible since, well, I’m not sure but it’s been a looong time.

Now there’s the templated rendering. Rendering the same thing from the server and the client without repeating oneself too much is not as simple. The key to it is to render the server template with an empty data item and then to use the result of that as the client template.

<ul id="jediList"
<%if (!isDetails) { %> class="sys-template"<% } %>> <%var jedis = !isDetails ? new List<Jedi>() {new Jedi()} : new JediService().GetJedis(); foreach (var jedi in jedis) { %> <li>
<
a href="?jedi=<%= jedi.Name %>">
<%= jedi.Name%>
</
a>
</
li> <%} %> </ul>

This way, there is only one template, only one version of the markup, that we are using on both the server and client sides. When rendered from the server with the actual data set, we get the list right away, and the browser just displays it (and search engines can see the list as well, something you can’t achieve with pure script). When rendered with the dummy dataset, we get the following:

<ul id="jediList" class="sys-template">
  <li><a href="?jedi=all">all</a></li>
</ul>

This markup can be used as a template on the client-side by this code:

var jediList = Sys.create.dataView("#jediList", {
  dataProvider: "JediService.svc",
  fetchOperation: "GetJedis",
  autoFetch: false,
  itemRendered: function(sender, args) {
    var link = args.get("a");
    var dataItem = args.dataItem;
    link.innerHTML = dataItem.Name;
    Sys.UI.DomEvent.addHandler(
link, "click", function(e) { e.preventDefault(); details.set_data(dataItem); return false; }); } });

The details view is built on pretty much the same principle. The main difference is that it does have additional markup to delimit the fields that we’ll want to set dynamically. Here’s how it renders with the dummy data:

<div id="details" class="sys-template">
    <span id="jediName"></span>
    owns a
    <span id="jediLightSaber"></span>
    lightsaber and is on the
    <span id="jediSide"></span>
    side.
</div>

All the itemRendered handler has to do then is fill in the blanks:

var details = Sys.create.dataView("#details", {
  itemRendered: function(sender, args) {
    args.get("#jediName").innerHTML =
args.dataItem.Name; args.get("#jediLightSaber").innerHTML =
args.dataItem.LightsaberColor; args.get("#jediSide").innerHTML =
args.dataItem.DarkSide ? "dark" : "light"; } });

So what do you think?

Here’s the code (by the way, I’m using the Microsoft CDN in there):
http://weblogs.asp.net/blogs/bleroy/Samples/ServerClientTemplate.zip

9 Comments

  • @Mike: absolutely.

  • I assume "isDetails" translates to "?jedi=all" in the query string?

  • @Kelly: the server-side code, which I didn't detail here but that you can check out in the project zip, sets isDetails if "jedi" in the querystring is not empty. It can be "all" or the name of a jedi. In non-JS mode, the app has three states: initial (no list, no details), list expanded (but no jedi selected: "all"), and list expanded + details (jedi name on the QS).

  • @Joe: understood, and that's also the definition I've been going with.

  • This isn't the comment you're looking for.

  • @Andrew: nicely done. This is really interesting. It would be interesting to see how far you can go with complex expressions and also if you can generate template functions that are compatible with the Microsoft Ajax DataView.

  • @Andy: Thanks for the link. I don't see from this link what this has to do with the server. Also, OpenSocial templates suffer from a number of issues:
    * templates are stored in script tags, which is abusing the script tag and prevents authoring and validation tools from working over them.
    * the expression language, strangely, is JSPEL, whereas JavaScript is perfectly fine as an expression language. Choosing an arbitrary server technology (Java) over the existing client standard when building a client technology looks like a very weird choice.
    * it uses non-namespaced custom tags and attributes for conditional rendering and loops.
    * it has no hooks to enrich the created markup with script (to add event handlers and components for example).
    * no live bindings

    But yes, the template libraries are a neat idea. We've been thinking about something similar, closer to user controls. Maybe I'll post about that soon.

  • > I don't see from this link what this has to do with the server

    Data Pipelining and OS Templates can be evaluated on the server (not covered very clearly in those links), as is the case for gadgets on Profile views. The Shindig implementations currently do this server processing (which are of course, Java based, hence using Java conventions in the templates).

    I'm definitely going to be using Microsoft Ajax templates in projects, but OpenSocial Templates suddenly made me consider a change of heart, with their promise of easy distribution of ajax/templating to multiple social type sites, and even now corporate portal products (like Atlassian products). And it's less of a fudge, they should work exactly the same on the server side.

    Lack of hooks is an issue, but is a by-product of this goal of wider distribution with OS Templates.

    On another note, I was very excited today to get two-way binding to work with Extjs controls with Microsoft Ajax Library. Not too much hacking, and doesn't need to wrap the controls really, just a few events and added methods. It would be interesting to see on Codeplex or somewhere, hints on how to fit the binding into common frameworks.

    var textField = new Ext.form.TextField({});

    textField.on('change', function() { this.raisePropertyChanged('value') }, field);

    Sys.bind(textField, 'value', dataContext, 'title', { mode: Sys.BindingMode.twoWay });

    Ext.form.Field.prototype.set_value = function(value) {
    this.setValue(value);
    }

    Ext.form.Field.prototype.get_value = function() {
    return this.getValue();
    }

    Ext.Component.prototype.add_propertyChanged = function(handler) {
    Sys.Observer._addEventHandler(this, "propertyChanged", handler);
    }

  • @Andy: "which are of course Java based": how is it obvious that the server must use Java?
    What would prevent you from using Microsoft Ajax templates in an OpenSocial widget?

Comments have been disabled for this content.