Posted in ColdFusion | Posted on 12-13-2009 | 4,049 views
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:
2 <!--- alright, this is not a hacker challenge here; i expect only small, medium, or large to be passed in the URL for this demo --->
3 <cfset scaleSize = "large">
4</cfif>
5<cfset scaleSize = lcase(scaleSize) /><!--- it is case sensitive inside the js --->
6
7<cfscript>
8//NOTE: by changing [scaleSize] it retrieves the proper icons from the properly scaled icon folder
9//scaleSize = "large"; // values: small, medium, or large
10if (scaleSize EQ "small") {
11iconSize = "16"; // 16*16
12}
13else if (scaleSize EQ "medium") {
14iconSize = "24"; // 24*24
15}
16else {
17iconSize = "32"; // 32*32
18}
19</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:
2stNavBar1.title[1] = "HOME";
3stNavbar1.url[1] = "index.cfm";
4stNavbar1.iconCls[1] = "icon-menu#iconSize#-1";
5
6stNavBar1.title[2] = "SECOND BUTTON";
7stNavbar1.url[2] = "index.cfm";
8stNavbar1.iconCls[2] = "icon-menu#iconSize#-2";
9
10stNavBar1.title[3] = "SCALE THE TOOLBAR";
11stNavbar1.url[3] = "index.cfm";
12stNavbar1.iconCls[3] = "icon-menu#iconSize#-3";
13
14stNavBar2.title[3][1]= "Let's see a LARGE Toolbar";
15stNavbar2.url[3][1] = "index.cfm?scaleSize=large";
16stNavbar2.iconCls[3][1] = "icon-6-1";
17
18stNavBar2.title[3][2]= "Let's see a MEDIUM Toolbar";
19stNavbar2.url[3][2] = "index.cfm?scaleSize=medium";
20stNavbar2.iconCls[3][2] = "icon-6-1";
21
22stNavBar2.title[3][3]= "Let's see a SMALL Toolbar";
23stNavbar2.url[3][3] = "index.cfm?scaleSize=small";
24stNavbar2.iconCls[3][3] = "icon-6-1";
25</cfscript>
And here is how he looped over it to create the JavaScript:
2
3 tbar: [{
4 xtype:'buttongroup',
5 hideBorders:'true',
6 items: [
7 <cfloop index="i" from="1" to="#ArrayLen(stNavBar1.title)#">
8 <cfif i NEQ 1>,</cfif>
9 {
10 <cfif arrayLen(stNavBar2.title[i]) GT 0>
11 xtype:'splitbutton',
12 text: '<cfoutput>#stNavBar1.title[i]#</cfoutput>',
13 iconCls: '<cfoutput>#stNavBar1.iconCls[i]#</cfoutput>',
14 scale: '<cfoutput>#scaleSize#</cfoutput>',
15 handler: navigate,
16 url: '<cfoutput>#stNavBar1.url[i]#</cfoutput>',
17 menu: [
18 <cfloop index="j" from="1" to="#ArrayLen(stNavBar2.title[i])#">
19 <cfif j NEQ 1>,</cfif>
20 {
21 text: "<cfoutput>#stNavBar2.title[i][j]#</cfoutput>",
22 iconCls: "<cfoutput>#stNavBar2.iconCls[i][j]#</cfoutput>",
23 tooltip:' <cfoutput>#stNavBar2.url[i][j]#</cfoutput>',
24 url: '<cfoutput>#stNavBar2.url[i][j]#</cfoutput>',
25 handler: navigate
26 }
27 </cfloop>
28 ]
29 <cfelse>
30 text: '<cfoutput>#stNavBar1.title[i]#</cfoutput>',
31 iconCls: '<cfoutput>#stNavBar1.iconCls[i]#</cfoutput>',
32 scale: '<cfoutput>#scaleSize#</cfoutput>',
33 handler: navigate,
34 url: '<cfoutput>#stNavBar1.url[i]#</cfoutput>'
35 </cfif>
36 }
37 </cfloop>
38 ]
39}]
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:
2 {
3
4 text: 'HOME',
5 iconCls: 'icon-menu24-1',
6 scale: 'medium',
7 handler: navigate,
8 url: 'index.cfm'
9 }
10 //additional objects as needed possibly nested
11]
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.
2<cfset menuItem = structNew() />
3<cfset menuItem["text"] = "TODD" />
4<cfset menuItem["iconCls"] = "icon-menu24-1" />
5<cfset menuItem["scale"] = "medium" />
6<cfset menuItem["handler"] = "navigate" />
7<cfset menuItem["url"] = "index.cfm" />
8<cfset arrayAppend(menuArr, menuItem) />
9<cfset jArr = serializeJSON(menuArr) />
Which produces the following JSON object:
2 {
3 "scale":"medium",
4 "iconCls":"icon-menu24-1",
5 "text":"TODD",
6 "handler":"navigate",
7 "url":"index.cfm"
8 }
9]
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:
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.


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.
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.
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.
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.
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.
[Add Comment] [Subscribe to Comments]