Twitter: raymondcamden


Address: Lafayette, LA, USA

Ask a Jedi: Dumping a Recursive Directory List

02-06-2006 12,105 views ColdFusion 20 Comments

A reader (nicely) asked me something before I left for Boston, and I never got around to answering. He had an interesting problem. He wanted to list directories and files, in a recursive fashion, using HTML's unordered list display to handle the directories and their children.

Now I thought this was a simple thing - just use the recurse=true option in <cfdirectory>. However - the more I thought about it - the more difficult it seemed. You can sort the <cfdirectory> result - but not in an way you can simply output with HTML.

My first thought was to switch back to a recursive <cfdirectory>, and while that would work, I assumed I'd lose a lot in terms of speed due to all the file operations. So what I came up with was a mix of recursive CFML and the built-in recursive <cfdirectory> tag:

view plain print about
1<cfset initialDir = "c:\apache2\htdocs\testingzone\blogcfc_flex2">
2<cfdirectory directory="#initialDir#" recurse="yes" name="files" sort="directory asc">
3
4<cfset display(files,initialDir)>
5
6<cffunction name="display" returnType="void" output="true">
7    <cfargument name="files" type="query" required="true">
8    <cfargument name="parent" type="string" required="true">
9    <cfset var justMyKids = "">
10    
11    <cfquery name="justMyKids" dbtype="query">
12    select    *
13    from    arguments.files
14    where    directory = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.parent#">
15    </cfquery>    
16    
17    <cfoutput><ul></cfoutput>
18    
19    <cfoutput query="justMyKids">
20        <li>#directory#\#name#</li>
21        <cfif type is "Dir">
22            #display(arguments.files, directory & "\" & name)#
23        </cfif>
24    </cfoutput>
25    
26    <cfoutput></ul></cfoutput>
27    
28</cffunction>

As you can see, I do the initial <cfdirectory> and have it fetch all the files. The UDF simply handles displaying items from the query. I don't normally do output from UDFs, so to be honest, I feel a bit dirty. I'd probably just wrap it up in a cfsavecontent and return that, but this was written in about 5 minutes. Another problem - note I hard code \ as my file delimiter. I could have made this more dynamic by using a Java call:

view plain print about
1<cfset separator = createObject("java","java.io.File").separator>

In general, the use of "/" will work just fine in any OS, however, since I was doing a string comparison in my query, I'd probably want to use the same separator CF used.

