Building a Directory Chooser with ColdFusion and Java
The ColdFusion code required turned out to be an easy task because I was able to use the java.io.File object to get the information I needed. You may ask, why not use expandPath() and cfdirectory? Well, the answer is that 1) I wanted to get your root paths (i.e. on Windows, that might be something like C:/ and D:/) which I could not get directly though ColdFusion and 2) the java code worked very quickly. Here is the core of how it works...The DirectoryService CFC
(note: I have currently only tested this code on Windows and OSX but in theory it should be cross platform)
So, in my case, I only care about directories and not files since the files are going to be what I am generating. My component has only three methods, one being an init() which doesn't do anything but return the component back. The other methods are getRoots() which returns all the root directories of the computer. The other is getDirectories, which returns all the directories under a given directory (ignoring files as we mentioned). Each of these returns arrays of directory beans which only have four properties, the directory name, a path string, a parent path string and a boolean for hasChildren which allows me to know whether a given directory is expandable or not. As you can see below, the most complicated part was simply creating a means to filter out files when populating the hasChildren property - which wasn't very complicated after all:
<cfcomponent output="false"> <cffunction name="init" access="public" output="false" returntype="directoryService"> <cfreturn this /> </cffunction> <cffunction name="getRoots" access="public" output="false" returntype="array"> <cfset var fileObj = createObject("java","java.io.File") /> <cfset var systemRoots = fileObj.listRoots() /> <cfset var i = 0 /> <cfset var arrReturn = arrayNew(1) /> <cfset var thisDirectory = "" /> <cfloop from="1" to="#arrayLen(systemRoots)#" index="i"> <cfset thisDirectory = createObject("component","cfcgenerator.com.cf.model.directory.directory").init(systemRoots[i].getAbsolutePath(),systemRoots[i].getAbsolutePath(),"",true) /> <cfset arrayAppend(arrReturn,thisDirectory) /> </cfloop> <cfreturn arrReturn /> </cffunction> <cffunction name="getDirectories" access="public" output="false" returntype="array"> <cfargument name="baseDirectory" type="string" required="true" /> <cfset var fileObj = createObject("java","java.io.File").Init(arguments.baseDirectory) /> <cfset var allFiles = fileObj.listFiles() /> <cfset var i = 0 /> <cfset var x = 0 /> <cfset var arrReturn = arrayNew(1) /> <cfset var thisDirectory = "" /> <cfset var thisHasChildren = false /> <cfset var thisChildren = arrayNew(1) /> <cfset var thisParent = "" /> <P>
<!--- trap for null returned by allFiles ---> <cfif not isDefined("allFiles")> <cfset allFiles = arrayNew(1) /> </cfif> <cfloop from="1" to="#arrayLen(allFiles)#" index="i"> <cfif allFiles[i].isDirectory()> <!--- figure out if at least one of the children is a directory ---> <cfset thisChildren = allFiles[i].listFiles() /> <!--- trap for null ---> <cfif not isDefined("thisChildren")> <cfset thisChildren = arrayNew(1) /> </cfif> <cfset thisHasChildren = false /> <cfloop from="1" to="#arrayLen(thisChildren)#" index="x"> <cfif thisChildren[x].isDirectory()> <cfset thisHasChildren = true /> <cfbreak /> </cfif> </cfloop> <!--- get the parent directory, and trap for a case where null could be returned (though this should not come up) ---> <cfset thisParent = allFiles[1].getParent() /> <cfif not isDefined("thisParent")> <cfset thisParent = "" /> </cfif> <cfset thisDirectory = createObject("component","cfcgenerator.com.cf.model.directory.directory").init(allFiles[i].getName(),allFiles[i].getAbsolutePath(),thisParent,thisHasChildren) /> <cfset arrayAppend(arrReturn,thisDirectory) /> </cfif> </cfloop> <cfreturn arrReturn /> </cffunction> </cfcomponent>
The directory component is very basic as I mentioned, basically consisting, for the moment, of getters and setters for my four properties:
<cfcomponent output="false"> <cfproperty name="directoryName" type="string" required="true" /> <cfproperty name="path" type="string" required="true" /> <cfproperty name="parentPath" type="string" required="true" /> <cfproperty name="hasChildren" type="boolean" required="true" /> <cffunction name="init" access="public" output="false" returntype="directory"> <cfargument name="directoryName" type="string" required="true" /> <cfargument name="path" type="string" required="true" /> <cfargument name="parentPath" type="string" required="true" /> <cfargument name="hasChildren" type="boolean" required="true" /> <cfset setDirectoryName(arguments.directoryName) /> <cfset setPath(arguments.path) /> <cfset setParentPath(arguments.parentPath) /> <cfset setHasChildren(arguments.hasChildren) /> <cfreturn this /> </cffunction> <cffunction name="setDirectoryName" access="public" output="false" returntype="void"> <cfargument name="directoryName" type="string" required="true" /> <cfset variables.directoryName = arguments.directoryName /> </cffunction> <cffunction name="getDirectoryName" access="public" output="false" returntype="string"> <cfreturn variables.directoryName /> </cffunction> <cffunction name="setPath" access="public" output="false" returntype="void"> <cfargument name="path" type="string" required="true" /> <cfset variables.path = arguments.path /> </cffunction> <cffunction name="getPath" access="public" output="false" returntype="string"> <cfreturn variables.path /> </cffunction> <cffunction name="setParentPath" access="public" output="false" returntype="void"> <cfargument name="parentPath" type="string" required="true" /> <cfset variables.parentPath = arguments.parentPath /> </cffunction> <cffunction name="getParentPath" access="public" output="false" returntype="string"> <cfreturn variables.parentPath /> </cffunction> <cffunction name="setHasChildren" access="public" output="false" returntype="void"> <cfargument name="hasChildren" type="string" required="true" /> <cfset variables.hasChildren = arguments.hasChildren /> </cffunction> <cffunction name="getHasChildren" access="public" output="false" returntype="string"> <cfreturn variables.hasChildren /> </cffunction> </cfcomponent>
If you want to see this code in action, I built a little test page to allow you to browse your local machine using simple links with URL variables:
<cfset directoryService = createObject("component","cfcgenerator.com.cf.model.directory.directoryService").init() /> <P>
<cfif structKeyExists(url,"viewDir")> <cfset directories = directoryService.getDirectories(url.viewDir) /> <cfelse> <cfset directories = directoryService.getRoots() /> </cfif> <P>
<cfoutput> <cfloop from="1" to="#arrayLen(directories)#" index="i"> <cfif directories[i].getHasChildren()><a href="#CGI.SCRIPT_NAME#?viewDir=#directories[i].getPath()#"></cfif>#directories[i].getDirectoryName()#<cfif directories[i].getHasChildren()></a></cfif><br /> </cfloop> </cfoutput>
Going forward obviously I need to implement this within Flex. While that shouldn't be difficult, I am toying with some options on the specifics - i.e. whether to use the tree object or something simpler (I have heard of a lot of people who have had some difficulties with the tree and perhaps just a window that shows the current directory contents with a link to move up a level would work better - similar to my sample HTML version (though the sample version doesn't have the up a level implemented). Anyway, hopefully I can have something like this implemented soon to overcome the crippling limitations (IMO) of the current clunky save to file method in my generator.
When using the save feature, I type in the full path to the directory I want to save the files in. That string is concatenated with the path to the cfcgenerator folder (i.e. where the flex file resides)in the input field. Perhaps a quick and dirty would be to override the path that is created when you generate with only the path that is typed into "File System Root Path;"?
I like where you're going with this, but I can type faster than drilling up and down the directory list. What do you think?
