Friday, March 19, 2010    
Home My Books Blog ColdFusion About Me Back    

Calendar
<< Aug 2006 >>
S M T W T F S
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

Search

Categories
 • Acrobat (3) [RSS]
 • Adobe (90) [RSS]
 • AdobeMAX06 (45) [RSS]
 • AdobeMAX07 (59) [RSS]
 • AdobeMAX08 (66) [RSS]
 • AdobeMAX09 (39) [RSS]
 • AdobeMAX10 (1) [RSS]
 • AIR (219) [RSS]
 • Appearances (191) [RSS]
 • Books (72) [RSS]
 • CFEclipse (15) [RSS]
 • ColdFusion (1381) [RSS]
 • Data Services (34) [RSS]
 • Fish Tank (5) [RSS]
 • Flash (197) [RSS]
 • Flex (498) [RSS]
 • Home Automation (5) [RSS]
 • Jobs (116) [RSS]
 • JRun (14) [RSS]
 • Labs (43) [RSS]
 • LiveCycle (34) [RSS]
 • MAX (232) [RSS]
 • Mobile (120) [RSS]
 • Regular Expressions (17) [RSS]
 • RIA (21) [RSS]
 • SQL (40) [RSS]
 • Stuff (536) [RSS]
 • Tips (CF Studio) (80) [RSS]
 • Tips (CF) (795) [RSS]
 • Tips (Dreamweaver) (91) [RSS]
 • Tips (Flex Builder) (2) [RSS]
 • Using CF (162) [RSS]

Other BLOGs
 • Charlie Arehart
 • Lee Brimelow
 • Ray Camden
 • Christophe Coenraets
 • Sean Corfield
 • Mihai Corlan
 • Cornel Creanga
 • Mark Doherty
 • John Dowdell
 • Danny Dura
 • Enrique Duvos
 • Steven Erat
 • Kevin Hoyt
 • Serge Jespers
 • Adam Lehman
 • Duane Nickull
 • Miti Pricope
 • Andrew Shorten
 • Ryan Stewart
 • James Ward
 • Greg Wilson
 • Full As A Goog

RSS Feeds
 • Feed
 • Subscribe

Join my mailing list and find out about new books and other topics of interest.

Thoughts, ideas, tips, musings, and pontifications (not necessarily in that order) by Ben Forta ...
NOTE: This is my personal blog, and the opinions and statements voiced here are my own.

Viewing By Entry / Main
August 24, 2006

Building Flex Tree MXML From ColdFusion Queries

ColdFusion queries are flat and non-hierarchical. The Flex <mx:Tree> control supports data specified in a variety of formats, all of which are hierarchical and not flat. Each time I need to populate a Tree control with dynamic query data I find myself jumping through hoops to convert the data into something Flex can use.

The truth is, what I want is something as simple as ColdFusion's <cftree> tag. That tag lets you pass it a query and a list of columns which it uses to build the nested branches. It does not work for every situation (if you have different levels of nesting, for example) but it does work for most tree driven queries.

So, how do you programmatically build nested XML based on ColdFusion queries?

The simplest solution would be to use <cfoutput group="..." in a <cfxml> block. That way you wouldn't have to figure out the nesting levels, <cfoutput>l does that for you. But that will only work for a fixed number of nested groups, you could use dynamic group names but you'd need to hardcode that number of nested groups because there is no way to programmatically add nesting levels on-the-fly. So much for the simple option.

The next option would be to loop through the data manually, creating a new XML object with XMLNew(), and then adding branches with XMLElemNew(). I spent way too much time on this idea only to discover that it's just not practical. ColdFusion's XML manipulation functions make it really easy to add nodes as long as you know where you want to add them, but there is no easy way to add child elements at arbitrary locations (at unknown depths), and there is no way to search an XML object returning a handle that could be used to create a child at the found location. So, no go.

I tried several other ideas too, until I came up with what follows. Now, before I go further I must state that this is a work in progress. The code is not perfect yet, but it does work, it is flexible, and as I have never seen ColdFusion code that uses query-of-queries recursion like this before I just had to post it in its current state.

