Blog

Blog posts made on 05-Jun-07
5Jun
2007
This Is What Education Should Look Like

My local paper, the Detroit Free Press, is running a wonderful story that should be read by educators everywhere. Bradford Academy is a local charter school serving 1100 students in kindergarten through eighth grade. A year or so ago, 75 seventh graders were given the opportunity to turn school reports about famous disasters into short documentaries. The kids were given access to computers, technical guidance, storyboarding direction, and more, and they worked on their projects for a year. They had to do the research, compile information, write the script, shoot the video, work on music and scoring, and more. Not all students made it all the way to the end, but those who did had their documentaries screened at a really nice local movie theater, complete with an Oscar night type experience (red carpet, tuxes and gowns, etc.) and an awards ceremony. What a great way to encourage kids to learn, while exposing them to technology in a meaningful way. I am more than impressed.

Read More ›

5Jun
2007
The Hosting News On ColdFusion 8

Derek Vaughan has posted an article on ColdFusion 8 over on TheHostingNews.com.

Read More ›

5Jun
2007
IPv6 Debugging Gotcha

I was trying to use the ColdFusion 8 Ajax debugger and could not get it to pop up, despite having that option enabled and my IP address (127.0.0.1) in the debug IP address list. Eventually I discovered (by mistake) that if I used "127.0.0.1" in the URL then debugging worked, but if I used "localhost" it did not. The problem? IPv6 (used by default in Vista), where localhost maps to "0:0:0:0:0:0:0:1" instead of "127.0.0.1", causing the IP address to not match what was in the allowed debug IP address list. The solution is to simply add "0:0:0:0:0:0:0:1" to the list of allowed IP addresses (or just access CF Admin using "localhost" and click "Add Current" which will add that address). We'll fix this by the time we ship CF8 so that 0:0:0:0:0:0:0:1 is also a default, but if you are using the ColdFusion 8 public beta you'll want to add this manually for now.

Read More ›

5Jun
2007
ColdFusion Ajax Tutorial 5: File System Browsing With The Tree Control

<CFTREE> has been part of ColdFusion since CF2 - originally a Java applet, and then a Flash control, and in ColdFusion 8 an HTML tree control with powerful Ajax support. To demonstrate how to use the new HTML <CFTREE>, here is an example that will let you browse the file system tree on your server. What makes this an Ajax control is that the entire tree is not loaded on startup. Rather, it is loaded incrementally, minimal data is loaded on startup, and when a node is expanded an asynchronous call is made back to the server to obtain the children for that node, and so on. This improves performance (by not requiring that entire trees be loaded if they are not needed), and also simplifies actual tree data (formatting and parsing nested tree data is not pretty).

The client-side code for this example is really simple, as seen here:

view plain print about
1<cfform>
2<cftree name="dirtree" format="html">
3    <cftreeitem bind="cfc:dirtree.getDirEntries({cftreeitempath}, {cftreeitemvalue})">
4</cftree>
5</cfform>

This <CFTREE> uses a "bind" to point to a CFC method to obtain tree contents. When a binding is used, as it is here, only a single <CFTREEITEM> may be specified, and it is responsible for loading all data as needed. On initial tree load the CFC method is invoked (passing an empty string for the current path and value), and then as branches are expanded the CFC method is invoked again (passing the current path and value back to the server).

It is worth noting that, by default, results are cached on the client. So expanding and collapsing a branch repeatedly does not trigger multiple CFC method calls. Caching can be disabled if needed.

The CFC method that the <CFTREE> is bound to is shown here:

view plain print about
1<!--- Get directory entries for Ajax CFTREE --->
2<cffunction name="getDirEntries" access="remote" returnType="array">
3    <cfargument name="path" type="string" required="false" default="">
4    <cfargument name="value" type="string" required="false" default="">
5
6    <!--- Init variables --->
7    <cfset var dir="">
8    <cfset var entry="">
9    <cfset var result=arrayNew(1)>
10    <cfset var filepath="">
11
12    <!--- Check if top level --->
13    <cfif ARGUMENTS.value IS "">
14        <!--- Yes, top level, get drives --->
15 <cfset dir=getDrives()>
16        <!--- Loop through dir list --->
17        <cfloop query="dir">
18            <!--- Add each drive/root --->
19            <cfset entry=StructNew()>
20            <cfset entry.value=dir.name>
21            <cfset entry.display=dir.name>
22            <cfset entry.img="fixed">
23            <cfset ArrayAppend(result, entry)>
24        </cfloop>
25    <cfelse>
26        <!--- Not top level, get dir list --->
27        <cfdirectory action="list"
28                    directory="#ARGUMENTS.value#"
29                    name="dir"
30                    sort="name">

31        <!--- Loop through dir list --->
32        <cfloop query="dir">
33            <!--- Create entry --->
34            <cfset entry=StructNew()>
35            <!--- Use full path as value --->
36            <cfset entry.value=ARGUMENTS.value & THIS.separator & dir.name>
37            <!--- Use just name for display --->
38            <cfset entry.display=dir.name>
39            <!--- Is this a file or a dir? --->
40            <cfif dir.type IS "file">
41                <!--- A file, so no children --->
42                <cfset entry.leafnode=TRUE>
43                <!--- Use document icon --->
44                <cfset entry.img="document">
45            <cfelse>
46                <!--- A dir, use folder icon --->
47                <cfset entry.img="folder">
48                <cfset entry.imgopen="folder">
49            </cfif>
50            <!--- Add it to the array --->
51            <cfset ArrayAppend(result, entry)>
52        </cfloop>
53    </cfif>
54
55    <!--- And return the results --->
56    <cfreturn result>
57</cffunction>

