Contact Doug!
Learn About Doug!
View Doug Boude's online resume
updated 11/18/2009

View Doug Boude's profile on LinkedIn
Link to me!

Follow Doug Boude on Twitter
Follow me!

Be Doug's friend on Facebook
Befriend me!
(I promise not to follow you home)
OO Lexicon
Chat with Doug!
Recent Entries
You may also be interested in...
Florida web site design



Czech your Page Rank!
Check Page Rank of any web site pages instantly:
This free page rank checking tool is powered by Page Rank Checker service
Surf's Up!
Visit Egosurf.org and massage YOUR web ego!
My Score: 9,001
Doug's Books

Read (and recommend)

  • Men are from Mars, Women are from Venus
  • The Wisdom of Crowds: Why the Many Are Smarter Than the Few and How Collective Wisdom Shapes Business, Economies, Societies and Nations
  • Blink: The Power of Thinking Without Thinking
  • Head First Design Patterns
  • Transact-SQL Programming
  • What's So Amazing About Grace?
  • Just So Stories (Rudyard Kipling collection)

Reading

  • Prayer: Does it Make Any Difference?
  • Data Mining (Practical Machine Learning Tools and Techniques)
<< March, 2010 >>
SMTWTFS
123456
78910111213
14151617181920
21222324252627
28293031
Search Blog

Recent Comments
Categories
Archives
Photo Albums
Funnies (5)
Family (3)
RSS

Powered by
BlogCFM v1.11

09 March 2008
Client-Side Drilldowns Made Easy
Last September I shared a post on an alternative to Ajax for client-side interactivity leveraging Coldfusion's WDDX. I'd like to take it a step further now and share an approach (and corresponding code) I often use in my Model-Glue apps when needing to create tiered or drilldown-type select lists withOUT having to make numerous calls to the server. The gist of this methodology is the same as in my previous post:
  1. Retrieve all needed data sets for populating the drilldowns;
  2. Convert those CF queries to a form that Javascript can manipulate;
  3. Write the necessary functions to populate the dropdowns based on previous options selected.

Here is the working sample. Go ahead, play with it!





The main difference in this example is the fact that I'm not utilizing WDDX, but rather JSON to make the data Javascript friendly.

Let me show you what the queries look like for each tier of this particular drilldown:
Tier 1Tier 2Tier 3

An important item of note here is that the data set for a given tier must include the parent ID of the previous tier in order to perform the filtering operations you'll see soon.

Okay, so how to get the queries into a format Javascript can manipulate. The route I chose to go was to convert the queries to JSON via one of two methods, depending on what version of CF you're using. Since not everyone is using version 8 yet, I made this example compatible with anything 6 or higher (versions previous to 8 are dependent on outside conversion; I chose to use JSON.CFC. Version 8 can utilize the built in function "serializeJSON"). Here's the statement where the queries are transformed (using the commonly known custom tag "QuerySim" to create the data for this example):

<cf_querysim>
    level_1
    level_1_id,name
    1|Colors
    2|Shapes
    3|Foods
</cf_querysim>
<cf_querysim>
    level_2
    level_2_id,level_1_id,name
    1|1|Red
    2|1|Blue
    3|1|Yellow
    4|2|Triangle
    5|2|Square
    6|2|Circle
    7|3|Bread
    8|3|Meat
    9|3|Fruit
</cf_querysim>
<cf_querysim>
    level_3
    level_3_id,level_2_id,name
    1|1|Fuschia
    2|1|Brick Red
    3|1|InfraRed
    4|2|Teal
    5|2|Cyan
    6|2|Navy Blue
    7|3|Light Yellow
    8|3|Dark Yellow
    9|4|Isosceles
    10|4|Equilateral
    11|4|Right Triangle
    12|5|Rectangle
    13|5|Parallelogram
    14|6|Ellipse
    15|6|Oval
    16|7|Matzah
    17|7|Hot Cross Buns
    18|7|Brioche
    19|8|Steak
    20|8|Fajitas
    21|8|Hamburger
    22|9|Kiwi
    23|9|Grapes
    24|9|Oranges
</cf_querysim>

<!--- convert our query data to JSON strings...mind the second parameter to the serializeJSON function... --->

<cfif listfirst(SERVER.ColdFusion.ProductVersion) gt 7>
    <cfset level_2_json = serializeJSON(level_2, true) />
    <cfset level_3_json = serializeJSON(level_3, true) />
<cfelse><!--- we're on less than version 8. use json.cfc --->
    <cfset objJSON = createobject("component","json") />
    <cfset level_2_json = replace(objJSON.encode(level_2),"data","DATA","all") />
    <cfset level_3_json = replace(objJSON.encode(level_3),"data","DATA","all") />
</cfif>

