Pablo Varando's ColdFusion Blog

What is the best way to show categories (and their children, and their children and those children)

Today I ran by a forum post on EasyCFM.COM (see it here) that asked for help in displaying categories. This can be tricky; especially if you have many parent/child relationships. So I thought I would post a blog entry talking about the best way I have found to do this... Here goes nothing..

First you need to create the database for it... so lets make a table and cll it "categories".

In that table you will need to have the following items:

categoryID int identity (autonumber).
category   nvarchar(50) (string/text)
parentcategoryID int (number)

Then in that database; we will put in a few records (Let's do (3) layers).

CategoryID    Category             ParentCategoryID
1             Automotive           0
2             Real Estate          0
3             Used                 1
4             New                  1
5             For Sale             2
6             For Rent             2
7             Homes                5
8             Apartments           5
9             Homes                6
10            Apartments           6

Now notice that you have two records that are "master" records... Or top level which have a parentID of "0". This means that these are the ones that will show up in bold and are special :)

Now inside of that we created a (3) level one as follows, the other one has only two levels:

Real Estate
   > For Sale
       > Homes
       > Apartments
   For Rent
       > Homes
       > Apartments

Now to show them on a page all the way down, can be tricky if you code it; since it will require you to loop through the ones with a "0" and then do another query and loop through it and then do it again and again... so how do you accommodate for as many levels as you may need.. (imagine one has 40 levels deep)....

This is wehre custom tags really do come in handy... You can do a custom tag that will call itself over and over and over until it doesnt need to anymore... So how do you ask?

Well, let's show you...

First lets create a custom tag and let's call the file "categories.cfm"; on this page lets put this code:

<!--- this is a custom tag that will loop over itself and show categories and their children --->

<!--- first let's allow you to pass in a variable that will tell the system which categories to show --->
<cfparam name="attributes.ParentCategoryID" default="0" />

<!--- now let's hit the database and call the records --->
<cfquery name="qGetCategories" datasource="#request.dsn#">
select categoryID, category
from categories
where ParentCategoryID = #val(attributes.ParentCategoryID)#
</cfquery>

<!--- now let's show the categories --->
<cfoutput query="qGetCategories">
<a href="apage.cfm?categoryID=#val(categoryID)#">#category#</a><br />
<!--- now here goes teh magic... here let's have it call itself (the same page) but with a different ParentCategoryID ID value so it gets this records children --->
<cf_categories ParentCategoryID="#categoryID#">
</cfoutput>

That's pretty much it... now on the main page (where you want the parent categories to always show up) let's call the custom tag with a ParentCategoryID of "0" (again, remember this is the master records).

<cf_categories ParentCategoryID="0">

How easy was that? Now this baby can go 1, 2, 10, 40, 60, 100 levels deep and you don't have to do anyhing else... Sweeet..

If you want to only force it to go "2" levels down... you can do this:

<!--- now let's show the categories --->
<cfoutput query="qGetCategories">
<a href="apage.cfm?categoryID=#val(categoryID)#">#category#</a><br />
<!--- now here goes teh magic... here let's have it call itself (the same page) but with a different ParentCategoryID ID value so it gets this records children --->
<cfif attributes.ParentCategoryID EQ 0>
<cf_categories ParentCategoryID="#categoryID#">
</cfif>
</cfoutput>

That will only run one time when you call the parent caegories... then the parent's grandchildren will not be show; because when you call that tag again for the third time.. the ParentCategoryID will be higher then zero... make sense?

Let me know your thoughts....

-P

Comments
specific's Gravatar Cool Ray! Nice Post Keep Up the Good Work?
# Posted By specific | 12/7/08 6:37 AM
Chris's Gravatar That was simple and brilliant! I would never have thought of having the custom tag call itself. Thanks!
# Posted By Chris | 12/7/08 5:23 PM
specific's Gravatar Hey Pablo! You have Something Wrong in the Code:
the Code
<cfparam name="attributes.ParentCategoryID" dfault="0" />

should be

<cfparam name="attributes.ParentCategoryID" default="0" />

Cheers
# Posted By specific | 12/8/08 12:28 AM
Pablo Varando's Gravatar I will fix it; thanks.. considering I wrote all of that in a "textarea" and didnt test it... I can;t complaing on a typo being the issue :) hehe

Thanks for letting me know; I will correct it now!
# Posted By Pablo Varando | 12/8/08 1:20 AM
Raul Riera's Gravatar Actually this isent a good idea, you are polling the database for every single category, so this approach has horrible performance.

http://nstree.riaforge.org/

That is what you need
# Posted By Raul Riera | 12/8/08 3:14 PM
TS's Gravatar First, love this approach...simple and straight forward with min lines of code!!!!
I guess you could cache the query if performance is your concern...??

Question......
Is there a way to write this without the custom tag? I do not have access to use custom tags.

Thanks.
# Posted By TS | 2/2/09 10:25 PM
Pablo Varando's Gravatar TS, you could always do a cfinclude... however will have to track some type of ID that goes internally per request..

<!--- example --->
<cfset variables.parentIDs = arrayNew(1) />
<cfset variables.parentIDs[0].currentParentID = 0 />
<cfinclude template="directoryInclude.cfm" />

Then int he include you could do this:
<cfset variables.parentIDs[variables.parentIDs[0].currentParentID].currentParentID = 0 />

Or something like that... then its specific just to that included template...
# Posted By Pablo Varando | 2/2/09 10:31 PM