Entirely unobtrusive and imperative templates with Microsoft Ajax Library Preview 6

(c) 2009 Bertrand Le Roy 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:The JavaScript class browserhttp://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

9 Comments

  • This is great, I have been using the other versions imperatively but with a lack of tutorials using the imperative way, I was stuck using Fire Bug to decipher the method names and what they do. Now I imagine all the examples will be imperative, good news.

    It is so much better having the code separated from the html.

  • The # symbol is used in the Sys.create.dataview("#destinationDiv", {data settings} )

    Why was the $get("element") replaced with #element when used as a parameter to the Sys.create.dataview ?

    How to set the Events,References with Sys.create.dataview?

    Does Sys.create.dataView and $create(Sys.UI.dataView) create the same exact object?

    Does the Sys.create.dataview expose any new properties, methods or new functionality?

    And is it the same as the Object created with Preview 5?

    Thank you,
    William Apken

  • @WMA4432: $get still takes a plain id. Sys.get takes a CSS selector, hence the #.
    In create.dataView, you can put properties, fields and event in the same parameter object. For example, to wire up the command event, just do command: function(sender, args) {...}.
    Sys.create and $create create exactly the same objects, but Sys.create can be used even if the script defining the component hasn't been loaded yet: it will then load it, and when it's done create the object.

  • Question for you, might interest other devs around here...
    I have a process that generate and publish JS widgets, and right now it goes thru a JVM to minify the output. Is the code of your minifier shared? Can I include it in my publish?

    Thanks,
    Regis.

    a couple of random notes: love the pic, pre.code not really comfortable to read in chrome, font too small :)

  • @Regis: no, the code for the minifier is not shared.
    I use Chrome myself. If you find the font too small, I recommend a CTRL +.

  • @Joe: this is a sample that only makes sense if JavaScript is enabled (it's browsing the *client-side* object model). It is not made to be indexed by search engines.
    If you want something that is SEO, there is no going around the solid fact that you need to do the rendering on the server-side. So if you need your contents to be indexed, move over, this is the wrong rendering technology. Always has been.
    Now if you hold the opinion that if it can't be indexed by Google, it's useless, that's fine, but some people have different views and use cases.
    On the point of accessibility, it's been a few years now since assistive technology embraced script.

  • @OmariO: that's interesting, I will forward it to the team. This goes against all measurements we have done on the desktop, where parsing is pretty much instantaneous even with large files. This being said, this would only help for mobile applications that also have large script files, which is a relatively small subset of applications. Barring putting it into the framework, it may be interesting to see how such a hack could be grafted on top of the existing script loader.
    Another thing to consider is that the script loader works cross-domain, which the code in that post can't do as it is (it uses XHR to load the script). Their code also has a few scope problems in the way they eval the code. Nothing that can't be worked around though. Thanks for the pointer.

  • I take the point that this is client side only but using the word unobtrusive is simply wrong here. Unobtrusive scripting is used to enhance an already working html page. I make that twice now that microsoft have mentioned unobtrusive scripting. It's a good sign but lets use it where it correctly applys.

  • hi
    every thing ajax team is doing is really appreciable
    but i think visual studio is still missing one thing that's support for js files. At least it should have code folding. Nowadays the size of js files is really large.

Comments have been disabled for this content.