<CFTREE> expects the bound CFC method to return an array of entries represented as structures. These structures should, at a minimum, contain two members - "display" is the string to be displayed in the tree, and "value" is the value to be returned (back to the bound CFC when branches are expanded, as well as when the form containing the <CFTREE> is actually submitted). The structures may also contain additional members (equivalent to the attributes supported by <CFTREEITEM>), for example, "img" and "imgopen". One important optional member is "leafnode" which tells the tree whether or not the current entry is a leaf (has no children) or a branch (could have children). As tree data is loaded as needed, <CFTREE> can't know if a branch has children until the user tries to expand it. In this example we want folders to be expandable, but not files, and so <leafnode> is set to "true" for file entries.

If the method is being invoked for the top level of the tree (in which case ARGUMENTS.value will be an empty string) then a function is called to get all system drives (or roots). The code then loops through the results, creating a structure for each, and appending these to a results array. If the method is being invoked for a child branch (in which case ARGUMENTS.value will contain the path to the branch that was expanded), <CFDIRECTORY> is used to obtain the contents of that folder, and then the results are looped over, creating a structure for each entry, and again appending these to a results array.

This example returns all file and folders. If you wanted to return a subset of data, perhaps to allow the user to find files of a specific type, you'd just need to modify the <CFDIRECTORY>, possibly replacing it with two <CFDIRECTORY> calls, one for folders and one for the files (using a filter to select just the files you want).

This method refers to a function named getDrives() (to obtain drives or roots) and a variable named THIS.separator (used to refer to the OS specific path separator character), both of which need to be defined. The following is the complete dirtree.cfc, containing the previously seen getDirEntries() method, as well as all supporting code:

view plain print about
1<cfcomponent output="false">
2
3
4<!--- Constructor code --->
5<cfset THIS.separator=getPathSeparator()>
6
7
8<!--- Get system path separator character --->
9<cffunction name="getPathSeparator" access="private" returntype="string">
10    <!--- Init variables --->
11    <cfset var jifObj="">
12    <!--- Need java.io.File class --->
13    <cfobject type="java" class="java.io.File" name="jifObj">
14    <!--- Return seperator --->
15    <cfreturn jifObj.separator>
16</cffunction>
17
18
19<!--- Get system drives (or roots) --->
20<cffunction name="getDrives" access="private" returntype="query">
21    <!--- Init variables --->
22    <cfset var jifObj="">
23    <cfset var drives="">
24    <cfset var i=0>
25    <cfset var result=QueryNew("name")>
26
27    <!--- Need java.io.File class --->
28    <cfobject type="java" class="java.io.File" name="jifObj">
29    <!--- Get drives/roots --->
30    <cfset drives=jifObj.listRoots()>
31
32    <!--- Loop through results and create query --->
33    <cfloop from="1" to="#ArrayLen(drives)#" index="i">
34        <cfset QueryAddRow(result)>
35        <cfset QuerySetCell(result, "name", drives[i].toString())>
36    </cfloop>
37
38    <!--- Return results --->
39    <cfreturn result>
40
41</cffunction>
42
43
44<!--- Get directory entries for Ajax CFTREE --->
45<cffunction name="getDirEntries" access="remote" returnType="array">
46    <cfargument name="path" type="string" required="false" default="">
47    <cfargument name="value" type="string" required="false" default="">
48
49    <!--- Init variables --->
50    <cfset var dir="">
51    <cfset var entry="">
52    <cfset var result=arrayNew(1)>
53    <cfset var filepath="">
54
55    <!--- Check if top level --->
56    <cfif ARGUMENTS.value IS "">
57        <!--- Yes, top level, get drives --->
58        <cfset dir=getDrives()>
59        <!--- Loop through dir list --->
60        <cfloop query="dir">
61            <!--- Add each drive/root --->
62            <cfset entry=StructNew()>
63            <cfset entry.value=dir.name>
64            <cfset entry.display=dir.name>
65            <cfset entry.img="fixed">
66            <cfset ArrayAppend(result, entry)>
67        </cfloop>
68    <cfelse>
69        <!--- Not top level, get dir list --->
70        <cfdirectory action="list"
71                    directory="#ARGUMENTS.value#"
72                    name="dir"
73                    sort="name">

74        <!--- Loop through dir list --->
75        <cfloop query="dir">
76            <!--- Create entry --->
77            <cfset entry=StructNew()>
78            <!--- Use full path as value --->
79            <cfset entry.value=ARGUMENTS.value & THIS.separator & dir.name>
80            <!--- Use just name for display --->
81            <cfset entry.display=dir.name>
82            <!--- Is this a file or a dir? --->
83            <cfif dir.type IS "file">
84                <!--- A file, so no children --->
85                <cfset entry.leafnode=TRUE>
86                <!--- Use document icon --->
87                <cfset entry.img="document">
88            <cfelse>
89                <!--- A dir, use folder icon --->
90                <cfset entry.img="folder">
91                <cfset entry.imgopen="folder">
92            </cfif>
93            <!--- Add it to the array --->
94            <cfset ArrayAppend(result, entry)>
95        </cfloop>
96    </cfif>
97
98    <!--- And return the results --->
99    <cfreturn result>
100</cffunction>
101
102
103</cfcomponent>

As you can see, once you get your arms around how data is formatted and passed back and forth, the new Ajax enabled HTML based <CFTREE> is both powerful and really easy to use.

Read More ›