Key Takeaways
- Building a dynamic menu in ColdFusion allows for easy management of links without the need to modify HTML files each time new content is added, making it an efficient solution for teams with limited time for project maintenance.
- The dynamic menu is designed with two database tables, MENU_ITEMS and MENU_ITEMS_XREF, which hold the menu tree items and the structure of the tree respectively. A sort_order field is also included to allow content owners to prioritize certain topics on the page.
- The code for creating the dynamic menu involves a custom ColdFusion tag that recurses over a database structure and builds the appropriate menu structure. The JavaScript function ShowMenu() is the workhorse of the menu, handling the collapsing and expanding of the menu branches and changing the image states of those branches as required.
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.
Why go through the trouble of building a dynamic menu in ColdFusion when an HTML/JavaScript menu could be written and maintained with relative ease? Sure, we could have built the menu in HTML and JavaScript and just modified the code as needed, but where’s the fun in that? More importantly, project maintenance wasn’t what I was looking for. I didn’t want to be tied to the project in perpetuity; the team I was on was too small and had too many new projects to be spending time on the maintenance of old projects.
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
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. Name
(menu_item_name
) and HREF
(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 menu_item_id
and 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.
Image 2
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
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.
Section A
<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 = "../../">
</CFIF>
The following sections of code all combine into a single JavaScript block, but for the sake of making it easier to explain, I’ve broken it into smaller, more digestible pieces. This is the JavaScript that will handle the menu, collapsing and expanding the appropriate branches, and changing the image states of those branches as required.
Section B
<CFOUTPUT>
<script type="text/javascript">
function ShowMenu(n)
{
var menu, arrowImg;
menu = document.getElementById("d" + n);
Section C
// 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.
Section D
else
{
// 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;
}
}
}
</script>
</CFOUTPUT>
Section D is pretty long — it’s the remainder of the JavaScript function 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).
Once the entire menu has been collapsed, we have to expand the chosen menu branch. Since each branch can have its own sub-branches, the JavaScript has to not only expand the main branch, but, if the user clicks a sub-branch, that branch must be expanded also (see Image 3). Once the appropriate branches are expanded, the corresponding images need to be changed. Our menu uses two images: a blue arrow pointing to the right and a gold arrow that points down. These serve as an additional visual cue for the user.
Image 3
Once the images are adjusted, we’re done – in terms of the JavaScript, that is. We covered the JavaScript first, even though the ColdFusion part of our custom tag will execute first, because I wanted to establish the flow of the code and how it should work before we cover the set up. The JavaScript is the work horse of this menu, so it’s important to know what’s going on there and how everything works. Now, time to face the CF!
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.
Sample 1
<body <CFIF IsDefined('URL.Div')>
onload="ShowMenu('<CFOUTPUT>#URL.Div#</CFOUTPUT>')"</CFIF>>
<CFINCLUDE TEMPLATE="modules/menu/create_menu.cfm">
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.
Section E
<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>
</CFLOCK>
<CFQUERY DBTYPE="query" NAME="qGetMenuElement">
SELECT *
FROM VARIABLES.qGrabMenu
WHERE Menu_item_ID = #ATTRIBUTES.MenuID#
ORDER BY sort_order
</CFQUERY>
<CFQUERY DATASOURCE="#APPLICATION.DataSource#" NAME="APPLICATION.qGrabMenu" CACHEDWITHIN="#APPLICATION.QueryTimeOut#">
SELECT
menu_item_id,
(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,
child_id,
(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,
sort_order
FROM menu_items_xref
</CFQUERY>
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.
Section F
<!--- Build menu --->
<CFLOOP QUERY="qGetMenuElement">
<!--- Increment the loop counter --->
<CFSET VARIABLES.LoopCount = VARIABLES.LoopCount + 1>
<CFIF (qGetMenuElement.childlink NEQ '')>
<!--- Last element on a branch --->
<CFOUTPUT>
<a href="#VARIABLES.RootLevel##childlink#" class="menu"><img src="#VARIABLES.RootLevel#images/transparent.gif" width="9" height="9" border="0" hspace="4">#child#<!--- -#ATTRIBUTES.divID# ---></a><CFIF ATTRIBUTES.divID NEQ ""><CFIF FindNoCase(qGetMenuElement.ChildLink, CGI.PATH_INFO)><script type="text/javascript">ShowMenu('#ATTRIBUTES.divID#');</script></CFIF></CFIF>
</CFOUTPUT>
<!--- build out branches --->
<CFELSE>
<CFOUTPUT>
<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#">
</div>
</CFOUTPUT>
</CFIF>
</CFLOOP>
<!--- 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.
John is an independent Web consultant and freelance writer. He has written several articles for print and Web publications, and has contributed on books relating to ColdFusion. Visit John at www.red-omega.com