<script>
    //set our json data into Javascript objects
    var lev2data = <cfoutput>#level_2_json#</cfoutput>;
    var lev3data = <cfoutput>#level_3_json#</cfoutput>;
</script>

You'll notice in the section where I rely on JSON.CFC, I am doing a replace on the lower case word "data" to make it upper case. This is to make the JSON string produced consistent with the one produced utilizing serializeJSON. Since Javascript is case sensitive, case consistency in the JSON is required if you wish to utilize only one javascript function to perform the select list population. You'll also notice the use of the optional secondary parameter in the serializeJSON function call. This is needed in order to produce a JSON string that can be accessed by Javascript exactly the same as the JSON.CFC string.

MG NOTE: Regarding the fact that I utilize this approach in Model-Glue apps...I have my controller return an already formatted JSON string to my view rather than return a query and then perform the transformation there. Many of my controller methods have an optional "returnJSON" argument that I use when I need a JSON string back rather than a query.

Okay, data sets are available to Javascript. Now to write a few Javascript functions that can spin through that data and populate the appropriate select list. Here are the functions needed to perform the necessary tasks:

<script>
    //function to repopulate targeted select list
    function repopulate(targetObjID,targetDataSet,selectedIDVal, idColName, valColName, optionValColName){
        /*
         parameters:
         targetObjID - the ID of the select list we want to populate;
         targetDataSet - the actual Javascript data object we created previously;
         selectedIDVal - the name of the column in this data set that contains the parent record ID
         idColName - the name of the column that contains THIS tier's own record ID
         valColName - the name of the column that contains the data we want to display as the option text in the dropdown;
         optionValColName - the name of the column that contains the value we want to use as the new options's VALUE value;
        */
       
        //loop over the data object. for every object with a keyname of idval, add it to the dropdown
        var objTarget = document.getElementById(targetObjID);
        ResetObject(objTarget);
        for(i=0;i<targetDataSet.DATA[idColName].length;i++){
            if(targetDataSet.DATA[idColName][i] == selectedIDVal){
                objTarget[objTarget.options.length] = new Option(targetDataSet.DATA[valColName][i].substring(0,45),targetDataSet.DATA[optionValColName][i],false,false);
            }
        }
    }
    //function to clear a dropdown
    function ResetObject(objTarget){
            objTarget.options.length=0;
            objTarget.options[0] = new Option("---------------","",false,false);
            return;
    }

function resetAll(objIDList){//empty out all of the dropdowns specified in objIDList
        var idlist = objIDList.split(",");
        for (i=0;i<idlist.length;i++){
                ResetObject(document.getElementById(idlist[i]));
        }
        return;
}
    
</script>


Of note is the fact that we are working with three select lists. The first select list is always populated at load time and needs no Javascript intervention at all. Select lists "level_2" and "level_3", however, are being completely manipulated by the JS calls by one generically written function.

Last but not least, the HTML with the Javascript calls embedded in the select lists' onChange event:

<body>
    <h2>Client-Side Drilldown Example</h2>
    Level 1: <select name="level_1" id="level_1" onChange="resetAll('level_2,level_3');repopulate('level_2',lev2data,this.options[this.selectedIndex].value,'level_1_id','name','level_2_id');">
        <option value=""><cfoutput>#repeatstring("-",15)#</cfoutput></option>
        <cfoutput query="level_1">
            <option value="#level_1_id#">#name#</option>
        </cfoutput>
    </select>
    <hr width="25%" align="left">
    Level 2:
    <select name="level_2" id="level_2" onChange="resetAll('level_3');repopulate('level_3',lev3data,this.options[this.selectedIndex].value,'level_2_id','name','level_3_id');">
        <option value=""><cfoutput>#repeatstring("-",15)#</cfoutput></option>
    </select>
    <input type="button" value="show ID" onClick="alert(document.getElementById('level_2').options[document.getElementById('level_2').selectedIndex].value);">
    <hr width="25%" align="left">
    Level 3:
    <select name="level_3" id="level_3" >
        <option value=""><cfoutput>#repeatstring("-",15)#</cfoutput></option>
    </select>
    <input type="button" value="show ID" onClick="alert(document.getElementById('level_3').options[document.getElementById('level_3').selectedIndex].value);">
</body>

Voila! That's it! instant, client-side drilldown with only a single call to the server!

Hope it saves someone a little time. ;)

Doug out.



Posted by dougboude at 10:46 PM | PRINT THIS POST! |Link | 0 comments
Subscription Options

You are not logged in, so your subscription status for this entry is unknown. You can login or register here.

No comments found.

Name:   Required
Email:   Required your email address will not be publicly displayed.

Want to receive notifications when new comments are added? Login/Register for an account.

Time to take the Turing Test!!!

14 plus 2 equals
Type in the answer to the question you see above:

Your comment:

Sorry, no HTML allowed!