So, here goes. The following code block contains two UDFs. BuildTreeXMLProcess() is an internal function is should never be called directly. BuildTreeXML() is the main entry point. You pass it a ColdFusion query, a list of columns specifying your nesting order, and an optional top-level identifier (the default root label is "root"). So, if your query had columns company, department, name, phone, and e-mail, and you wanted the tree to list company at the top level, department beneath it, and name beneath that, you'd specify "company,department,name" as the cols value (it behaves just like <cftree>).

<!--- Internal query recursion function --->
<cffunction name="BuildTreeXMLProcess" returntype="string" access="private" output="false">
    <cfargument name="data" type="query" required="yes">
    <cfargument name="cols" type="string" required="yes">
    <cfargument name="where" type="string" required="no" default="0=0">

    <!--- Local vars --->
    <cfset var result="">
    <cfset var distinctData="">
    <cfset var subData="">
    <cfset var whereClause="">

    <!--- Check if have any more columns in this branch --->
    <cfif ListLen(ARGUMENTS.cols)>

        <!--- Get distinct values for this column --->
        <cfquery dbtype="query" name="distinctData">
        SELECT DISTINCT #ListFirst(ARGUMENTS.cols)# AS col
        FROM ARGUMENTS.data
        WHERE #PreserveSingleQuotes(ARGUMENTS.where)#
        </cfquery>
    
        <!--- Loop through distinct data --->
        <cfloop query="distinctData">
            <cfset whereClause = ARGUMENTS.where & " AND " & ListFirst(ARGUMENTS.cols) & " = '" & col & "'">
            <cfquery dbtype="query" name="subData">
            SELECT *
            FROM ARGUMENTS.data
            WHERE #PreserveSingleQuotes(whereClause)#
            </cfquery>
            <!--- Any more columns in this branch? --->
            <cfif ListLen(ListRest(ARGUMENTS.cols))>
                <!--- Yes, create a node and recurse --->
                <cfset result=result & "<node label=""#col#"">">

                <cfset result=result & BuildTreeXMLProcess(subData, ListRest(ARGUMENTS.cols), whereClause)>
                <cfset result=result & "</node>">
            <cfelse>
                <!--- No, create node and populate with all columns --->
                <cfset result=result & "<node label=""#col#""">
                <cfset result=result & BuildTreeXMLProcess(subData, ListRest(ARGUMENTS.cols), whereClause)>
                <cfset result=result & "/>">
            </cfif>
        </cfloop>

    <cfelse>

        <!--- Bottom of this branch --->
        <cfquery dbtype="query" name="subData">
        SELECT *
        FROM ARGUMENTS.data
        WHERE #PreserveSingleQuotes(ARGUMENTS.where)#
        </cfquery>
        <!--- Write all columns as name="value" pairs --->
        <cfloop list="#subData.ColumnList#" index="column">
            <cfset result = result & " #LCase(column)#=""#subData[column][1]#"" ">
        </cfloop>

    </cfif>

    <!--- And return it --->
    <cfreturn result>
</cffunction>


<!--- Build Flex Tree XML from a query --->
<cffunction name="BuildTreeXML" returntype="xml" access="public" output="no">
    <cfargument name="data" type="query" required="yes">
    <cfargument name="cols" type="string" required="yes">
    <cfargument name="root" type="string" required="no" default="root">

    <!--- Local vars --->
    <cfset var xmlTree="">

    <!--- Populate XML object --->
    <cfxml variable="xmlTree">
    <cfoutput>
    <node label="#ARGUMENTS.root#">
    #BuildTreeXMLProcess(data, cols)#
    </node>
    </cfoutput>
    </cfxml>
    
    <!--- And return it --->
    <cfreturn xmlTree />

</cffunction>

Now, I did say that the code is not quite done yet. The biggest limitation for now is that the columns listed in cols must all be string values (no numbers or dates). I'll fix this at some point. For now, if you need to populate Flex Tree controls with ColdFusion query data, this may help.

If you have comments or suggestions, I'd love to hear them.

Related Blog Entries

TrackBacks
There are no trackbacks for this entry.

No trackback URL. Trackbacks are only allowed via interactive form.

Comments

  © Copyright 1997-2009 Ben Forta, All Rights Reserved