Coldfusion Tutorials



Spry Tutorials

Air Tutorials


ColdFusion 8 Binding Part 1

In my previous Coldfusion 8 tutorials CFTREE and CFGRID I introduced two of the new Coldfusion 8 tags CFTREE and CFGRID. As I said at the end of the CFTREE tutorial I would show you how to bind a grid to a tree.
The idea behind this tutorial is to produce something a little more real world. While this tutorial shouldn't be put on a production machine I will be expanding on this tutorial to produce something that you may find useful.

We will be producing using the new ajax features of Coldfusion 8 a file explorer that will eventually allow a user to browse the files on the server and upload, download, and delete files.
In Part 1 of this multipart tutorial we will build the Component that will feed our tree and grid with data. In part 2 we will bind our grid and tree to our component. And then in later tutorials we will expand our file explorer application to allow for uploading, downloading and deleting of files on the server. We will also explore ways to restrict our little application to a specific directory and finally allow our users to create and delete directories.

The Component

The first thing we need to do is to create our component. This component will contain a function to return the directory structure, and a function to return the files contained in a directory. I have also added a helper function to check to see if a directory has sub-directories this will allow us to correctly set whether or not a directory needs to be an expandable node or not.
<cfcomponent displayname="fileExplorer">

<cffunction name="getDirectories" access="remote" returnformat="json" returntype="any">
   <cfargument name="itemPath" type="string" required="yes"/>
   <cfargument name="itemValue" type="string" required="yes"/>
   <cfargument name="addSubDirs" type="boolean" required="no" default="true"/>

   <!--- we set the base directory here though we should really do this in the application.cfm or a
   ini file. --->

   <cfset var baseDir ="/"/>
   <!--- the tree expects to receive a json array so we initialize our array here --->
   <cfset var result =arrayNew(1)/>
   <cfset var sbaseDir=""/>

   <!--- get the absolute path of the base --->
   <cfset baseDir = expandpath(basedir)/>
   <!--- this piece of code will only run on the first call --->
   <cfif arguments.itemValue eq ''>


      <cfset element = StructNew()>
      <cfset element.value = "#request.approot#">
      <cfset element.display = "Root">
      <cfset arrayappend(result,element)>
      <cfreturn result/>
   </cfif>

   <!--- this block of code will run on each subsequent call and return the directories for
   the passed in path --->

      <cfset sbaseDir ="#basedir##itemvalue#"/>
      <cfdirectory action="list" name="allDirs" directory="#sbaseDir#"/>
      <cfquery name="dirs" dbtype="query">
         select name
         from alldirs
         where type='Dir'
      </cfquery>

      <cfoutput query="dirs">
      <cfset element = StructNew()>
      <cfset element.value = "#itemvalue#\#name#">
      <cfset element.display = "#name#">
      <!--- the hasSubDirs is a helper function so that we format the tree nodes correctly as either
      being an end node or one having children --->

      <cfif not hasSubDirs('#sbaseDir#\#name#')>
         <cfset element.LeafNode = true/>
      </cfif>
      <cfset arrayappend(result,element)>
      </cfoutput>

   <cfreturn result/>
</cffunction>

<cffunction name="getFiles" access="remote" returnformat="json">
   <cfargument name="page" type="numeric" required="yes"/>
   <cfargument name="pageSize" type="numeric" required="yes"/>
   <cfargument name="gridSortColumn" type="string" required="yes"/>
   <cfargument name="gridSortDirection" type="string" required="yes"/>
   <cfargument name="path" type="string" required="false" default="/"
                     hint="again we should have the default value in a application variable here"/>


   <cfset var webpath="" & path>

   <cfdirectory action="list" directory="#expandpath('#path#')#" name="files"/>
   <cfquery name="getFiles" dbtype="query">
      select name,size,'#webpath#' as directory ,datelastmodified
      from files
      where type='File'
      <cfif gridsortcolumn neq ''>
      order by #gridsortcolumn# #gridsortdirection#
      </cfif>
   </cfquery>

      <cfset mq = queryNew("name,size,directory,datelastmodified","varchar,integer,varchar,varchar")>
      <cfset i = 0>
      <cfoutput query="getfiles">
            <cfset i = i + 1/>
            <cfset temp = queryAddRow(mq,1)/>
            <cfset temp= querySetCell(mq,"name","#name#",i)/>
            <cfset temp= querySetCell(mq,"size","#size#",i)/>
            <cfset temp= querySetCell(mq,"directory","#directory#",i)/>
            <cfset temp= querySetCell(mq,"datelastmodified","#dateformat(dateLastModified,'medium')# #timeformat(datelastmodified,'medium')#",i)/>
      </cfoutput>

   <cfreturn queryConvertForGrid(mq,arguments.page,arguments.pageSize)/>
