Search my blog:
Viewing By Entry / Main
February 14, 2007

Multi-Selects Related with AjaxCFC

A friend of mine was wondering how you would do two selects related using AjaxCFC. While Rob has similar tutorials on his site, I didn't see this one specifically. It is pretty easy to accomplish since Rob includes utilities in the AjaxCFC scripts that handle things like removing and adding options to a select.

First, let's look at the component:

<cfcomponent extends="ajax">
   
   <cffunction name="getFillQuery" output="no" access="private" returntype="query">
      <cfargument name="itemID" required="true" type="numeric" />
      
      <!--- I am faking the query for test purposes --->
      <cfset var selectQuery = queryNew("display,id") />
      <!--- this would be where you do your real query --->
      <cfset var i = 0 />
      <cfloop from="1" to="5" index="i">
         <cfset queryAddRow(selectQuery) />
         <cfset querySetCell(selectQuery,"display","selected #arguments.itemID# option #i#") />
         <cfset querySetCell(selectQuery,"id",i) />
      </cfloop>
      
      <cfreturn selectQuery />
   </cffunction>
</cfcomponent>

There is not much going on here. Basically, I have a function that returns a query that, if this were real, would be filtered based upon an ID (or whatever criteria you want to send). Now, let's look at the form:

<html>
<head>
<script type='text/javascript'>
_ajaxConfig = {   '_cfscriptLocation':'/sandbox/com/selectBoxes.cfc',
            '_jsscriptFolder':'/sandbox/js',
            'debug':false};
</script>
<script type='text/javascript' src='/sandbox/js/ajax.js'></script>
<script language="javascript">
   var formItemToFill;
   function callFill(thisSelect,target) {
      formItemToFill = target;
      DWREngine._execute(_ajaxConfig._cfscriptLocation, null, 'getFillQuery',thisSelect.options[thisSelect.selectedIndex].value,fill);
   }
   function fill(r) {
      var getValue = function (thisRow) {
         return thisRow.id; // name of the value column       }
      var getText = function (thisRow) {
         return thisRow.display; // name of the display column       }
      DWRUtil.removeAllOptions(formItemToFill)
      DWRUtil.addOptions(formItemToFill, r, [getValue, getText], null);
   }
</script>
</head>
<body onload="callFill(document.forms['myForm'].foo1,'foo2');">
<form name="myForm">
   <select name="foo1" onchange="callFill(this,'foo2');">
      <option value="1">Option 1</option>
      <option value="2">Option 2</option>
      <option value="3">Option 3</option>
   </select>
   <br />
   <select name="foo2" id="foo2"></select>
</form>
</body>
</html>

My callFill() function, for lack of a better name, is called when a new option is selected in my foo1 select box. I also call this function on the page load to pre-fill my second select box (foo2) with the initially selected item. The callFill() function simple calls my getFillQuery() function in my cfc and set fill() as the callback function. Notice how I have set a global JavaScript variable here to allow you to easily set the target select box you would like to fill.

When the data is returned, we set functions to return the data from the query we want used as the value and text of our select box options. Next, we use a built in AjaxCFC utility function to remove all options from the target select box. Finally, we use another built in utility function to add new options to the target using our query data.

That's it! Pretty easy once you are familiar with AjaxCFC. You could also easily expand this to add additional related select boxes as necessary.

Comments

Thanks Brian,

This looks simple now that someone else has done the work for me! ;)

Glenn


Thanx for the example. But I'm new to AJAX and not very experienced with Javascript. I'm also Dutch, so please forgive me my English. Maybe you can help me.

Your example makes use of a numeric ID (itemID) that get passed to the selectBoxes.cfc (I suppose the number of the selected row of the 1st select box in the form). But what I need is to pass the _value_ of the selected row and that's a string (UUID). I've struggling with your code for a few hours, but don't know how to make it work.

Basically, the first selectbox is a loop from a query that outputs something like:
<select name="Parent">
<cfloop query="getParentIitems">
<option value="#ParentID#">#ParentTitle#</option>
</cfloop>
</select>

Then in selectBoxes.cfc I want to catch the value of the selection (#ParentID#" for the second query, something like:
<cfquery ...>
SELECT *
FROM Child
WHERE ParentID = '#arguments.Parent#'
</cfquery>

Does this makes sense?

rene


Rene, ItemID is a numeric id from the db not the items position in the options array. Therefore, the code *should* not care whether this is a uuid or a numeric id since the JavaScript never specifically references it as though it were numeric. You will just need to change the names of the columns to fit your column names in the getValue and getText methods. Some of the ColdFusion code would change because of the reference, but that should be fairly self-explanatory. What problem are you running in to.


Brian,

the 2nd selectbox returns "undefined" when a record was found in the CFC and nothing when no record was found.

you use "id" and "display" in the Javascript. When I query the database in the CFC I want to get the columns "ID" and "Titel". So I replaced your code with:

return thisRow.ID; // name of the value column

and

return thisRow.Titel; // name of the display column

my query in the CFC is straight forward:

<cffunction name="getFillQuery" output="no" access="private" returntype="query">
<cfargument name="itemID" required="true" type="string" />
<cfquery name="selectQuery" datasource="#application.config.DSN#">
SELECT ID, Titel
FROM toolkit_subdoelgroepen
WHERE DoelgroepID = '#arguments.itemID#'
ORDER BY Titel
</cfquery>
<cfreturn selectQuery />
</cffunction>


Rene,

Simply based upon what you have provided I cannot see anything obviously wrong with your code. I would throw something like alert(thisRow.titel) in the getText() JS method (same for the id). This should create a popup with the values. If they still come up undefined, my best guess would be that you are running into some case-sensitivity issue here. Try something like thisRow.TITEL because sometimes CF returns query rows in all caps.


Hello, at first I'd like to thanks for sharrin, and say tht's a nice code.

So, I'm tryin to modify for my case, I need 3 select boxes...

The first I have parameters like size... 1/2/3

In the DB, I will select the colors of the tips of clothes that is in arguments.itemID

Ok, I had the 1st and 2nd steps, but Now I wanna a 3rd select box, that I wanna the values from the first and secund select

like

<cfqyery>
Select * from tbl where size in (1stfield) and color in (2fiel).
</cfquery>


I dont know so much about JS, Ajax Neither, so, how can I return the next variables?


@Roberto Just repeat the steps here with an onchange on the second select box that calls another JS function that calls another method on your CFC. If you are running CF8, I have also shown how to do this (a little easier IMO) with cfajaxproxy which you can find at http://www.remotesynthesis.com/blog/index.cfm/2008/3/4/Multiselects-Related-with-CFAjaxProxy


Ow, Thanks again
I've done it and it's right, changed the type to string, this way I could concat 2 variables and separate the information to acess another database.

Thanks