Today's entry is cf_ext_navbar. It was submitted by Andrew Duval and reviewed by Todd Sharp. Todd's review is below in it's entirety with no editing from me. Thanks Andrew and Todd!
This contest entry is called CF_EXT_NAVBAR'and was created by Andrew Duvall and is an example of how to utilize the Ext3 JavaScript library included with ColdFusion 9. Many developers were thrilled when they learned that the Ext libraries that power the ColdFusion Ajax and UI widgets was being upgraded to the most recent version of the popular framework because there were a number of significant upgrades since the launch of ColdFusion 8 which bundled Ext 1.0. Andrew's entry demonstrates the required libraries and scripts to create a "navbar" widget.
When I first ran the entry I had the same reaction that I usually have when I see Ext widgets - "wow, that's pretty cool looking." Here's what the entry looks like:

My next step was to load up the code in ColdFusion Builder and once I got that done I loaded the entry up in the internal browser so I could have a peek at the code and the entry at the same time. Unfortunately what I saw in the internal browser wasn't so pretty:

Turns out my default browser in CF Builder was set to IE and since IE can't render HTML properly like every other browser on earth there was a bit of a problem. To be fair to Andrew, I'm sure this isn't his fault. What surprises me is that the Ext folks never caught this issue with the navbar in IE.
At this point I had the same second thought that I always do when I see Ext widgets - "I wonder how many scripts had to be imported just to create a simple navbar?" I opened up Firebug and took a look:

