ColdFusion 8 - Tapping Into the Power of the YUI
Under the hood ColdFusion 8 uses the Yahoo! User Interface Library (YUI) for a lot of its cool new AJAX UI elements. That is great news for CF developers because the YUI is very well documented and has lots of excellent examples. Using these examples and documentation we can easily extend the capabilities of ColdFusion's new AJAX components beyond what Adobe has built for us.
For this post I'm going to show how easy it is to add context menus to the new AJAX version ColdFuison 8's cftree. This is basically a combination of an example on page 915 of the ColdFusion 8 Developer's Guide, Populating the tree using a bind expression (Example 1), and the "Adding A Context Menu To A TreeView" example from the YUI site. (Thanks to HostMySite you can see a working example here.)
First some code. We have two files, the first of which uses cfform to create a tree control:
<cfajaximport tags="cfmenu" />
<html>
<head>
<title>Example: Adding a Context Menu to a Tree View</title>
<script language="JavaScript">
YAHOO.example.onTreeViewAvailble = function() {
var oContextMenu, // The YAHOO.widget.ContextMenu instance
oCurrentTextNode = null; // The YAHOO.widget.TextNode instance whose "contextmenu" DOM event triggered the display of the context menu
function editNodeLabel() {
var sLabel = window.prompt("Enter a new label for this node:", oCurrentTextNode.getLabelEl().innerHTML);
if(sLabel && sLabel.length > 0) {
oCurrentTextNode.getLabelEl().innerHTML = sLabel;
}
}
function onTriggerContextMenu(p_oEvent, p_oMenu) {
/*
Returns a TextNode instance that corresponds to the DOM
element whose "contextmenu" event triggered the display
of the context menu.
*/
function GetTextNodeFromEventTarget(p_oTarget) {
if(p_oTarget.tagName.toUpperCase() == "A" && p_oTarget.className == "ygtvlabel") {
return ColdFusion.objectCache[p_oTarget.id];
} else {
if(p_oTarget.parentNode) {
return GetTextNodeFromEventTarget(p_oTarget.parentNode);
}
}
}
var oTextNode = GetTextNodeFromEventTarget(this.contextEventTarget);
if(oTextNode) {
oCurrentTextNode = oTextNode;
} else {
this.cancel();
}
}
oContextMenu = new YAHOO.widget.ContextMenu(
"mytreecontextmenu",
{
trigger: "t1",
lazyload: true,
itemdata: [
{ text: "Edit Node Label", onclick: { fn: editNodeLabel } }
]
}
);
oContextMenu.triggerContextMenuEvent.subscribe(onTriggerContextMenu, oContextMenu, true);
};
YAHOO.util.Event.onAvailable("t1", YAHOO.example.onTreeViewAvailble);
ColdFusion.Tree.loadNodes = (function (old) {
return function (nodesArray, params) {
var tempNode;
if (typeof old == 'function') old.apply(this,arguments);
var tree = ColdFusion.Tree.getTreeObject('t1');
// now we are going to loop over the tree nodes and put them in a cache
for (var i = 0; i < YAHOO.widget.TreeView.nodeCount; i++){
tempNode = tree.getNodeByIndex(i);
if (tempNode) {
ColdFusion.objectCache[tempNode.labelElId] = tempNode;
}
}
};
})(ColdFusion.Tree.loadNodes);
</script>
</head>
<body>
<h1>Example: Adding a Context Menu to a Tree View</h1>
<cfform name="testform">
<cftree name="t1" format="html">
<cftreeitem bind="cfc:makeTree.getNodes({cftreeitemvalue},{cftreeitempath})">
</cftree>
</cfform>
</body>
</html>
The second is a simple component which returns nodes for our tree:
<cfcomponent>
<cffunction name="getNodes" returnType="array" output="no" access="remote">
<cfargument name="nodeitemid" required="true" />
<cfargument name="nodeitempath" required="true" />
<cfset var nodeArray = ArrayNew(1) />
<cfset var element = StructNew() />
<cfset var i = "" />
<!--- the initial value of the top level is the empty string --->
<cfif nodeitemid IS "">
<cfset nodeitemid =0>
</cfif>
<!--- create a array with elements defining the child nodes --->
<cfloop from="1" to="#RandRange(1,4)#" index="i">
<cfset StructClear(element) />
<cfset element.value = "#nodeitemid#.#i#" />
<cfset element.display = "Node #element.value#" />
<cfset element.expand = "false" />
<cfset element.href = "index.cfm" />
<cfset element.leafnode = "false" />
<cfset element.target = "_blank" />
<cfset nodeArray[i] = Duplicate(element) />
</cfloop>
<cfreturn nodeArray />
</cffunction>
</cfcomponent>
So what is going on here? Well first we use the cfajaximport tag to import the menu related JavaScript libraries. We do this because we are not using the cfmenu tag on this page, so we need to tell ColdFusion to load these libraries for us. (Note that because we are not using any ColdFusion menu features we don't really need cfmenu.js, one of the files cfajaximport loads. In this case it may actually be better to load the necessary libraries manually but I wanted to keep the example simple as possible so went ahead and used the cfajaximport tag.)
Next we have the "custom" code in the JavaScript block needed to add the context menu to the tree. Most of this code is straight from the Yahoo! example. I've removed the JavaScript code which generates the and populates the tree as we are relying on the cftree tag to generate our tree and our maketree CFC for tree values. I've also removed a couple of the context menu items and their associated functions just to keep the example simple.
Toward the bottom of the JavaScript block is the only real custom code I wrote for this example, a JavaScript closure which I use to overwrite the default ColdFusion.Tree.loadNodes function. ColdFusion.Tree.loadNodes is actually the callback function which gets called everytime we get data back from our makeTree CFC. This function uses the data returned from the CFC to create new YAHOO.widget.TextNode instances which it then inserts into the tree. The Yahoo! example expects the the TextNode objects used to populate the tree to be cached in an object called oTextNodeMap. For some reason Adobe decided not to cache the TextNode objects created in ColdFusion.Tree.loadNodes, so that is what this function does. Looking at the code, it first calls the original loadNodes function defined in the Adobe library. Next it gets the tree, which is an instance of the YAHOO.widget.TreeView object, using the built in getTreeObject function. Finally it loops over every node in the tree and adds them to the cache. Note that it uses the existing ColdFusion.objectCache here instead of the oTextNodeMap object for caching. The Adobe libraries create this object so I decided to go ahead and use it rather than creating a separate caching object for our TextNodes.
Finally we have the cfform and cftree tags which are taken straight from the ColdFusion documentation. The cftreeitem tag uses the bind attribute which allows our tree to be populated via AJAX calls to the makeTree.cfc.
So that is it. With just a few lines of custom JavaScript, most of which was provided for us by an existing YUI example, we were able to add context menus to the html tree generated by the cftree tag.
If you want to find out more about what the YUI can do beyond what has been pre-built into CF 8 definitely check out the YUI site.

missing variable name
function editNodeLabel()
var oContextMenu, // The YAHOO.widget.ContextMenu instance
oCurrentTextNode = null; // The YAHOO.widget.TextNode instance...
To fix the error you are seeing either remove the JavaScript comments, or break the statement up into two lines as displayed.