Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy

BoudinFatal's Gamercard

Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

JavaScript class browser: once again with jQuery

(c) 2004 Bertrand Le Roy I’ve already posted twice about that little class browser application. The first iteration was mostly declarative and can be found here:
http://weblogs.asp.net/bleroy/archive/2009/09/14/building-a-class-browser-with-microsoft-ajax-4-0-preview-5.aspx

The second one was entirely imperative and can be found here:
http://weblogs.asp.net/bleroy/archive/2009/10/15/entirely-unobtrusive-and-imperative-templates-with-microsoft-ajax-4-preview-6.aspx

This new version builds on top of the code for the imperative version and adds the jQuery dependency in an attempt to make the code leaner and simpler. I invite you to refer to the imperative code (included in the archive for this post) and compare it with the jQuery version, which shows a couple of ways the Microsoft Ajax Library lights up when jQuery is present.

The first thing I want to do here is convert the plain function I was using to build the browser’s namespace and class tree into a jQuery plug-in:

$.fn.classBrowserTreeView = function (options) {
  var opts = $.extend({},
$.fn.classBrowserTreeView.defaults,
options); return this; };

That plug-in will have two options: the data to render (which will default to the root namespaces in the Microsoft Ajax Library), and the node template selector (which will default to “#nodeTemplate”):

$.fn.classBrowserTreeView.defaults = {
  data: Type.getRootNamespaces(),
  nodeTemplate: "#nodeTemplate"
};

For the moment, as you can see, this plug-in does nothing. We want it to create a DataView control on each of the elements of the current wrapped set. We will do this by calling into the dataView plug-in.

You may be wondering where this plug-in might come from. Well, that’s the first kind of lighting up that the Microsoft Ajax Library’s script loader (start.js) will do in the presence of jQuery: every control and behavior will get surfaced as a jQuery plug-in, and components will get added as methods on the jQuery object. This is similar to what I had shown a while ago in this post, only much easier:
http://weblogs.asp.net/bleroy/archive/2009/05/04/creating-jquery-plug-ins-from-microsoftajax-components.aspx

For example, we can write this in our own plug-in to create DataView components over the current jQuery wrapped set:

return this.dataView({
  data: opts.data,
  itemTemplate: opts.nodeTemplate,
});

Now we can wire up the itemRendered event of the data view and start enriching the markup that the DataView control rendered for each data item. First, let’s get hold of the nodes in the rendered template and wrap them:

var elt = $(args.nodes);

Then, if the current node is representing a namespace, we want to hook up the expansion button’s click event so that it toggles visibility of the list of children, and we want to “unhide” the button itself (it has a “hidden” class in the default markup):

if (Type.isNamespace(args.dataItem)) {
  elt.find(".toggleButton").click(function (e) {
    e.preventDefault();
    return toggleVisibility(this);
  }).removeClass("hidden");
}

You can see here that we’re taking advantage of chaining.

Next thing is to set-up the node link itself. We start by inhibiting the link’s default action. Then we set the text for the link, and finally we set the command that will bubble up to the DataView when the link gets clicked:

elt.find(".treeNode").click(
function (e) {
e.preventDefault();
return false;
}) .text(getSimpleName(args.dataItem.getName())) .setCommand("select");

Here, I’m using a small plug-in to set the command:

$.fn.setCommand = function (options) {
  var opts = $.extend({},
$.fn.setCommand.defaults, options); return $(this).each(function () { $.setCommand(this,
opts.commandName,
opts.commandArgument,
opts.commandTarget); }); } $.fn.setCommand.defaults = { commandName: "select", commandArgument: null, commandTarget: null };

I’m using $.setCommand here, which does get created by the framework for me, but I still need to create that small plug-in to make it work on a wrapped set instead of a static method off jQuery. I’ve sent feedback to the team that setCommand and bind should get created as plug-ins by the framework and hopefully it will happen in a future version.

The last thing we need to do here is to recursively create the child branches of our tree:

elt.find("ul").classBrowserTreeView({
    data: getChildren(args.dataItem)
});

This just finds the child UL element of the current branch and calls our plug-in on the results with the children namespaces and classes as the data.

And this is it for the tree, we can now create it with this simple call:

$("#tree").classBrowserTreeView();

The details view rendering will only differ in minor ways from the code we had in our previous imperative version. The only differences is the use of jQuery to traverse and manipulate the DOM instead of the mix of native DOM APIs and Sys.get that we were using before. For example,

args.get("li").innerHTML =
args.dataItem.getName ?
args.dataItem.getName() :
args.dataItem.name;

becomes:

$(args.nodes).filter("li").text(
args.dataItem.getName ?
args.dataItem.getName() :
args.dataItem.name);

Notice how jQuery’s text method makes things a little more secure than the innerHTML we had used before.

Updating the details view with the data for the item selected in the tree is done by handling the select command of the tree from the following function:

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); var detailsChild =
Sys.Application.findComponent("detailsChild"); detailsChild.onItemRendering =
isClass ?
onClassMemberRendering :
onNamespaceChildRendering; detailsChild.onItemRendered =
onDetailsChildRendered; detailsChild.set_data(childData); $("#detailsTitle").text(dataItem.getName()); $(".namespace").css(
"display",
isClass ? "none" : "block"); $(".class").css(
"display",
isClass ? "block" : "none"); $("#details").css("display", "block"); } }

Not much change here from the previous version, again, except for the use of jQuery and some chaining.

And that is pretty much it. I’ve made other changes in the script to make use of the new script loader in the Microsoft Ajax Library but that will be the subject of a future post.

Hopefully, this has shown you how the Microsoft Ajax Library can light up with jQuery. The automatic creation of plug-ins feels very much like native jQuery plug-ins and brings all the power of client templates to jQuery. Once we have bind and setCommand plug-ins as well, the Microsoft Ajax Library may become a very useful tool to jQuery programmers just as much as jQuery itself is a very useful tool to Microsoft Ajax programmers.

The code can be found here:
http://weblogs.asp.net/blogs/bleroy/Samples/ClassBrowserWithjQueryUpdated.zip

Update: fixed a problem in Firefox & Chrome.

Comments

RichardD said:

This works in IE, but not in Firefox. The first two versions (default.htm and declarative.htm) work in both. I don't get any script errors, but I don't get any UI either.

# October 30, 2009 2:42 PM

Bertrand Le Roy said:

@Richard: thanks, this is now fixed.

# October 30, 2009 3:31 PM

Joe Chung said:

Great example!  I wasn't getting how the jQuery integration worked from previous things I've read and seen about the new Microsoft Ajax library, but now I get it.  Thanks!

# October 31, 2009 4:00 PM