Building a class browser with Microsoft Ajax 4.0 Preview 5
The Microsoft Ajax Library 4.0 Preview 5 is the first release of Microsoft Ajax that I didn’t participate in: I left the team a few months ago. But that doesn’t mean I don’t love what’s in there, and I really do. And by the way I’ve also seen what’s in Preview 6 too and man that will seriously rock.
So I thought I’d write a little something to celebrate the new preview. The new features include recursive templates, which is pretty much begging us to implement a treeview with it, and we’ll do just that in this post.
There is also an intriguing capability, which enables you to dynamically set what template to render for each data item, and where to render it. At first, this doesn’t look like the most useful thing in the world, but it actually opens up some very interesting possibilities, which we’ll also show in this post.
The sample code that I’m going to write for this post is a rudimentary class browser. It will render a treeview representing the hierarchical structure of namespaces and classes in Microsoft Ajax, and clicking one of the tree nodes will render a details view for it: a list of classes and subnamespaces for namespaces, and a grouped list of members for classes.
Let’s start with the tree. It will be rendered as nested unordered lists by a simple recursive DataView:
<ul id="tree" class="tree" sys:attach="dataview" dataview:data="{{ Type.getRootNamespaces() }}" dataview:itemtemplate="nodeTemplate" dataview:oncommand="{{ onCommand }}"> </ul> <ul id="nodeTemplate" class="sys-template"> <li> <a href="#" onclick="return false;" sys:command="select"> {{ getSimpleName($dataItem.getName()) }} </a> <ul sys:attach="dataview" dataview:data="{{ getChildren($dataItem) }}" dataview:itemtemplate="nodeTemplate" dataview:oncommand="{{ onCommand }}"></ul> </li> </ul>
On the first UL, which is the outer DataView for the tree, you can see that we set the data property to Type.getRootNamespaces(), which returns the set of root namespaces currently defined.
We also set the template to point to the “nodeTemplate” element, which has to be outside the DataView itself when doing recursive templates. Note that the outer node of the template, the UL, won’t actually get rendered into the target ul (tree). It is only a container.
The command event of the DataView is hooked to the onCommand function, and we’ll get back to that when we couple the tree with the detail view.
In the template itself, you can see we have a link with the select command so that clicking it will trigger the nearest onCommand event up the DOM.
The text of that link is the results of a call to getSimpleName, which will extract the last part of the fully-qualified name of the namespace or class.
After that link, we find another DataView control. The data property of that control points to an array of namespaces and classes under the current object. But the nice part here is that the template property points to “nodeTemplate”, its own parent, enabling the recursive nature of the tree.
In other words, we’ve morphed a simple DataView control into a tree, with minimal effort and code.
There is just one thing missing to the tree, and that is the +/- buttons that will collapse and expand the tree nodes. This is actually very easy to set-up using CSS and some simple script. First, let’s collapse the tree by default. This is done by defining the style of the tree as follows in our stylesheet:
.tree ul { padding: 0; display:none; }
This has the effect of collapsing all unordered list nodes under the tree.
The +/- button is created by adding the following to the template, right before the existing link:
<a class="toggleButton" href="#" sys:if="Type.isNamespace($dataItem)" onclick="return toggleVisibility(this);">+</a>
The button is a simple link whose rendering is conditioned by whether the current data item is a namespace: only namespaces can be expanded, classes are leaf nodes.
The toggling function itself is fairly simple:
function toggleVisibility(element) { var childList = element.parentNode
.getElementsByTagName("ul")[0], isClosed = element.innerHTML === "+"; childList.style.display =
isClosed ? "block" : "none"; element.innerHTML = isClosed ? "-" : "+"; return false; }
This just toggles the display style of the first child UL between none and block, and the text of the link between + and –.
So there it is, we have built a simple tree by simply making use of the recursive capabilities of DataView and some very simple script.
Before we look at the details view, let’s look at the code that gets called when the user selects a node in the tree:
function onCommand(sender, args) { var dataItem = sender.findContext(
args.get_commandSource()).dataItem; $find("details").set_data(dataItem); }
That code gets a reference to the data item for the selected node from the template context that we can get from the sender of the event (the inner DataView that contains the selected node) using the command source as provided by the event arguments (that source is the element that triggered the command). We can then set the data of the details DataView to that data item, which will trigger that view to re-render.
Now let’s build the details view. The details view will display the child namespaces and classes if a namespace is selected in the tree, and the properties, events and methods (instance and static) in the case of a class.
For each case, we’ll use a different template: “namespaceTemplate” for namespaces, and “classTemplate” for classes, but we’ll do so from the same DataView. This dynamic template switching is done by handling the onItemRendering event of the DataView:
function onDetailsRendering(sender, args) { var dataItem = args.get_dataItem(); args.set_itemTemplate(Type.isNamespace(dataItem) ? "namespaceTemplate" : "classTemplate"); }
This code gets the data item from the event arguments and sets the itemTemplate property depending on its type.
Each of these two templates will have to display the contents of the selected object. But, and that will be the tricky part, we want all those to be neatly grouped into separated lists.
One way to do that would be to have one DataView per list but where would the fun be in that? Here, we are going to enumerate only once through the data items to display and dispatch them dynamically to this or that placeholder depending on their nature.
Once more, the key to doing that will be handling the onItemRendering event:
function onNamespaceChildRendering(sender, args) { if (Type.isClass(args.get_dataItem())) { args.set_itemPlaceholder("classPlaceHolder"); } }
This code is simply changing the rendering place holder for the curent item from the default (the DataView’s element) to “classPlaceHolder” if the current data item is a class (instead of a namespace). The template itself looks like this:
<div id="namespaceTemplate" class="sys-template"> <h1>{{ $dataItem.getName() }}</h1> <div class="column"> <h2>Namespaces:</h2> <ul sys:id="namespacePlaceHolder" sys:attach="dataview" dataview:data="{{ getChildren($dataItem) }}" dataview:itemtemplate="namespaceChildTemplate" dataview:onitemrendering=
"{{ onNamespaceChildRendering }}"> </ul> </div> <div class="column"> <h2>Classes:</h2> <ul><li sys:id="classPlaceHolder"></li></ul> </div> </div> <ul id="namespaceChildTemplate" class="sys-template"> <li>{{ $dataItem.getName() }}</li> </ul>
As you can see, there really is only one DataView in there, and thanks to the code above, it can dispatch its rendering to different places if necessary. The template for the items of that DataView happens to be the same in all cases (namespaceChildTemplate) but it could be easily different, as it was for the parent details view.
The template for displaying classes is essentially the same thing, but with four placeholders instead of two.
So here’s what it looks like in the end:
Key takeaways of this post are that it’s now super-easy to render hierarchical data structures with DataView, and that you can do some interesting grouping of data on the fly by handling the item rendering event.
You can play with the class browser live here:
http://boudin.vndv.com/AjaxPreview5Tree/default.htm
And you can download the code here:
http://weblogs.asp.net/blogs/bleroy/Samples/AjaxPreview5Tree.zip
Microsoft Ajax 4.0 Preview 5:
http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=32770
Jim and Dave’s posts on Preview 5:
http://weblogs.asp.net/jimwang/archive/2009/09/11/asp-net-ajax-preview-5-and-updatepanel.aspx
http://weblogs.asp.net/infinitiesloop/archive/2009/09/10/microsoft-ajax-4-preview-5-the-dataview-control.aspx