Entirely unobtrusive and imperative templates with Microsoft Ajax Library Preview 6
Today is the release of the sixth preview of Microsoft Ajax Library. Don’t get fooled by the somewhat silly and long name: this is a major release in many ways. The scripts have been majorly refactored since preview 5. Check out the other posts out there (links at the bottom of this post) to see just some of the many new features that are in there. Some of my favorite are all the small improvements that have been made to make imperative instantiation of components and templated contents easier than ever. Many of you have told us that you preferred to do things imperatively and this release makes it a lot better.
When Preview 5 came out, I built a simple class browser using the declarative syntax. The class browser shows the hierarchy of namespaces and classes in a tree view on the left side of the page, and the details of whatever’s selected in the tree on the right side of the page:http://weblogs.asp.net/bleroy/archive/2009/09/14/building-a-class-browser-with-microsoft-ajax-4-0-preview-5.aspx
It still works (and an updated version is attached to this post), but I thought I would demonstrate how you can take that same sample and re-implement it in a completely imperative way. Of course, you never have to go all the way one way or another, and it’s always possible for example to use the nice declarative syntax for bindings but instantiate your components imperatively if you choose to do so. In this post, I’m deliberately going imperative all the way. Just keep in mind this is rather extreme.
The first thing to notice in the new version is that the markup is perfectly clean and contains no weird extension, namespace or custom binding syntax whatsoever. It’s 100% pure HTML 5. Here is for example the complete markup for the tree view on that page:
<ul id="tree" class="tree"></ul> <ul id="nodeTemplate" class="sys-template"> <li> <a class="toggleButton" href="#">+</a> <a class="treeNode" href="#"></a> <ul></ul> </li> </ul>
The script that builds the dynamic contents is bootstrapped by the following code:
<script type="text/javascript"
src="Scripts/start.js"></script> <script type="text/javascript"> Sys.loadScripts(["Scripts/Tree.js"], function() { Sys.require([Sys.components.dataView], function() { createTreeView("#tree",
Type.getRootNamespaces(),
"#nodeTemplate"); Sys.create.dataView("#detailsChild", { itemRendered: onDetailsChildRendered }); }); }); </script>
We’re making use of the new script loader here: we first include the bootstrapper file, start.js, and then we declare that we need one custom script, “tree.js” and everything necessary to instantiate a DataView. The script loader will figure out on its own the set of scripts it needs to download for that. Once those scripts have been downloaded, we call createTreeView, which is custom code that we’ll look at in a moment that creates nested DataView controls over the markup. We also create a second DataView to display the details of what’s selected in the tree.
Notice that we set some properties to a selector string here (for example “#nodeTemplate”). This is actually a breaking change from the previous preview, which only understood id strings. Microsoft Ajax does not include a full selector engine but it does understand the most basic of selectors (.class, tagName and #id). But where it gets really interesting is that if you had included jQuery on the page, the framework would detect it and enable you to use full selectors everywhere. Isn’t that sweet?
So how does the imperative approach compare with the declarative one? Well, for instantiating components, you already have an example above, where we use Sys.create.dataView. But what about wiring up events, setting text contents and attribute values, instantiating components over the markup inside the template?
All those are done by post-processing the template instances after they’ve been instantiated, by handling the itemRendered event:
itemRendered: function(sender, args) { // do magic }
Wiring up events is as simple as getting a reference to an element and calling addHandler:
var toggleButton = args.get(".toggleButton"); Sys.UI.DomEvent.addHandler(toggleButton, "click",
function(e) { toggleVisibility(this); }, true);
The args.get function, which you will use a lot, takes a selector and returns the first element that matches it inside the template. Here, we are looking for an element with class “toggleButton”, but a local id would work just as well.
To set text contents and attribute values is trivial once you know how to get references to elements from local selectors (remember, jQuery also works here transparently or even explicitly when and if you need it).
Finally, instantiating components is also quite easy. For example, here is the code that creates an inner DataView for the child nodes of a node in the tree, recursively:
var childView = args.get("ul"); createTreeView(childView,
getChildren(args.dataItem),
nodeTemplate);
The args.get funtion is used once more to get a reference to the first UL element within the template, and it is then easy to do a recursive call into our tree creation function and build the new branch of the tree.
The command bubbling feature that makes it so easy to wire up custom commands into a template is still usable in imperative code:
var treeNode = args.get(".treeNode"); Sys.setCommand(treeNode, "select");
Finally, there is one feature that I’m not using in that sample, but that’s immensely useful, and I’m talking of course of live bindings. Those work too, all you have to do is call the Sys.bind function and give it the target object, the name of the target property to bind, the source object and the source property name.
To render the details view, I decided to not use a single item DataView like I did with the declarative version: since I’m going to use imperative code instead of declarative bindings, it is just as easy to directly manipulate the DOM that already exists, and do some hiding and showing of elements:
function onCommand(sender, args) { if (args.get_commandName() === "select") { var dataItem = sender.findContext(
args.get_commandSource()).dataItem; var isClass = Type.isClass(dataItem) &&
!Type.isNamespace(dataItem); var childData =
(isClass ? getMembers : getChildren)(dataItem), namespaceElementsDisplay =
isClass ? "none" : "block", classElementsDisplay =
isClass ? "block" : "none", detailsChild =
Sys.Application.findComponent("detailsChild"); detailsChild.onItemRendering =
isClass ?
onClassMemberRendering :
onNamespaceChildRendering; detailsChild.set_data(childData); Sys.get("#detailsTitle").innerHTML =
dataItem.getName(); Sys.get("#namespacesColumn").style.display = Sys.get("#classesColumn").style.display =
namespaceElementsDisplay; Sys.get("#propertiesColumn").style.display = Sys.get("#eventsColumn").style.display = Sys.get("#methodsColumn").style.display = Sys.get("#staticMethodsColumn").style.display =
classElementsDisplay; Sys.get("#details").style.display = "block"; } }
We do have a DataView to render the contents of the currently selected object though. The nice trick we used with the declarative version to dynamically switch the target place holder where the template gets rendered is still there, which enables a single DataView control to dispatch the data into two to four separate lists (or however much you want for that matter):
function onNamespaceChildRendering(args) { args.set_itemPlaceholder( Type.isClass(args.get_dataItem()) ? "#classPlaceHolder" : "#namespacePlaceHolder" ); }
I think all this is pretty cool and I hope the comparison between the declarative version and the imperative version of this little application gives you a sense of the flexibility that the Microsoft Ajax library now offers, and of how much you can choose your own development style and do pretty much anything with the same ease.
Download the code for this post here:
http://weblogs.asp.net/blogs/bleroy/Samples/Preview6ClassBrowser.zip
Microsoft Ajax Library Preview 6 can be downloaded from here:
http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=34488
Here are a few links about this release:
http://weblogs.asp.net/scottgu/archive/2009/10/15/announcing-microsoft-ajax-library-preview-6-and-the-microsoft-ajax-minifier.aspx
http://channel9.msdn.com/posts/jsenior/Announcing-Microsoft-Ajax-Library-Preview-6/
http://www.jamessenior.com/post/How-the-Script-Loader-in-the-Microsoft-Ajax-Library-will-make-your-life-wonderful.aspx