20 Comments

  • Commented on 02-06-2006 at 8:38 PM
    This is really ironic...I was thinking about this exact topic on my way home from work today (although with a slightly different output). Thanks a lot! This will definitely help.
  • emmet #
    Commented on 02-06-2006 at 8:57 PM
    More on the subject of recursion and coincidence. I just came across this tonite before popping over here. http://rickosborne.org/blog/?p=3

    I'm interested to see how you handle db sorting and recursion Ray. My method has always been on the sloppy side. Including the same file over and over again until there are no more parents to be found.
  • Commented on 02-07-2006 at 8:43 AM
    Emmet - well, that is an example of recursion actually. But I know what you mean - recursion can get messy and is a pain to debug at times.
  • Doug #
    Commented on 02-07-2006 at 8:49 AM
    Minor nit-pick: Nested lists should have the sub list between the li tags of the parent, not after.

    Simply moving the closing li in your function so it's after the recursive call to the function accomplishes this.

    I only point this out because it's a handy function if you use a menuing system like udm4 (www.udm4.com), but it won't work if the nesting isn't proper.
  • Commented on 02-07-2006 at 8:53 AM
    Thanks Doug - I was a bit unsure on that.
  • Commented on 02-07-2006 at 9:01 AM
    Nice, simple, and elegant! Great!
  • Doug #
    Commented on 02-07-2006 at 10:52 AM
    emmet - For database hierarchies, I tend to prefer to let the database do the work. The only downside is that different RDBMS' have different methods of approaching the problem.

    Oracle has by far the best method of handling hierarchical data with the "start with...connect by prior" syntax.

    Given a table with id, name and parentid columns, you can get a sorted tree result like so:

    select name, level
    from table
    start with parentid is null
    connect by prior id = parentid
    order siblings by name asc

    The level column is a pseudo column that gives the depth of each row in the hierarchy, so the root is level 1, it's children level 2, and so on.

    The "order siblings by" sorts the results appropriately within each node (in this case, alphabetically by name).

    You can get a similar result in other databases using CTE (Common Table Expressions), but not nearly so easily. Here's an article that compares the two methods:

    http://www-128.ibm.com/developerworks/db2/library/...

    In my experience, from a strictly performance standpoint, either method is preferrable to doing the sorting in the application, particularly if the dataset is large.

    That being said, if your requirements dictate database portability, Rick Osborne's method in the link you cited seems to be a pretty decent approach, although I haven't tested it out myself.
  • Emmet #
    Commented on 02-07-2006 at 11:04 AM
    Im on MSSQL. Oracle is but a pipe dream. I never really had a problem with my method until I noticed how horribly one of our clients apps are now performing. It's an older CF5 app and I dont think it was ever expected to grow to the level it has. It's now taking over 1000ms to generate a tree. Now that were on CFMX7 I need to explore some other options.
  • Doug #
    Commented on 02-07-2006 at 11:20 AM
    For MSSQL, take a look here:
    http://msdn2.microsoft.com/en-us/library/ms175972....

    This covers using CTEs in MSSQL, and combined with the ibm.com link in my previous comment should give you a solid foundation if you want to try this approach.
  • Commented on 02-07-2006 at 1:52 PM
    Though it's not an unordered list, a tree display might be a better solution and easier. I just whipped up an example and added it as a trackback.

    Ray... Forgive the title, I couldn't resist :-)

    Wiggy
  • emmet #
    Commented on 02-07-2006 at 3:08 PM
    Thanks Doug. Thats alot to wrap my head around.
  • Commented on 02-07-2006 at 3:47 PM
    Don't forget Forta's example:
    http://www.forta.com/blog/index.cfm?mode=entry&...
  • Doug Wilder #
    Commented on 03-09-2006 at 2:19 PM
    I have two dropdown menus in a form. The first is populated with directories using cfdirectory (looping thru to find any of type="dir"). The second dropdown needs to be a list of subdirectories that fall under whatever directory the user chooses in the first dropdown menu. Below is the code I've tried but it's not working. Seems like I need something in the onchange event. I've looked at the cf_twoselectsrelated but can't see how to implement it when the query is a set of directories. I don't see exactly how to use the recurse="true" (or "yes"?) either since it looks like all that will do is put the subdirectories into the same dropdown box as their parent directories. Ultimately, I want to allow users to choose at least upto two directory levels for uploading files. For that, I need the parent dir name and subdir name.Any help/ideas is greatly appreciated!
    Doug in Fairbanks

    <p>Choose top level folder:<br />
    <select name="mnuFolder" onchange="">
    <cfdirectory directory="D:\myplace\" name="Parent_Folder">
    <cfloop query="Parent_Folder">
    <cfif #Parent_Folder.Type# eq "Dir">
    <cfoutput>
    <option value="#Parent_Folder.Name#\">#Parent_Folder.Name#</option>
    </cfoutput>
    </cfif>
    </cfloop>
    </select><br />
    Choose subfolder (if any):<br />
    <select name="mnuSubFolder">
    <cfdirectory directory="D:\myplace\#Parent_Folder.Name#\" name="Sub_Folder">
    <cfloop query="Sub_Folder">
    <cfif #Sub_Folder.Type# eq "Dir">
    <cfoutput>
    <option value="#Sub_Folder.Name#\">#Sub_Folder.Name#</option>
    </cfoutput>
    </cfif>
    </cfloop>
    </select><br />
  • Commented on 03-09-2006 at 2:28 PM
    What you need is dependant selects. You can find many posts on that if you google. It is a bit too much to discuss here.
  • Lamisaunet #
    Commented on 05-12-2006 at 2:28 PM
    After three days, I've found an easier way :

    <cfparam default="#GetDirectoryFromPath(GetTemplatePath())#/showcase" name="repertoire"/>
    <cfdirectory action="list" directory="#repertoire#" name="allDirectories" recurse="true"/>
    <cfoutput query="allDirectories" group="name">
    <cfif #allDirectories.type# IS "DIR">
    <h3>#allDirectories.name#</h3>
    <cfelse>
    <pre>#allDirectories.name#</pre>
    </cfif>
    </cfoutput>
  • Doug Wilder #
    Commented on 06-06-2006 at 3:37 PM
    Isn't this going to list all directories in one list? What I need is two selects with the first listing top-level directories and the second listing subdirectories of the top-level directory selected in the first select. I searched for "independent selects" but am coming up empty. Sorry if this is a bit much for this site but this is the closest I've come to a solution.

    Thanks,
    Doug Wilder
  • Dylan #
    Commented on 05-11-2007 at 12:10 PM
    Could someone share an example of how to call this function? I'm new to using UDF, and I'm unsure how to input the correct values for the arguments.
  • Commented on 05-11-2007 at 1:24 PM
    Please check the CF Docs on how to use UDFs. In general though you call them like any other function. The arguments for the UDF in this entry are first a query of files, then the path to the initial directory.
  • Dylan #
    Commented on 05-11-2007 at 1:26 PM
    ah, a query of files. that's what I needed to know. Thanks.
  • glen #
    Commented on 07-26-2009 at 9:07 PM
    Using Ray's example as a starting point I have tweaked it to create a recursive function that 'copies' a folder structure, 3 deep and excludes files.

    http://www.stinkylittlefriend.com/blog/index.cfm/2...

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty