Welcome to Dream.In.Code
Click Here
Getting Help is Easy!

Join 117,614 Programmers for FREE! Ask your question and get quick answers from experts. There are 1,986 online right now! We've got more than 500 tutorials and 2,000 snippets. Join and find out why Dream.In.Code is the #1 programming help community on the internet! Registration is fast and FREE... Join Now!



Nested Categories - Recursion Using Components

 
Reply to this topicStart new topic

> Nested Categories - Recursion Using Components

Rating  5
skyhawk133
Group Icon



post 15 Mar, 2006 - 02:18 PM
Post #1


Just a quick little tutorial on how to create display nested categories from a database using recursion and components. This is the simplest way I've found to accomplish recursion in ColdFusion, but feel free to comment if you know a better way.

In this tutorial, we are working with a single table in a SQL database with 3 columns: id, parent, name.

Here is the database structure:
CODE

categories
-----------------------------------
id (int, auto-increment, primary key)
parent (int)
name (nvarchar)


Create the component

Create a new component called categories.cfc, start with this structure:
CODE

<cfcomponent name="Categories">
    <cffunction name="getCats">
    
  <!--- Parent ID --->
  <cfargument name="parent" type="numeric">
  
  
    </cffunction>
</cfcomponent>


As you may already know, recursion simply calls the same function from within itself. The beauty of using components is there is no need to number or name our function or objects because each call creates a new instance of the object.

Setup the query

In the code above, we've created a function called getCats, it accepts 1 argument: parent. We will use this argument to query for child categories.

CODE

<cfcomponent name="Categories">
    <cffunction name="getCats">
    
  <!--- Parent ID --->
  <cfargument name="parent" type="numeric">

  <!--- Query for Children of Parent --->
  <cfquery name="qCats" datasource="YOUR_DATASOURCE">
  SELECT * FROM categories WHERE parent = '#arguments.parent#'
  </cfquery>    
  
    </cffunction>
</cfcomponent>


Create the output

Now that we've setup a query to get all the nested (child) categories, we should setup the output. We'll do this by setting text to a output variable which will be returned. Remember, you should never need to display anything from inside your functions, it should be returned using cfreturn.

CODE

<cfcomponent name="Categories">
    <cffunction name="getCats">
    
  <!--- Parent ID --->
  <cfargument name="parent" type="numeric">

  <!--- Query for Children of Parent --->
  <cfquery name="cats" datasource="YOUR_DATASOURCE">
  SELECT * FROM categories WHERE parent = '#arguments.parent#'
  </cfquery>    

  <!--- Create Output --->
  <cfset output = "<ul>">
  
  <!--- Loop over all items --->
  <cfloop query="cats">
      <cfset output = "#output# <li>#cats.name#</li>">
      
    <!--- Check Children of this item --->
    <cfif cats.recordcount GT 0>
        <cfset output = "#output# #CreateObject('component', 'categories').getCats(cats.id)#">
    </cfif>
    
  </cfloop>
  <cfset output = "#output#</ul>">
  
  <cfreturn output />


    </cffunction>
</cfcomponent>


We did a lot in that step, but it's really pretty simple. We've looped over the query appending text to our output variable and using #CreateObject('component', 'categories').getCats(cats.id)# we call the component and function from within itself to get child categories for the category the query is currently on. After the loop is finished, we return the output variable.

Putting it on a page

Save your categories.cfc and create a new .cfm file. This is where you'll call your recursive function and display your categories in a nicely organized list.

CODE

<cfoutput>
#CreateObject('component', 'categories').getCats(0)#
</cfoutput>


We instantiate the categories component we just made and call the getCats function. When you created your table to query categories from, all of your root (top-level) categories should have a parent of 0, passing 0 to the parent argument starts at the very top of your nesting and begins to recurse down.