</cffunction>

<cffunction name="hasSubDirs" access="private" returntype="boolean">
   <cfargument name="path" type="string" required="yes"/>

   <cfdirectory action="list" name="hasDirs" directory="#path#"/>

   <cfquery name="dirs" dbtype="query">
         select name
         from hasDirs
         where type='Dir'
      </cfquery>

      <cfif dirs.recordcount eq 0>
         <cfreturn false/>
      <cfelse>
         <cfreturn true/>
      </cfif>
</cffunction>
</cfcomponent>
As you can see from the code block above we use the cfdirectory function to return the results, we then loop through the results where the type is "dir" to get only the directories, in the getFiles function we restrict the query to return to type = 'file'.

Testing the component - getDirectories

Now that we have our component created we should test to see if it is return expected results. Since we will be calling the component directly from our Tree and Grid we should be able to type in a URL to our component and do the same thing. So lets do that. On my server I have a virtual directory called mycf and created my component in the directory com/gg so the url to my components directory would be "http://localhost/mycf/com/gg/". To call my component directly I just do the following.

http://localhost/mycf/com/gg/fileExp.cfc?method=getDirectories&returnFormat=json&itemPath=&itemValue=

What is new here is that we are calling our CFC directly, this can only be done when we have the access attribute set to remote. We then specify what method we want to call in our component using the method attribute. Also new in Coldfusion 8 is the returnformat argument. This argument allows us to specify how we want our data formated when it is returned as WDDX, JSON or Plain. It is important to note that if you are using other AJAX frameworks Coldfusion returns the JSON data escaped or prefixed so it may be a good idea to set the returnformat to plain. Be aware though that if you specify plain as your return format you will need to build the json structure yourself and won't be able to rely on CF to convert a complex structure for you.

Then last but not least you need to pass along your functions arguments. What you should get back from the component is a directory listing looking something like:
[{"DISPLAY":"Root","VALUE":"\/mycf"}]
Modifying the itemvalue parameter slightly by passing in value of the root node, /mycf in this case, should result in a larger return set where you can see which of the nodes are leaves (meaning they don't have subdirectories) and which ones are branches.

Testing the Component - getFiles

Now that we know that our component returns the root of our tree we should also make sure that our getFiles function returns files (makes sense doesn't it). So we will have a similar URL as above except that we will be calling a different method and passing different arguments.

http://localhost/mycf/com/gg/fileExplorer.cfc?method=getfiles&returnFormat=json&page=1&pageSize=10&gridSortColumn=&gridSortDirection=

We must pass the arguments page,pageSize,gridSortcolumn and gridSortDirection as they are required for the queryConvertForGrid function to correctly parse our query and return the results specifically set up for the grid. When everything works as it should we should end up with something that looks like:

{"TOTALROWCOUNT":33,"QUERY":{"COLUMNS":["NAME","SIZE","DIRECTORY","DATELASTMODIFIED"],"DATA":[["ajaxtree.cfm",3849,"\/mycf","Sep 20, 2007 5:23:10 PM"],["application.cfm",380,"\/mycf","Sep 27, 2007 11:53:42 AM"],["cfdiv.cfm",686,"\/mycf","Jul 9, 2007 5:47:48 PM"],["cftree.jpg",24312,"\/mycf","Jul 6, 2007 5:39:38 PM"],["dspGrid.cfm",467,"\/mycf","Jul 4, 2007 7:35:09 PM"]]}}

Whats Next

We now have a component with a function that returns a data structure containing our directories and another function returning a data strucutre with the files contained in a directory. Both functions return our data to us in specifically formated JSON data structures. We hand built the structure for the tree (we could build a function to do that for us), and we used a new Coldfusion 8 function queryConvertForGrid() to, as the function indicates, format a query into JSON specifically for the EXT grid.

In part 2, as I mentioned above, we will be setting up our tree and grid to bind to the functions in the component and then see how we bind the grid to the change event of the tree so that the grid is updated when a node of the tree is clicked.