If you’re churnin’ out a web app cause your sales guy said you would,
And the PM he’s a pushin’ cause he knows you’re just that good;
But the requirements call for nav that stretches to infinity,
Then it may be time to think about recursive hierarchy….
Oh, recursion is a big word to describe a special function
That can chase its tail and catch it if it’s written with some gumption!
You could rightly call it Schizo, as to why, here’s the scoop:
It will call itself AND ANSWER in a never ending loop!
The secret to its mental state that makes it strange, yet stable
Is the special way it uses scope to give its vars a label.
If a variable is private then it’s labeled V-A-R,
While the public ones are public with no label needed thar!
And so, oh best Beloved, this function’s prelude now is ended;
But I must insist that you press on to find it and befriend it!
For no programmer’s app is done, regardless of the version,
Unless it boasts, at least a mite, the power of Recursion!
On THAT note…Let’s say that you are designing an app and you want to give the user the ability to manage his navigational tree to an infinite level; the ability to add items under items under items under items to his or her heart’s content. From a database point of view, the simplest way to store relational data like this and meet the requirement of infinite relationship levels is a single table with a structure something like the following:

A few quick comments about the fields. ParentID contains the id of the record that the item is a child of. Records with a parentID of zero are top level; they have no parents.
Okay, so storing the hierarchical data is no problem and the way we chose to do it is both elegant and makes perfect sense! The CHALLENGE, however, (there are no problems, only challenges) is how to retrieve and properly display this data while maintaining its hierarchical relationship. For our example, the requirement will be to retrieve the navigational data and output it using html Unordered Lists, maintaining the data’s hierarchy. Here is where the power of the recursive function is a perfect fit.
Recursion can be a difficult thing to visualize in action, so let’s first go through how one could retrieve and display this data if recursion did not exist.
First of all, we must know up front how many levels deep our data goes so that we know how many nestings to use in our code. In this case, none of the data goes more than 4 levels deep. Assuming our data has been retrieved into a query named qryNavigation, the following ColdFusion code will display our information as a properly nested unordered list:
<cfloop query="qrynavigation">
<cfif parentid eq 0><!--- Step 2 - Loop through all top level nav items --->
<cfset menustring = menustring & "<li>" & navdescription>
<!--- Step 3 - see if this item has children --->
<cfquery name="checkforKids" dbtype="query">
select * from qrynavigation where parentid = #qrynavigation.id#
</cfquery>
<cfif checkforkids.recordcount gt 0><!--- Step 4 - loop through kids --->
<cfset menustring = menustring & "<ul>">
<cfloop query="checkforkids">
<cfset menustring = menustring & "<li>" & navdescription>
<!--- Step 5 - see if this item has children --->
<cfquery name="checkforkids2" dbtype="query">
select * from qrynavigation where parentid = #checkforkids.id#
</cfquery>
<cfif checkforkids2.recordcount gt 0><!--- Step 6 - loop through kids --->
<cfset menustring = menustring & "<ul>">
<cfloop query="checkforkids2">
<cfset menustring = menustring & "<li>" & checkforkids2.navdescription>
<!--- Step 7 - see if this item has children --->
<cfquery name="checkforkids3" dbtype="query">
select * from qrynavigation where parentid = #checkforkids2.id#
</cfquery>
<cfif checkforkids3.recordcount gt 0><!--- Step 8 - loop through kids --->
<cfset menustring = menustring & "<ul>">
<cfloop query="checkforkids3">
<cfset menustring = menustring & "<li>" & checkforkids3.navdescription & "</li>">
</cfloop>
<cfset menustring = menustring & "</ul></li>">
<cfelse><!--- this level 2 item has no kiddos --->
<cfset menustring = menustring & "</li>">
</cfif>
</cfloop>
<cfset menustring = menustring & "</ul></li>">
<cfelse><!--- this level 1 item has no kiddos --->
<cfset menustring = menustring & "</li>">
</cfif>
</cfloop>
<cfset menustring = menustring & "</ul></li>">
<cfelse><!--- this level 0 item has no kiddos --->
<cfset menustring = menustring & "</li>">
</cfif>
</cfif>
</cfloop>
<cfset menustring = menustring & "</ul>">
<cfoutput>#menustring#</cfoutput>
Walking through this code, the first thing we have is a query containing all of the nav items. Step 1 is to create the opening UL tag for our unordered list. Next, we retrieve all of our top level (parentID = 0) nav items and loop through them. For each one, we check to see via a Query of Queries if that particular nav item has children. If so, we retrieve that items children and loop through those. For each of the children, we check to see if THEY have children. If so, we loop through them, repeating the process one more time. Opening and closing UL and LI tags are added appropriately to form a string containing a properly formatted html unordered list which is then output to the browser.
That is a lot of code, and as you can tell, it limits the data to 4 levels deep while our requirement called for infinite levels. Not the solution we’re looking for. We need one with a little more imagination, grace, elegance, and usability.
I’m just going to lay it on you; here is the equivalent recursive function that accomplishes the same thing, but without limitations and with less code:
<cfset variables.menustring = "">
<cfset variables.navigation = "">
<cffunction name="GenerateNav" access="public" returntype="string">
<CFARGUMENT name="parentID" type="numeric" required="yes" default=0 >
<CFARGUMENT name="level" type="numeric" required="yes" default=0 >
<!--- scoping the variables that need to have their values kept private
to a particular instance of the function call... --->
<CFSET var checkForKids = ""><!--- used to hold temporary check for children --->
<CFSET var objNav = ""><!--- used to hold temporary subqueries --->
<!--- On our initial call to this function, we will purge the menustring and grab our navigation source query. --->
<CFIF arguments.level eq 0>
<CFSET variables.navigation = qryNavigation>
<CFSET variables.menustring = "">
</CFIF>
<!--- Retrieve all nav records from variables.navigation who are the children of our current parent --->
<CFQUERY name="objNav" dbtype="query">
select * from variables.navigation where parentid = <CFQUERYPARAM value="#arguments.parentid#" cfsqltype="CF_SQL_INTEGER">
order by navorder
</CFQUERY>
<!--- write our current parent to the menustring... --->
<CFSET variables.menustring = variables.menustring & "<UL>">
<!--- loop through this parent's children... --->
<CFLOOP query="objNav">
<!--- check for children. If there are any, call this function recursively --->
<CFQUERY name="checkForKids" dbtype="query">
select * from variables.navigation where parentid = <CFQUERYPARAM value="#objNav.id#" cfsqltype="CF_SQL_INTEGER">
</CFQUERY>
<CFIF checkForKids.recordcount gt 0><!--- this child has kids too! add it to the menustring, then make the recursive call... --->
<CFSET variables.menustring = variables.menustring & "<LI>" & objNav.NavDescription >
<CFSET GenerateNav(parentID = objNav.ID, level = arguments.level + 1) >
<CFELSE><!--- this child is childless...just add it to the menustring... --->
<CFSET variables.menustring = variables.menustring & "<LI>" & objNav.NavDescription >
</CFIF>
<!--- close the list item --->
<CFSET variables.menustring = variables.menustring & "</LI>">
</CFLOOP>
<!--- close the UL tag --->
<CFSET variables.menustring = variables.menustring & "</UL>">
<!--- return final variable to the caller... --->
<CFIF arguments.level eq 0>
<CFRETURN variables.menustring>
</CFIF>
</cffunction>
<!--- THIS is the kickoff point of the whole thing! --->
<cfset recursivenav = GenerateNav(ParentID=0, level=0)>
<cfoutput>#recursivenav#</cfoutput>
Let’s walk through this recursive process. Step 1 really comes last, and that is our initial call to this function that kicks the whole process off like the first domino falling. From here on out, we will cease to use the actual value of the parent ID and simply refer to it as ‘X’; this will make it easier to walk through the code. We call the function passing a value of zero to the level argument (which lets us keep track of where we are in our recursive calls) and a value of X to the parentID argument (used when retrieving navigation subsets via query of queries). Our first call to the function sets up the two private (VAR scoped) variables for use by this instance of the function. Since we are at level zero, it then initializes the values of two public variables: navigation and menustring. Menustring is our cumulative variable, meaning that with every call to GenerateNav, we will be appending more and more items to it.
Our next step is to execute the query objNav which retrieves all items from the complete set of nav items that have a parentID of X. Once retrieved, we have our X level items and begin looping through them. For each item returned, we check to see if there are any children. If there are (drumroll please…), then we make a recursive call to GenerateNav, passing it a parentID equivalent to the current item’s ID, and a level equal to the current level plus one.
The recursive call to GenerateNav sets up its two private instances of checkForKids and objNav, skips the initialization of the public variables, then selects all nav items for the parentID that was just passed in. Those children are retrieved, we step through them, and for each of them that are found to have children themselves, we again call GenerateNav passing in the appropriate values for that particular instance of the function.
When we’ve gotten through every item in our first initial objNav query (which would have been all items with a parentID of zero), we add the closing UL tag to our public variable Menustring and then return it. Notice we enclose the code to add the closing UL tag and to return Menustring within a CFIF that looks at the level. This prevents Menustring from getting returned prematurely by any of our recursive calls.
When a recursive function calls another instance of itself, you can rightly picture that it just managed to perform mitosis and now has an identical brother performing the exact same work, only with its own private variables. Whatever variables were declared public are shared between the first and its brother, and any subsequent brothers that are spawned. As each clone completes its task, it disappears back into the ether until finally there is only the original instance left, and when it has completed its task, it returns the final value and itself disappears, completing the entire process. This is recursiveness in a nutshell. Beautiful, isn’t it?
You may also be wondering how useful it is to output your navigation in an unordered list. Bear in mind that I kept it as simple as possible and did not include fields to hold URLs and other linking information that would normally be present in a navigation table. Those can be added later and included within the information being appended to your Menustring variable, along with CSS class names based on level. Upon output, it is a relatively simple thing to create a Stylesheet to format your unordered list as a flyout menu (no javascript required), such as is shown here:
I hope you found this information both useful and inspiring, and will be looking for places to leverage it within your own application development. As the late great Buckminster Fuller once said, “…if the solution is not beautiful, then it is wrong.”, so let's keep it beautiful folks.
You are not logged in, so your subscription status for this entry is unknown. You can login or register here.
$node = 0;
$tree = "";
function treeview($branch,$node){
$chkcld = "";
$ObjNav = "";
$tree = $tree . "\r";
$qObjNav = mysql_query("SELECT * FROM architecture WHERE parent='$branch'");
while ($row = mysql_fetch_array($qObjNav)){
$ObjNavId = $row["id"];
$qObjNavX = mysql_query("SELECT * FROM architecture WHERE parent='$ObjNavId'");
if ($rowX = mysql_fetch_array($qObjNavX)){
$ObjNavIdX = $row["id"];
$tree = $tree . "" . $ObjNavId .(treeview($ObjNavIdX,$node+1,$limit)) . "\r\r";
} else {
$tree = $tree . "" . $ObjNavId . "\r";
}
}
$tree = $tree . "\r";
return $tree;
}
echo(treeview());
?>
once again, nice code..thanx.
Also, I just posted another use of recursion, one that also nests queries, but this one to build Flex Tree XML from ColdFusion queries. See http://www.forta.com/blog/index.cfm/2006/8/24/Building-Flex-Tree-MXML-From-ColdFusion-Queries.
--- Ben
http://rickosborne.org/blog/?p=3
Thanks!
Let's imediately go to the problem I get.
It says to me: "Error Occurred While Processing Request, Variable QRYNAVIGATION is undefined.". It's the part where we do this .
Before running the code I wet through your code Dough and asked myself where do you take the "qryNavigation" variable from. Are you sure you didn't forget to mention something? Now I am surprised, how come no one noticed this problem. Or is just me? :) It can be that I missed something, so please, help.
I would Also like to ask you where should I place the navigation table. In which database? I am asking this because I don't see this information anywhere in your code.
This said I have to ask you, another basic thing which I still didn't learn about CF. Please be patient with me. :) Is it possible to write and use functions also in *.cfm files? I guess... my idea is that it is possible, since in Java, for instance, you can also write methods in class definitions as you can do it in *.java files. So I guess in CF it must be the same - you can write functions in *.cfm and in *.cfc files. Am I right? :)
I am not new in programming and recursive functions / methods are nothing new to me either. I've been studying some basic recursion examples in C languange, so I understand it's importance and role. I also have experience in PHP where I used a code for the same recursive problem (challenge :) ). It's just that I am new to coldfusion and I am still a bit slow. :) However I do understand the basics, because as I said, I have experience in other programming and script languages (PHP,Java,C), so I do understand the basic concepts like functions, its arguments, return values etc.
Thank you a lot in advance for your help!
"It's the part where we set the variables.navigation variable."
Would be very grateful if you or anyone could help.
Thank you very much
