How to render the same template on the server and client with minimal redundancy
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:
- Markup and behavior are strictly separated. That means no DOM-0 event handlers, no custom attributes or tags, and even no microformats imo.
- 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. 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