I was pretty shocked to see 590 KB of scripts and images for a simple navbar widget especially since my local environment uses gzip compression on scripts. The majority (519KB out of 590KB) of that bloat does come from a single file – 'ext-all-debug.js' which I'm pretty sure is the core Ext library (and is actually 2.5MB before being gzip'd). I swapped out the 'ext-all-debug.js' for the minified 'ext-all.js' and the total script size dropped down to 238 KB – much easier to swallow (but still pretty large for a simple navbar in my opinion). The fact that about 99% of the scripts and images are served from cache on subsequent page requests makes all of this a bit irrelevant if you were creating a full blown application but it's something to keep in mind if you're thinking of just dropping in a simple widget on a page somewhere.
So let's take a look at some of the code. I really would have liked if Andrew would have wrapped this up in a custom tag but since this is just a simple example I'll give him a pass on that. The first thing that caught my eye was the following – and this isn't necessarily related to ColdFusion 9, but it is a pet peeve of mine so I thought it was worth mentioning:
<cfif ListContainsNoCase('small,medium,large', scaleSize) EQ 0>
<!--- alright, this is not a hacker challenge here; i expect only small, medium, or large to be passed in the URL for this demo --->
<cfset scaleSize = "large">
</cfif>
<cfset scaleSize = lcase(scaleSize) /><!--- it is case sensitive inside the js --->
<cfscript>
//NOTE: by changing [scaleSize] it retrieves the proper icons from the properly scaled icon folder
//scaleSize = "large"; // values: small, medium, or large
if (scaleSize EQ "small") {
iconSize = "16"; // 1616
}
else if (scaleSize EQ "medium") {
iconSize = "24"; // 2424
}
else {
iconSize = "32"; // 32*32
}
</cfscript>
So he starts out with a simple logic check that sets the 'scaleSize' variable as appropriate and then he does some simple clean up to make sure the variable is lower case. So far, so good, but then he immediately jumps into a
The next part I found a bit perplexing was the fact that Andrew created a 2d array of values in ColdFusion and then manually looped over the values to create the required JavaScript to create the navbar. Here's a snippet from his code that creates the 2d array:
<cfscript>
stNavBar1.title[1] = "HOME";
stNavbar1.url[1] = "index.cfm";
stNavbar1.iconCls[1] = "icon-menu#iconSize#-1";
stNavBar1.title[2] = "SECOND BUTTON";
stNavbar1.url[2] = "index.cfm";
stNavbar1.iconCls[2] = "icon-menu#iconSize#-2";
stNavBar1.title[3] = "SCALE THE TOOLBAR";
stNavbar1.url[3] = "index.cfm";
stNavbar1.iconCls[3] = "icon-menu#iconSize#-3";
stNavBar2.title[3][1]= "Let's see a LARGE Toolbar";
stNavbar2.url[3][1] = "index.cfm?scaleSize=large";
stNavbar2.iconCls[3][1] = "icon-6-1";
stNavBar2.title[3][2]= "Let's see a MEDIUM Toolbar";
stNavbar2.url[3][2] = "index.cfm?scaleSize=medium";
stNavbar2.iconCls[3][2] = "icon-6-1";
stNavBar2.title[3][3]= "Let's see a SMALL Toolbar";
stNavbar2.url[3][3] = "index.cfm?scaleSize=small";
stNavbar2.iconCls[3][3] = "icon-6-1";
</cfscript>
And here is how he looped over it to create the JavaScript:
var mymenu=new SamplePanel({
tbar: [{
xtype:'buttongroup',
hideBorders:'true',
items: [
<cfloop index="i" from="1" to="#ArrayLen(stNavBar1.title)#">
<cfif i NEQ 1>,</cfif>
{
<cfif arrayLen(stNavBar2.title[i]) GT 0>
xtype:'splitbutton',
text: '<cfoutput>#stNavBar1.title[i]#</cfoutput>',
iconCls: '<cfoutput>#stNavBar1.iconCls[i]#</cfoutput>',
scale: '<cfoutput>#scaleSize#</cfoutput>',
handler: navigate,
url: '<cfoutput>#stNavBar1.url[i]#</cfoutput>',
menu: [
<cfloop index="j" from="1" to="#ArrayLen(stNavBar2.title[i])#">
<cfif j NEQ 1>,</cfif>
{
text: "<cfoutput>#stNavBar2.title[i][j]#</cfoutput>",
iconCls: "<cfoutput>#stNavBar2.iconCls[i][j]#</cfoutput>",
tooltip:' <cfoutput>#stNavBar2.url[i][j]#</cfoutput>',
url: '<cfoutput>#stNavBar2.url[i][j]#</cfoutput>',
handler: navigate
}
</cfloop>
]
<cfelse>
text: '<cfoutput>#stNavBar1.title[i]#</cfoutput>',
iconCls: '<cfoutput>#stNavBar1.iconCls[i]#</cfoutput>',
scale: '<cfoutput>#scaleSize#</cfoutput>',
handler: navigate,
url: '<cfoutput>#stNavBar1.url[i]#</cfoutput>'
</cfif>
}
</cfloop>
]
}]
While that certainly works I tend to think he's overcomplicating things a bit. Take a look at a sample of the generated source code:
[
{
text: 'HOME',
iconCls: 'icon-menu24-1',
scale: 'medium',
handler: navigate,
url: 'index.cfm'
}
//additional objects as needed – possibly nested
]
So Ext is basically looking for a simple array of objects. To me it would have been much simpler (and easier to read) to just create an array of structs in CF and serialize it as JSON. Here's one way that might have looked. Remember that you'll need to use associative array, or 'bracket' notation to keep CF from changing the case of your struct keys when serializing.
<cfset menuArr = arrayNew(1) />
<cfset menuItem = structNew() />
<cfset menuItem["text"] = "TODD" />
<cfset menuItem["iconCls"] = "icon-menu24-1" />
<cfset menuItem["scale"] = "medium" />
<cfset menuItem["handler"] = "navigate" />
<cfset menuItem["url"] = "index.cfm" />
<cfset arrayAppend(menuArr, menuItem) />
<cfset jArr = serializeJSON(menuArr) />
Which produces the following JSON object:
[
{
"scale":"medium",
"iconCls":"icon-menu24-1",
"text":"TODD",
"handler":"navigate",
"url":"index.cfm"
}
]
The only issue with this version is that CF treats all the values as strings and if you notice Andrew's JSON object the value for the 'handler' key is actually a variable reference to the navigate JavaScript function. I worked around this by just replacing the quoted value "navigate" with an unquoted value in the JSON string that CF created:
<cfset jArr = replace(jArr,'"navigate"', "navigate", "all") />
Sure, it's a bit of a hack but I'm willing to accept a simple hack in exchange for not having my eyes bleed from trying to work with a 2d array.
Overall I think it was a good entry that took advantage of the Ext 3.0 features that ship with CF 9.
Archived Comments
Very nice, I was just looking at the Ext Toolbar stuff today. Ext is a very powerful library, whenever I look into it, the API always proves to be excellent.
I may say that I was trilled too when I saw that latest version of Extjs FW is bundled with CF9.
But are we going to wait for CF10 to see implementation of forecomming versions of Extjs.
Version 1.0 became "outmoded" and many developers replace it with JQuery equivalents.
Is it going to happen with this implentation too? Should I start developing/base (long-term) application on something that will not improve by time?
Btw I found that extjs 3 is very powerful UI framework and nice to work with.
IMHO, it still need some size optimization and to start using CSS-like selectors (as jquery do).
Regarding script "bloat" size. Certainly that is an issue, but we should have in mind that time is working in favor of this issue.
Bandwidths are better by time, what makes transfering of so big libraries faster (less notable).
Also, many web applications are exclusively for internal use (intranet). Performance always requires some sacrifice.
Don't forget caching.
Marko - using the built in Ajax UI stuff is always a trade off. You get the extreme ease of use but you also lose (sometimes) the extensibility and customization you would have if you did things by hand. This isn't a jQuery versus Ext issue, but simply a case of a 'static' version of a library included with CF.
What I normally tell people is this: If you have enough experience w/ Ajax where you can nitpick what ships with CF, then you are probably not the audience for something like cfwindow.
The bundled ColdFusion Ajax functionality is really good when starting out with Ajax. Thats how I started out, I got my hands wet with cfgrid, and cfwindow, etc. Once I understood some the basics working with CF8-Ajax, I was able to grasp jQuery and ExtJS much more easily (even then my lack of JavaScript knowledge still slows me down).
I'll ditto Kumar. CF8 really helped me ramp up on my Ajax skills. That and Spry as well.
I appreciate the thorough review it gives me some thoughts on improvements.
Regarding the 2D-array vs array of struct... I was just re-using a block of navigation code i had used in the past to get started. I had considered using an array of structures, but felt that although it looked cleaner; the trade-off was that the child links and consequent sub-child-links may appear in an unpredicted order; whereas the multiple dimention array approach assured the order of the subsequent child level navigation. Hoping that makes sense; though the sample for the contest was simpler than my real world usage.
Regarding the wish that this was served up as a custom tag... I had considered that, but knew that it would vary greatly from developer to developer if they populate there navigation by database, xml, or arrays and so I took the easy way out and left it NOT as a cftag.
you brought to my attention how much bloat there is and i will switch to the minified file. I use this all through-out my application so, i am relieved that it seems to cache well.
Anyway, this was a fun little project for me so that i could also feel more comfortable with EXT / AJAX solutions.
I hope this helps other CF developers that are also beginning their steps into the AJAX direction.
Ray, do you have more info on how you did your version to get around the javascript issues? On ie8, this thing falls over somewhere in there: var mymenu=new SamplePanel({
So did you loop over the group of nav of cfset there for each of your menu and submenu items? I am understanding you are replacing the javascript work, I think.
Did you mean me or Andrew? I don't use IE. :)
Neither do I, but was testing in it.
I found that setting the width causes it to blow up in IE, leaving it commented out and it worked fine.
Ray, I was talking about how you mentioned you would rewrite it a bit, and I have not used that approach before. Was more curious how you flesh out all the menu options in your approach to learn from.
Not quite sure I get what you mean. Remember most of the review was written by Todd Sharp.