Selecting default items using ColdFusion 8's AJAX Controls
So here is an interesting question. Take a look at Ben's post on related selects in ColdFusion 8 (and don't forget it is even easier now). Pretty simple, right? Well how do you set a control to use Ajax and have a default selected item? Turns out this isn't so easy.
The first thing I tried was selected=, figuring that would be the easiest solution, but unfortunately it didn't work.
I then tried ajaxOnLoad, thinking maybe I could set the default myself. But ajaxOnLoad() is fired when the page is complete, but before those Ajax calls to populate the drop downs are done.
I then tried onLoad in the cfform tag. This didn't work at all. So I threw the problem over to Todd Sharp, and together we were able to come up with a solution. It ain't pretty, but it works. My hope is that folks can look at this and suggest something nicer. I'm actually a bit surprised this isn't supported out of the box. It seems like I use forms half the time to edit content, not create content, so being able to set defaults is a must.
Anyway - the code:
<cfajaxproxy bind="javascript:test({mediaid},2)">
<head>
<script>
var imdone = false;
function test(x,val) {
if(!imdone) {
var dd = document.getElementById('mediaid');
for(var i = 0; i < dd.length; i++){
if(dd.options[i].value == val){
dd.selectedIndex = i;
}
}
imdone = true;
}
}
</script>
</head>
<cfform >
<table>
<tr>
<td>Select Media Type:</td>
<td><cfselect name="mediaid" id="mediaid"
bind="cfc:art.getMedia()"
bindonload="true" value="mediaid" display="mediatype" /></td>
</tr>
<tr>
<td>Select Art:</td>
<td><cfselect name="artid"
bind="cfc:art.getArt({mediaid})" value="artid" display="artname" /></td>
</tr>
</table>
</cfform>
So first off - note the use of cfajaxproxy. It is bound to the first drop down. When the value changes, and this occurs on initial load, code is run to set the default. In this case note the hard coded value of 2. This would be #form.selected# or whatever. Also note the use of a variable to remember that the default value has been selected. The cfajaxproxy will always run on change, so we want to be sure it is run only once.
Thoughts? This code only supports one selected item, and only supports defaulting the left control, but obviously it could be extended to handle both. Again though it is a bit disappointing that essentially one line of code:
cfselect name="mediaid" id="mediaid" bind="cfc:art.getMedia()" bindonload="true" value="mediaid" display="mediatype" />
had to be extended by about 10 lines of JavaScript. To be fair, my beloved Spry doesn't make this much easier. You can use spry:if type conditionals so it is a bit slimmer. Maybe someone can speak to how other frameworks like Prototype does it?
Edit: A followup post by Todd: Selecting Multiple Default Items With ColdFusion 8 Ajax Controls
Comments
http://cfsilence.com/blog/client/index.cfm/2007/8/...
The docs are here: http://extjs.com/deploy/ext/docs/ for anyone to go through. I looked at them briefly but saw nothing off the top of my head. Will take another look after the kids go down for the night.
I tried updating the javascript by adding the same logic to the 2nd select but it doesn't work.
Thanks.
<pre>
<script>
function initSelect(){
var dd2 = document.getElementById('Model');
for(var i = 0; i < dd2.length; i++){
//loop over the array of selectedItems
if(dd2.options[i].value == '<cfoutput>#form.defaultvalue#</cfoutput>'){
dd2.options[i].selected = true;
}
} ;
}
setTimeout("initSelect()",1000)
</script>
</pre>
I've created a .js file that "overrides" a function from coldfusion's cfajax.js library.
The new version of the function allows you to specify which options are selected as part of the array you return from your "bound" .cfc function.
See my site for details.
http://www.realitystorm.com/experiments/coldfusion...
<cfcomponent>
<cffunction name="get" access="remote">
<cfargument name="TABLE_NAME" default="">
<cfargument name="VARIABLE_NAME" default="">
<!--- this is kind of lame, but cold fusion will not set the selected variable!!!!! --->
<cfargument name="SELECTED" default="" type="String">
<cfset answer=ArrayNew(2)>
<!--- this is to set the "selected" item first; so that they're um.. selected. --->
<cfset iter = 2>
<cfset answer[1][1]=selected>
<cfset answer[1][2]=selected>
<cfloop index="i" from="1" to="#data.RecordCount#">
<cfif ucase(trim(data.value[i])) neq ucase(trim(selected))>
<cfset answer[iter][1]=data.value[i]>
<cfset answer[iter][2]=data.display[i]>
<cfset iter = iter + 1>
<cfelse>
<!--- ooo! we have the label of the first selected value! --->
<cfset answer[1][2]=data.display[i]>
</cfif>
</cfloop>
<cfreturn answer>
</cffunction>
Ray, if you see this comment, how can Steve's solution be passed on to Adobe for possible inclusion in the next update of CF8?
Can you please give us an example how did you work around with Steve Savage's solution? I'm not sure how to use his new function.
Thanks,
George
I am using the function just for a single cfselect which is using javascript bind.
My example uses a product type/category/sub category hierarchy.
I added an argument to each of the functions in the cfc for the ID of the value I wanted to select:
<!--- Get array of product types --->
<cffunction name="getProductTypes" access="remote" returnType="array">
<cfargument name="typeID" type="numeric">
...
<!--- Get category by product type --->
<cffunction name="getCategories" access="remote" returnType="array">
<cfargument name="typeid" type="numeric" required="true">
<cfargument name="catID" type="numeric">
...
<!--- Get sub category by category --->
<cffunction name="getSubCategories" access="remote" returnType="array">
<cfargument name="categoryID" type="numeric" required="true">
<cfargument name="subCatID" type="numeric">
...
In each of the functions I added a cfif to the query to array loop to set the new array element to true for the passed ID:
<!--- Convert results to array --->
<cfloop index="i" from="1" to="#getTypes.RecordCount#">
<cfset result[i][1]=getTypes.typeID[i]>
<cfset result[i][2]=getTypes.type[i]>
<cfif getTypes.typeID[i] is arguments.typeID>
<cfset result[i][3]=true>
</cfif>
</cfloop>
...
(repeat for categories and sub categories)
Then on the display page I included Steve's magic JavaScript and added the arguments to each of the cfselect tags:
<cfselect name="typeID" bind="cfc:typeCatSubCat.getProductTypes(#typeID#)" bindonload="true" />
...
<cfselect name="catID" bind="cfc:typeCatSubCat.getCategories({typeID},#catID#)" />
...
<cfselect name="subCatID" bind="cfc:typeCatSubCat.getSubCategories({catID},#subCatID#)" />


<cfajaxproxy bind="javascript:test({mediaid},'[2,3,4]' )">
<script>
var imdone = false;
function test(x,val) {
if(!imdone) {
var dd = document.getElementById('mediaid');
valArr = ColdFusion.JSON.decode(val);
for(var i = 0; i < dd.length; i++){
//loop over the array of selectedItems
for(var j = 0; j < valArr.length; j++){
if(dd.options[i].value == valArr[j]){
dd.options[i].selected = true;
}
}
}
imdone = true;
}
}
</script>