You should see something like this:


  • Parent 1
    • Child 1.1
      • Child 1.2
        • Child 1.2.1
          • Child 1.2.1.1
            • Child 1.2.1.1.1
            • Child 1.2.1.2
              • Child 1.2.1.3
                • Child 1.2.1.3.1
          • Parent 2
            Go to the top of the page
            +Quote Post


            Register to Make This Ad Go Away!

            sadara
            *



            post 2 Jul, 2006 - 06:00 AM
            Post #2
            thanks for this tutorial! after hours of banging my head against a brick wall, i have a working script.
            Go to the top of the page
            +Quote Post

            billybrag
            *



            post 23 Aug, 2006 - 03:55 AM
            Post #3
            hello there,
            i tried this and it works ok, but produces lots of empty UL's

            please can you help me to sort them - an eg of this in action is here... www.organiclinker.com/test.cfm

            thanks
            Mike

            This post has been edited by billybrag: 23 Aug, 2006 - 03:55 AM
            Go to the top of the page
            +Quote Post

            skyhawk133
            Group Icon



            post 23 Aug, 2006 - 06:54 AM
            Post #4
            You may need to add some code in the output to check for an empty value. Not sure why it would be outputting empty UL's. Do you have anything in your database that might cause this?!
            Go to the top of the page
            +Quote Post

            billybrag
            *



            post 23 Aug, 2006 - 07:48 AM
            Post #5
            QUOTE(skyhawk133 @ 23 Aug, 2006 - 06:54 AM) *

            You may need to add some code in the output to check for an empty value. Not sure why it would be outputting empty UL's. Do you have anything in your database that might cause this?!


            No - not that i can see - all i have changed from your code is the variable names to match my db?

            it seems to be a problem with the logic?
            Go to the top of the page
            +Quote Post

            monizzle
            *



            post 15 Dec, 2006 - 10:33 AM
            Post #6
            I made a few changes to skyhawk133's code so that it produces valid html. (see this url for an explanation of proper list nesting: http://www.w3schools.com/xhtml/xhtml_html.asp)

            In skyhawk's original code the child ul's were not nested within the parent li's. There may be a more elegant way to do what I have done, so please chime in if you've got one.

            skyhawk133: Thanks for the original post!

            CODE


            <!--- Create Output --->
            <!--- Monizzle: The following IF wrapper makes sure an empty <ul> isn't returned if no children are found with the default parent id  --->
              <cfif qCats.recordcount GT 0>
              <cfset output = "<ul>">
              
              <!--- Loop over all items --->
              <cfloop query="cats">
                <!--- Monizzle: Removed closing </li> from output below to properly nest child <ul>s  --->
                  <cfset output = "#output# <li>#cats.name#">
                  
                <!--- Check Children of this item --->
                <cfif cats.recordcount GT 0>
                    <cfset output = "#output# #CreateObject('component', 'categories').getCats(cats.id)#">
                </cfif>
                <!--- Monizzle: Add closing </li> to the output  --->         
                <cfset output = "#output#</li>">
              </cfloop>
              <cfset output = "#output#</ul>">
              
              <cfreturn output />
            <!--- Monizzle: close IF wrapper --->
            </cfif>

            Go to the top of the page
            +Quote Post

            dduck1934
            *



            post 13 Apr, 2008 - 06:09 PM
            Post #7
            instead of Creating a new Object everytime, couldnt you change this line..

            <cfset output = "#output# #CreateObject('component', 'categories').getCats(cats.id)#">


            to just <cfset output = "#output# #getCats(cats.id)#">


            Go to the top of the page
            +Quote Post


            Reply to this topicStart new topic
            1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
            0 Members:

             

            Lo-Fi Version Time is now: 10/7/08 11:55PM

            Live Help!

            Tutorials

            Programming

            Web Development

            Reference Sheets

            Code Snippets

            Bye Bye Ads

            Free DIC T-Shirt

            T-Shirt Example

            Related Sites

            Monthly Drawing

            Thumb Drive

            Partners

            Top Contributors

            Top 10 Kudos This Month