Build a Dynamic Menu in ColdFusion
Static content is so yesterday. Websites, whether internal or external, are all about staying current.
To that end, on a recent internal project I created a new intranet site and opted to develop a dynamically populated menu in ColdFusion. I went this route so that managing the links didn’t require us to open up the HTML files each time new content was added. This made updating the menu much less trouble for me as well as the content owners.
The end users and I came up with a menu that could be managed easily, and took up very little space on the site. The reason why space was an issue was that the site had several embedded sections, each of which had several embedded sections, which had… you see where I’m going.
Before the Code…
We’ll start by designing the database — we can’t write code to access a database until we know how the database is designed. The database for our dynamic menu is straightforward. To accomplish our menu we only need two tables — no stored procedures or anything else (though a stored procedure would be the easiest way to manage the data in the menu), so you can use any database system you’d like.
Image 1 shows the database tables we’ll use for our menu. The
MENU_ITEMS table is where all the actual menu tree items will live.
menu_item_url) are stored along with the primary key (
menu_item_id). The table
MENU_ITEMS_XREF holds the structure of the tree itself. Both
child_id refer to the
menu_item_id from the
MENU_ITEMS table. IDs that appear in the
child_id column indicate that that ID will be nested under the item in the
menu_item_id column. Image 2 illustrates this concept.
The last column in the
MENU_ITEMS_XREF table is the
sort_order field. Initially, I just let the menu query that generates the menu handle the sorting (alphabetically). Wouldn’t you know it, though — the business unit had other ideas. So now there is a
sort_order field, which allows the content owner to place more important or popular topics closer to the top of the page.
That’s it for the database. Let’s code!
The code we use to create the menu is pretty easy to follow. Basically, we’ll create a custom ColdFusion tag that recurses over a database structure and builds the appropriate menu structure as it goes. We’ll break down the pieces of
create_menu.cfm, which you can download here, and discuss each section. The end result will be a completely de-mystified, dynamically built tree menu.
The code in Section A defines some initial parameters we’ll need as default. You may or may not need these, depending on whether your site has pages in sub folders and such. Our site did, so we needed a way to get up the folder tree to the images directory.
<CFPARAM DEFAULT="1" NAME="ATTRIBUTES.Level">
<CFPARAM DEFAULT="" NAME="ATTRIBUTES.divID">
<CFIF ATTRIBUTES.Level EQ 1>
<CFSET VARIABLES.RootLevel = "">
<CFELSEIF ATTRIBUTES.Level EQ 2>
<CFSET VARIABLES.RootLevel = "../">
<CFELSEIF ATTRIBUTES.Level EQ 3>
<CFSET VARIABLES.RootLevel = "../../">
var menu, arrowImg;
menu = document.getElementById("d" + n);
// Determine if the menu is currently showing.
if (menu.style.display == 'block')
// If it is showing, hide the menu and update the twisty image.
menu.style.display = 'none';
arrowImg = document.images['i' + n];
arrowImg.src = "#VARIABLES.RootLevel#images/right_arrow.gif";
Section C handles the event of clicking on an already-expanded menu branch. Doing this triggers the branch to collapse. The menu doesn’t have to function this way, but why require your users to select a different level just to collapse a section? Instead, we simply take the section the user clicked on and see if it’s expanded; if it is, we collapse it. If it is not expanded, move right along to Section D.
// Hide all layers first.
var divs = document.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++)
if (divs[i].id.indexOf("d0") >= 0)
divs[i].style.display = 'none';
// Reset the images.
for (var j = 0; j < document.images.length; j++)
if (document.images[j].src.indexOf("down_arrow") > 0)
document.images[j].src = "#VARIABLES.RootLevel#images/right_arrow.gif";
// Show the menus and update their twisty images.
i = 2;
while (n.length >= i)
menu = document.getElementById("d" + n.substring(0, i));
arrowImg = document.images["i" + n.substring(0, i)];
menu.style.display = "block";
arrowImg.src = "#VARIABLES.RootLevel#images/down_arrow.gif";
i += 2;
ShowMenu(). If the branch that the user selected was not already expanded, the first order of business is to collapse all branches. We essentially want to start with a fresh menu each time. To accomplish this, we loop over the structure of the image tags (all named in series), resetting each image. We repeat the process on the menu structure itself (each
<div> tag that makes up the menu is named in series like the images, so looping over the entire menu is very easy).
The Set Up
This menu is the product of a ColdFusion custom tag. Wherever you need the menu to appear (keeping in mind that this menu is vertical), you simply include the tag. To set up the menu on a page, you’ll require two lines of code. Sample 1 shows the
<body> tag as well as the actual custom tag call. The ColdFusion code in the body tag expands the menu to reflect the user’s location within the site. Without this logic the menu collapses on load by default.
<body <CFIF IsDefined('URL.Div')>
We don’t call the custom tag in the standard
<CF_> syntax; instead we call it using
<CFINCLUDE>. The tag’s designed to call itself as it builds the tree, so we just need to include the code.
Now, back to the custom tag — we’ll continue with the ColdFusion code. Section E establishes our variables and queries the database. You’ll notice the query is actually a query of a variable. You can execute this section in whichever way you are most comfortable. I chose to store my initial query as an APPLICATION scoped variable, as the menu data doesn’t change very frequently. Also, because the menu is located on each page of the site, I wanted to spare my database the extra traffic. The last query in Section E creates the application variable.
<CFPARAM DEFAULT="0" NAME="ATTRIBUTES.MenuID">
<CFPARAM DEFAULT="" NAME="ATTRIBUTES.DivID">
<CFPARAM DEFAULT="0" NAME="VARIABLES.LoopCount">
<CFPARAM DEFAULT="0" NAME="VARIABLES.IncreNum">
<CFLOCK SCOPE="APPLICATION" TIMEOUT="10" TYPE="READONLY">
<CFSET VARIABLES.qGrabMenu = APPLICATION.qGrabMenu>
<CFSET VARIABLES.DataSource = APPLICATION.DataSource>
<CFQUERY DBTYPE="query" NAME="qGetMenuElement">
WHERE Menu_item_ID = #ATTRIBUTES.MenuID#
ORDER BY sort_order
<CFQUERY DATASOURCE="#APPLICATION.DataSource#" NAME="APPLICATION.qGrabMenu" CACHEDWITHIN="#APPLICATION.QueryTimeOut#">
(SELECT menu_item_name FROM MENU_ITEMS WHERE MENU_ITEMS.menu_item_id = menu_items_xref.menu_item_id) AS Parent,
(SELECT menu_item_url FROM MENU_ITEMS WHERE MENU_ITEMS.menu_item_id = menu_items_xref.menu_item_id) AS Parentlink,
(SELECT menu_item_name FROM MENU_ITEMS WHERE MENU_ITEMS.menu_item_id = menu_items_xref.child_id) AS Child,
(SELECT menu_item_url FROM MENU_ITEMS WHERE MENU_ITEMS.menu_item_id = menu_items_xref.child_id) AS Childlink,
Section F rounds out our code; it’s the final piece of our
create_menu.cfm custom tag.
Using recursion, we’re able to build and populate the menu tree with a single custom tag.
Create_menu.cfm uses recursion to call itself as a custom tag in order to build sub-branches off the main root menu as needed. When a sub-branch is required, the code calls
<CF_create_menu>, which processes the data for the new branch. Should additional sub-branches be needed for an existing sub-branch, the code is called again and recurses another level (or as many levels as required) to create the branch(es). As each level completes, the one above continues on. After all the recursions have run, we’re left with the menu tree.
<!--- Build menu --->
<!--- Increment the loop counter --->
<CFSET VARIABLES.LoopCount = VARIABLES.LoopCount + 1>
<CFIF (qGetMenuElement.childlink NEQ '')>
<!--- Last element on a branch --->
<!--- build out branches --->
<CFSET VARIABLES.IncreNum = "0" & VARIABLES.LoopCount>
<CFSET VARIABLES.Num = ATTRIBUTES.DivID & VARIABLES.IncreNum>
<a href="##" class="menu" onclick="ShowMenu('#VARIABLES.Num#'); this.blur(); return false"><img src="#VARIABLES.RootLevel#images/right_arrow.gif" name="i#VARIABLES.Num#" width="9" height="9" border="0" hspace="4 /">#child#<!--- -#ATTRIBUTES.divID# ---></a>
<div id="d#VARIABLES.Num#" class="expand">
<CF_create_menu menuID = "#child_id#" divID = "#VARIABLES.Num#" level = "#ATTRIBUTES.Level#">
<!--- Reset the loop counter --->
<CFSET VARIABLES.LoopCount = 0>
That’s all there is to it! We’ve created a dynamic collapsible tree menu using a database and ColdFusion.