Continuing on with my tour of the new AJAX UI controls in ColdFusion 8, today I want to talk about menus. It seems like I've been building DHTML menus since before the dinosaurs walked the Earth, so it's nice to see this made a heck of a lot easier in ColdFusion 8. Let's get into it.
At the simplest level, menus are made with the cfmenu and cfmenuitem tags:
<cfmenu bgColor="##c0c0c0">
<cfmenuitem display="Item One" href="http://www.adobe.com" />
<cfmenuitem display="Item Two" href="http://www.cnn.com" />
</cfmenu>
This example creates a menu with two items. One that will show "Item One", and link to Adobe, and another one labeled "Item Two" that will link to CNN. I supplied an optional background color. By default the menu will be a normal horizontal menu. This creates the menu you see here. (I put some text under the menu so you could see it in context.)
How do you create sub menus? Just nest the menu items!
<cfmenu bgColor="##c0c0c0">
<cfmenuitem display="Adobe" href="http://www.adobe.com" />
<cfmenuitem display="Products">
<cfmenuitem display="ColdFusion" href="http://www.adobe.com/go/coldfusion" />
<cfmenuitem display="Flash" href="http://www.adobe.com/go/flash" />
</cfmenuitem>
</cfmenu>
This example creates a submenu under a menu named Products. You can see this in action here.
How about a vertical menu? Just add a type to the cfmenu tag:
<cfmenu bgColor="##c0c0c0" selectedItemColor="##ff0000" type="vertical" width="200">
I added a few things here. First the type="vertical". Then notice I also specified a highlight color for the menu. I think the red goes great with the gray. Next - since this isn't a menu going across the page, I supplied a width. I put this into a table and you can see the result here.
Another option - you can supply images to go along with your menu items. These should (obviously) be smaller sized images. I'm using a Silk Icon in the next example:
<cfmenuitem display="ColdFusion" href="http://www.adobe.com/go/coldfusion" image="page_white_coldfusion.png" />
Another modification you can make is to add dividers. This would be useful for breaking up a long menu into sections:
<cfmenuitem divider="true" />
Demo here.
So thats a quick look at cfmenu. I didn't cover everything (that's what docs are for!), but hopefully this will give you a taste of the control.
Archived Comments
I played with it yesterday while watching Tiger miss putts and noticed a couple of minor things. If you see something that I overlooked in the docs, let me know.
The images are set as css background images. On a horizontal menu with more than a few items and images, the text may overlap the image some. You can over-ride the width by adding css for the name of the item, such as cfmenuitem name="foo" would be #foo {width:120px;}
I couldn't get the text to right or center align with the cfmenu style. Had to add in css for the element such as #foo {text-align:right}
On large menus, the menu shows up in vertical order then 'flashes' to horizontal. Think maybe this is lazyload but not sure.
Of you have a large amount of sub-menu items, the scrolling is slow.
And lastly, I see that showdelay is set to 200ms. Do you know how I can change that after the page is loaded?
Very nice feature. Too bad it's nearly too ugly in Opera to use :(
@ScottP - not sure. File an ER? :)
@Thomas - Ditto - file an ER. Adobe _does_ listen to bug reports.
basic HTML markup problems with the generated content. This isn't valid:
<div id="_cf_menu1182244792322"
because all ids have to start with a letter.
Will do - think there is a already some of those on file but I'll dbl-check.
Can these new menus in CF8 be built dynamically, for example, with data pulled from a query?
I had no problem making dynamic menuitems.
Is it me or do the menus seem to have odd hide/display issues. I was playing around with submenus and the hide/display on them just does not seem consistant.
--Dave
I didn't see that - but as always - file a bug. :)
It looks like the target attribute doesn't work correctly. When I add target="_blank" so that it will open a new window, the new window gets displayed behind the calling window.
You can't use cfmenu/cfmenuitem with a recursive technique like those you reinvoke a .cfm file (using customtag or cfmodule) to build a multiple item menu.
Also, on IE (so far I found IE7), you can't finish a cfmenuitem block with a "cfmenuitem divider=true", it will throw a JavaScript error. You must finish with a "content" cfmenuitem. On Firefox, Safari and Opera runs fine.
changing the height of the menu seems to be a bit of a pain.
i tried wrapping it in a div and it seems to work fine in firefox, but not in ie:
<div style="height: 30px;line-height:30px;">
<cfmenu bgColor="##c0c0c0">
<cfmenuitem display="Adobe" href="http://www.adobe.com" />
<cfmenuitem display="Products">
<cfmenuitem display="ColdFusion" href="http://www.adobe.com/go/col..." />
<cfmenuitem display="Flash" href="http://www.adobe.com/go/flash" />
</cfmenuitem>
</cfmenu>
</div>
Hi Ray, I have a web site using frameset. Could I manage to use cfmenu in the menu frame and post content in the content frame? Where could I found example/reference for this? Is this is not a good idea?
Thanks.
Bill
I haven't tried, but the links for cfmenu can be _any_ link afaik, so you should be able to target the frame.
I just checked - cfmenuitem includes a target attribute.
So there ya go. :)
Thanks Ray. It works fine. Appreciate your help.
Hi, how are you?
When using multiple cfmenus on a page, I've noticed that an additional carot is displayed (when displaying submenus) for each additional menu that is used. For example, in the following, there will be 2 carots in the top menu and one in the bottom. If I were to add a 3rd menu, there would be 3 carots in the top menu, and so on...
<cfmenu name="menu" type="horizontal" bgcolor="##015A9C">
<cfmenuitem name="menuitem" display="Home" href="http://www.adobe.com" />
<cfmenuitem name="Bagels" href="http://www.adobe.com" display="Bagels">
<cfmenuitem name="Raisin" href="http://www.adobe.com" display="Raisin" />
<cfmenuitem name="Garlic" href="http://www.adobe.com" display="Garlic" />
</cfmenuitem>
</cfmenu>
<cfmenu name="menu2" type="horizontal" bgcolor="##015A9C">
<cfmenuitem name="menuitem2" display="Home" href="http://www.adobe.com" />
<cfmenuitem name="HotDogs" href="http://www.adobe.com" display="Hot Dogs">
<cfmenuitem name="Nathans" href="http://www.adobe.com" display="Nathans" />
<cfmenuitem name="Sabrett" href="http://www.adobe.com" display="Sabrett" />
<cfmenuitem name="HebrewNational" href="http://www.adobe.com" display="Hebrew National" />
</cfmenuitem>
</cfmenu>
Does anyone know where I can modify this? We have people who don't like the additional carots and would like to limit them to just one no matter how many menus are on the page. Thanks for any help you can give.
Nevermind...I found the stylesheet
Ray,
I have a mysql table to use for a menu that has an ID and parentID. For instance, I have a menu item called "Sports" and below it would be "Football". Football's parentID would be Sport's primary key ID. Football may also have items under it, such as "Playoffs", "Regular Season", etc. Those would then have the parentID of Football's primary key ID. Basically making the menu ever expandable.
I want to use cfmenu to build a horizontal menu, but have ran into a brick wall. Any ideas?
You would need to use recursion. Write a function that gets the top level items, then the children, and so on, and so on.
@Chris
<!--- MENU GENERATOR --->
<!--- GET THE MENU TO DISPLAY --->
<!--- GET MENU ATTRIBUTES --->
<cfset menu = StructNew() >
<cfset menu.typeid = "mainmenu">
<!--- GET PARENT MENU DETAILS --->
<cfquery datasource="#application.datasource#" name="getMenu">
SELECT * FROM menu WHERE
menutype = <cfqueryparam value="#menu.typeid#"> ORDER by order_id asc;
</cfquery>
<!--- CREATE THE MENU --->
<cfmenu bgColor="##eeeeee" selectedItemColor="##ffffff">
<!--- LOOP OVER MENU PARENT --->
<cfloop query="getMenu" startrow="1" endrow="1000">
<cfmenuitem display="#title#" href="http://www.google.com">
<!--- CHECK TO SEE IF THIS MENU HAS A SUBMENU THIS WOULD BE TIED FROM PARENT IN THE DATABASE
TO THE PARENTS MENU ID --->
<!--- GET SUB MENU QUERY --->
<cfquery datasource="#application.datasource#" name="getSubMenu">
SELECT * FROM menu WHERE
menutype = <cfqueryparam value="sub.#menu.typeid#"> ORDER by order_id ASC;
</cfquery>
<!--- LOOP OVER THE RESULTS SO WE CAN RENDER THE SUB MENU --->
<cfloop query="getSubMenu" startrow="1" endrow="1000">
<cfif getSubMenu.parent eq getMenu.id >
<cfmenuitem display="#title#" href="http://www.adobe.com/go/flash"
image= "http://farm1.static.flickr...." />
</cfif>
</cfloop>
</cfmenuitem>
</cfloop>
</cfmenu>
@Ray,
I have tried a few attempts to get recursion to work with cfmenu/cfmenuitem. One of the main problems is that you can't have a <cfmenuitem> tag unless it is inside of a <cfmenu> or another <cfmenuitem> tag. So, you can't really have a recursive function that creates child cfmenuitem tags as the CF parser will throw this error:
"The tag CFMENUITEM must have parent cfmenu or cfmenuitem tag."
I have tested my code by replacing the cfmenu/cfmenuitem tag output with <cfmenu and <cfmenuitem, so it is simply displayed the tags. I pasted the output into a template and it works perfectly when I run it. I don't want to have to create cfm template files on the fly using code like this.
I also tried to embed the 'level' into the data so that the code steps into the cfmenu tag tree each time the level increases and steps back out the correct number of </cfmenuitm> tags. So, there was no recursion, just calculating when and how many closing tags to use. This does not work either, the CF parser throws an error on the 'extra' closing tags it thinks are there. Again, I replaced the output cfmenu/cfmenuitem tags with html escaped values and when I pasted the output into a template file and ran it, it worked pefectly.
@Jody,
Nice example for a two level menu. How would you make it work for variable levels? For example one menu may have 3 descendants and another may have 9. I've tried this and once you get into your 4th internal loop, it is pretty messy and once you set your limit, someone will always ask for just one more level.
So far I'm inclined to agree with @Alex Hubner. Recursion doesn't work with cfmenu/cfmenuitem.
I'm trying to use an example of cfmenu to direct content into a div. The code example I have is
<cfajaximport tags="CFMENU, CFGRID, CFFORM"/>
<cfmenu name="nav" type="horizontal" bgcolor="##336699" fontcolor="##fff">
<cfmenuitem display="Page One"
href="javaScript:ColdFusion.navigate('cfgridtesting.cfm','myDiv')" style="color:##fff;">
</cfmenuitem>
<cfmenuitem display="Page Two"
href="javaScript:ColdFusion.navigate('cfgridtesting1.cfm','myDiv')" style="color:##fff;">
</cfmenuitem>
</cfmenu>
<cfdiv id="myDiv" bind="url:cfgridtesting.cfm">
</cfdiv>
The problem is that the original cfgrid page works but when I hit the links in the menu, I get an error: Error processing JavaScript in markup for element myDiv: [Enable debugging by adding 'cfdebug' to your URL parameters to see more information]
I really don't understand why.
So what's the error? Did you try adding cfdebug or checking the logs?
It just says "Error processing JavaScript in markup for element myDiv: [Enable debugging by adding 'cfdebug' to your URL parameters to see more information]"
How do i use cfdebug, if I put ?cfdebug after my url, it does nothing. Seems like I remember that cfdebug isn't supported by hostek.com.
If you use a tool like Firebug, or Chrome's Developer Tools, you can monitor the Ajax request and see the error there.
Firebug says:
"Firebug's log limit has been reached. 0 entries not shown. Preferences
uncaught exception: [Exception... "'[JavaScript Error: "CFMessage is not defined" {file: "http://www.botdls.com/CFIDE..." line: 429}]' when calling method: [nsIDOMEventListener::handleEvent]" nsresult: "0x80570021 (NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS)" location: "native frame :: <unknown filename> :: <TOP_LEVEL> :: line 0" data: yes]"
Look at Firebug's Net tab. It will tell you what the server result is. If it is online, let me know and I'll look.
The net tab doesn't show anything. All the other tabs like DOM and stuff list all the info. The weird thing is that when you click on the tab, and click ok on the alert, the page just acts like it's loading forever.
Well, is this somewhere I can see?
http://botdls.com/codetesti...
Sorry it took me so long, been busy.
For me it fails immediately. I noticed you are using a Flash Form grid. If you get rid of that, replace it with some static content. Do things work ok?
Ok this is weird. If I use "page one" and "page two" files as both flash it fails. If I change the bind for "page one" to a HTML grid, then it works and "page two" fails. Is this a glitch in flash binding?
Not sure - but there is a reason why I don't recommend Flash Forms to anyone. ;)
Thank you so much for your article and all the comments on <cfmenu> and <cfmenuitem>. This was the most helpful website I have found.
I had been beating my head against a wall on a problem with my menu. The testers noticed that the full width of a menu item was not "clickable", ie. you have to hover and click on the text, not on the space to the right of the text. If one menu item's text is much longer than the others, it is more apparent. Using examples from comments above, I checked to make sure I wasn't missing something. I noticed in the Hot dog example above, that the space to the far right WAS clickable, and behaving as one would expect! What was different about mine? Finally it occurred to me! My links were using the target="_blank" option. Sure enough, if I take the Hot dog example, and add the target option, that menu item is no longer clickable on the far right!
<cfmenu name="menu2" type="horizontal" bgcolor="##99cbfe" selectedfontcolor="##0000dd" selecteditemcolor="##FF9900">
<cfmenuitem name="menuitem2" display="Home" href="http://www.adobe.com" />
<cfmenuitem name="HotDogs" href="http://www.adobe.com" display="Hot Dogs">
<cfmenuitem name="Nathans" href="http://www.adobe.com" display="Nathans" />
<cfmenuitem name="Sabrett" href="http://www.adobe.com" display="Sabrett" target="_blank" />
<cfmenuitem name="HebrewNational" href="http://www.adobe.com" display="Hebrew National" />
</cfmenuitem>
</cfmenu>
Do you get the same results I do? If this is a bug, where do I go to report it?
Thanks!
Sounds like a bug to me. I'd report it here: http://cfbugs.adobe.com/cfb...
Perhaps I've missed it, but is there a way to add a tabindex or something else to make these menus keyboard accessible?
Update to my last comment. . . . It automatically tabs through fine in IE 6/7, but not Firefox 3.6 (Yes I have to test on a newer version of Firefox, but backwards compatibility still important).
Not as far as I know- there's no options in the cfmenu tag that I see.