Ask a Jedi: AjaxProxy example

This post is more than 2 years old.

Mike asks:

I'm trying to do someting i think may be simple, but its method is eluding me. I want to get to grips with using the cfajaxproxy tag to allow me to have a cfselect that when selecting values will auto populate some information into other fields, having pulled the info from the db.

I've blogged about cfajaxproxy before and it's truly one of the most amazing tags ever added to the language. I quickly built up an example of what I think Mike was talking about. I first started with related selects that spoke to the cfartgallery demo datasource. I started with this since Forta had already written the code. ;)

Here is what I began with:

<cfform name="main"> <b>Media:</b> <cfselect bind="cfc:art.getMedia()" bindonload="true" value="mediaid" display="mediatype" name="media" /> <b>Art:</b> <cfselect bind="cfc:art.getArt({media})" bindonload="true" value="artid" display="artname" name="art" />

Nothing too complex here. You select media and you get art populated in the second drop down. Now this is where I want to demonstrate the use of cfajaxproxy. I added a few form fields and other content:

<p> <b>Description:</b> <cftextarea name="desc" id="desc" /><br> <b>Price:</b> <cfinput name="price" id="price" /><br> <span id="img"></span> </cfform>

I have 3 areas now. The first two, description and price, I want to populate with the description and price of the art. The span, img, will be populated with the art image.

The first thing I want to do is bind to the art drop down. This will let me say, "When the art drop down changes, do something."

<cfajaxproxy bind="cfc:art.getArtDetail({art.value})" onSuccess="showDetail">

In this example, I've bound to the Art drop down, and I've called a CFC (the same CFC as before) to get more information about the art piece. I've then told the tag to run showDetail when done. That JavaScript function is rather trivial:

<script> function showDetail(r) { document.getElementById("desc").value = r.DESCRIPTION; document.getElementById("price").value = r.PRICE; var newbod = "<img src='http://localhost/cfdocs/images/artgallery/" + r.LARGEIMAGE + "'>"; document.getElementById("img").innerHTML = newbod; } </script>

Since my CFC method returns a struct, I can treat it in JavaScript pretty much the same way I treat it in ColdFusion.

Here is the complete CFC code:

<cfcomponent output="false">

<cfset variables.dsn="cfartgallery">

<!--- Get array of media types ---> <cffunction name="getMedia" access="remote" returnType="query"> <!--- Define variables ---> <cfset var data="">

 &lt;!--- Get data ---&gt;
 &lt;cfquery name="data" datasource="#variables.dsn#"&gt;
 SELECT mediaid, mediatype
 FROM media
 ORDER BY mediatype
 &lt;/cfquery&gt;

 &lt;!--- And return it ---&gt;
 &lt;cfreturn data&gt;

</cffunction>

<!--- Get art by media type ---> <cffunction name="getArt" access="remote" returnType="query"> <cfargument name="mediaid" type="numeric" required="true">

 &lt;!--- Define variables ---&gt;
 &lt;cfset var data=""&gt;

 &lt;!--- Get data ---&gt;
 &lt;cfquery name="data" datasource="#variables.dsn#"&gt;
 SELECT artid, artname
 FROM art
 WHERE mediaid = &lt;cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.mediaid#"&gt;
 ORDER BY artname
 &lt;/cfquery&gt;

 &lt;!--- And return it ---&gt;
 &lt;cfreturn data&gt;

</cffunction>

<cffunction name="getArtDetail" access="remote" returnType="struct"> <cfargument name="artid" type="numeric" required="true"> <cfset var data=""> <cfset var c = ""> <cfset var s = structNew()>

 &lt;!--- Get data ---&gt;
 &lt;cfquery name="data" datasource="#variables.dsn#"&gt;
 SELECT *
 FROM art
 WHERE artid = &lt;cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.artid#"&gt;
 &lt;/cfquery&gt;

 &lt;cfloop list="#data.columnlist#" index="c"&gt;
	 &lt;cfset s[c] = data[c][1]&gt;
 &lt;/cfloop&gt;
 
 &lt;!--- And return it ---&gt;
 &lt;cfreturn s&gt;

</cffunction>

</cfcomponent>

There isn't anything really complex here. Now here is the complete test document I used:

<cfajaxproxy bind="cfc:art.getArtDetail({art.value})" onSuccess="showDetail">

<script> function showDetail(r) { document.getElementById("desc").value = r.DESCRIPTION; document.getElementById("price").value = r.PRICE; var newbod = "<img src='http://localhost/cfdocs/images/artgallery/" + r.LARGEIMAGE + "'>"; document.getElementById("img").innerHTML = newbod; } </script>

<cfform name="main"> <b>Media:</b> <cfselect bind="cfc:art.getMedia()" bindonload="true" value="mediaid" display="mediatype" name="media" /> <b>Art:</b> <cfselect bind="cfc:art.getArt({media})" bindonload="true" value="artid" display="artname" name="art" /> <p> <b>Description:</b> <cftextarea name="desc" id="desc" /><br> <b>Price:</b> <cfinput name="price" id="price" /><br> <span id="img"></span>

</cfform>

Pay special attention to how simple the code is here. The only JavaScript isn't really that complex.

I hope this example helps.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Andrew posted on 1/31/2008 at 7:17 PM

Ray,

I can follow the code for the most part, but what I'm not understanding is the instantiation of the JavaScript function. Specifically this line:

function showDetail(r){

What does the "r" refer to and how does the JavaScript function know when to fire? Should <cfajaxproxy> have an id or name equal to r?

Comment 2 by Raymond Camden posted on 1/31/2008 at 7:20 PM

r is the result from the server. It can be anything. In my case, r is a struct (look at the CF code).

Comment 3 by Che posted on 1/31/2008 at 7:31 PM

I'm with Andrew. Where is 'r' defined? The CFC that is bound by the cfajaxproxy tag (getArtDetail) returns a struct 's' but is not set to a variable that I can see. What am I missing?

Could you just call it 'bozo' if you wanted to instead of 'r' as long as the 'onSuccess' value matches the function name?

Comment 4 by Raymond Camden posted on 1/31/2008 at 7:38 PM

Yes, you can call it whatever. This is no different than a UDF in CF. Imagine this simple UDF:

<cfscript>
function hi() { var msg = "Hi world"; return msg; }
</cfscript>

<cfset result = hi()>

In my udf that variable is called msg, but all I really return is the data.

Comment 5 by sean posted on 1/31/2008 at 11:57 PM

Ray,

Let's say you had a cfc similar to yours execpt that there was also an init() method through which you passed the dsn and placed it in variables scope, which the other methods would reference as needed. Pretty standard fare, right?

But I can't seem to get this to work using cfajaxproxy. can you initialize a cfc into memory and then bind to it? I know there's an easy answer to this and I'm going to feel really dumb...

Comment 6 by Raymond Camden posted on 2/1/2008 at 12:28 AM

Heh, I blogged on this about 8 days ago. :)

http://www.coldfusionjedi.c...

Comment 7 by sean posted on 2/1/2008 at 12:31 AM

Perfect - thank you, Ray.

Comment 8 by RDB posted on 2/1/2008 at 2:47 AM

When I tried using the <cfselect>s as in your example, I got the error:

The value of the QUERY attribute is invalid. The 'Query' attribute must be defined if the 'Value', 'Display', or 'Group' attributes are defined.

The <cfselect>s have to be pared down to include only the bind, bindonload and name parameters in order to work properly. Or am I missing something?

Comment 9 by Raymond Camden posted on 2/1/2008 at 2:52 AM

Odd, it works just fine locally. You are using CF8, right?

Comment 10 by RDB posted on 2/1/2008 at 3:18 AM

Yep, and as soon as I remove the "value" and "display" parameters it works fine.

Comment 11 by Raymond Camden posted on 2/1/2008 at 4:00 AM

All I can suggest is checking to ensure you don't have an older CF8. Make sure you have final.

Comment 12 by Andrew posted on 2/1/2008 at 4:23 AM

Ray,

You were right...you don't have to define r to use it in the function. When I got home I read the section in CFWACK VOl II, then I tried your code and it worked, except for the fact that not all media are represented in the art table. For those media that do not have art associated with it, I get an error. Other than that, I now understand how the code works and look forward to the opportunity to use it in my apps when it's appropriate.

Comment 13 by Joshua Curtiss posted on 2/3/2008 at 10:47 AM

Awesome. Straightforward example that shows how powerful ajaxproxy is. Thanks.

Comment 14 by Richard Baldwin posted on 3/21/2008 at 9:30 PM

Hey Gang,

I'm trying to bind a select box to two different buttons but so that the values in the select box will update anytime either of the two buttons are pressed. The only way I could get this to work was to pass useless variables to my cfc so I could use the @click handler. Is there another way to do this without have to pass unneeded variables?

<cfform name="phoneRequestForm">
<input name="addPhoneButton" type="button" value="Add Phone" onclick="insertPhone();" />
<input name="deletePhoneButton" type="button" value="Delete Phone" onclick="deletePhone();" />
<cfselect id="AvailableIDs" name="AvailableIDs" bind="cfc:phoneRequestMain.SelectAvailablePhones({addPhoneButton@click},{deletePhoneButton@click})" bindonload="true" value="PhoneID" display="PhoneID" />
</cfform>

Ray - Love the blog... it has been a great help to me.

Comment 15 by Raymond Camden posted on 3/21/2008 at 9:37 PM

So wait - are you saying you want the button click to call a CFC, but you don't want to pass any data? I'd probably just use onClick, and call a JS function that makes use of AjaxProxy to call then CFC.

Comment 16 by Richard Baldwin posted on 3/21/2008 at 10:07 PM

Right, my other js functions (insertPhone,deletePhone) uses AjaxProxy to update the database and I want to reflect those changes in my select that is bound to a cfc that just returns a list of phones. I could update the select in the callbackhandler for insertPhone and deletePhone (by making another ajax call to my SelectAvailablePhones cfc) but I thought binding would be neater. It seems like the purpose of binding is attach an element to changing data but maybe in this case it is more for binding to other changing elements.

Comment 17 by Howard posted on 4/2/2008 at 8:27 PM

I get an errors when i try to run this example:

Error invoking CFC /art.cfc: The MEDIAID argument passed to the getArt funtion is not of type numeric

Error invoking CFC /art.cfc: The ARTID argument passed to the getArt funtion is not of type numeric

Comment 18 by Raymond Camden posted on 4/2/2008 at 11:39 PM

Can you show me this anywhere online Howard? I know it works for most folks.

Comment 19 by siby posted on 4/16/2008 at 7:40 PM

Howard, I too get the same errors. I tried Forta's example and got the same errors.

Comment 20 by David Neale posted on 5/3/2008 at 9:26 AM

Error invoking CFC /art.cfc: The MEDIAID argument passed to the getArt funtion is not of type numeric

Error invoking CFC /art.cfc: The ARTID argument passed to the getArt funtion is not of type numeric

I get the same error bit I have no problem with Fortas example where he converts the query result to an array.

I'm running this on my local machine so I can't show anybody but I'm using Rays' code exactly.

Comment 21 by Raymond Camden posted on 5/3/2008 at 6:31 PM

If you can find a way to get it online I'd appreciate it. Otherwise I'm not sure what to recommend.

Comment 22 by todd posted on 5/15/2008 at 7:29 AM

I'm at a complete loss. I've stripped this code down as far as it can possibly go, and there is nothing I can do to get the onSuccess function to fire. This is the HTML in its entirety:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1...">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>

<script>
function noteRefresh(theResult) {
alert('noteRefresh');
}
</script>
</head>

<body>

<cfform name="theForm">
<cfinput src="../img/pin_green.gif" value="7" type="image" id="pin7" name="pin7" width="16" height="16" alt="Subject 7">
</cfform>

<cfajaxproxy bind="cfc:cfc.getNote({pin7.value@mouseover})" onSuccess="noteRefresh" />

</body>
</html>

The cfc, which does nothing but return a string returns its string, according to the ?CFDEBUG debugger. Yet the onSuccess does not fire. FireBug breakpoints to not trip, nothing happens. I've moved the tags around, changed everything it's possible to change and still nothing. I even put var i; i = 1; in the function so that it didn't invoke any UI, but still nothing.

Do you haven /any/ idea why?

The cfc in its entirety is:

<cfcomponent>

<cffunction name="getNote" access="remote" >
<cfargument name="id" type="numeric" required="yes" />

<cfreturn "A string returned from cfc">
</cffunction>

</cfcomponent>

Comment 23 by todd posted on 5/15/2008 at 8:00 AM

Holy crap! I think I found it!!!

The directory containing the cfc has an Application.cfm that has a <cfajaximport> and <htmlhead> tag in it. If I remove those two from Application.cfm, it works!

So there must be some unwritten rule about not putting those in the application.cfm?

Comment 24 by Raymond Camden posted on 5/15/2008 at 5:18 PM

Well, I'd definitely not put the cfhtmlhead in app.cfm as it would impact all requests. ANd cfajaximport really should be on the page it is needed on.

Comment 25 by Miles posted on 6/3/2008 at 7:07 AM

Ray, in response to Richard Baldwin's question, you said,

"Are you saying you want the button click to call a CFC, but you don't want to pass any data? I'd probably just use onClick, and call a JS function that makes use of AjaxProxy to call then CFC."

Would you mind showing a small example of this? All I am doing is having a text field display thevalue returned from my cfc function when a button is clicked - so am passing the button@click value, but it seems strange to have to have a function argument that accepts this value if it isn't being used in the function.

<cfinput type="text" name="next_id" bind="cfc:mycfc.getNextID({get_next_id_button@click})" />

<cffunction name="getNextID" access="remote" returntype="numeric">
<cfargument name="useless_cfajax_argument" required="no" />
...
<cfreturn nextID>
</cffunction>

Great blog, Ray.

Comment 26 by Raymond Camden posted on 6/3/2008 at 3:43 PM

Sorry - what do you need an example of? It sounds like you get it - but you find it odd that you don't care about the value - but since this is a button, I think that's normal. We just care that the button was clicked.

Comment 27 by Tom posted on 9/9/2008 at 11:51 PM

THANK YOU RAY!

I've been poking around the internets, trying to find a simple way to populate multiple form fields from a single function in a CFC (i.e., I don't want to bind each of the CFINPUT fields and have 10 queries running against the DB)... and you did it for me!

I'm querying an employee record based on autosuggested e-mail in the first function and then using your CFAJAXPROXY to call the second function which populates the rest of the fields on the form.

Works perfectly and saves me much work and hair pulling!

Comment 28 by Bill posted on 6/29/2009 at 10:49 PM

Thanks Ray!

Exactly the kind of example that I was looking for - short, sweet, and to the point.

Comment 29 by Ryan posted on 11/17/2009 at 1:17 AM

Ray, this is great. I can everything to work, except the values passed to the cftext and cfinput fields. Instead, it shows "undefined." And ideas?

Comment 30 by Raymond Camden posted on 11/17/2009 at 1:53 AM

No idea. I'd suggest looking w/ Firebug to see what data was returned.

Comment 31 by Jojo Serquina posted on 1/27/2010 at 8:41 PM

I just want to add that one cannot use this comment tag when using cfajaxproxy bind:

"<!-- some comment -->"

I had to install firebug to find that out. I wasted a few hours beforehand. Strange that the returned data included those comments. I didn't read the entire cfajaxproxy doc, must have been documented..

Comment 32 by Raymond Camden posted on 1/27/2010 at 8:43 PM

Thats an HTML comment. Of course it is returned. If you want a CF comment, you need to use 3 dashes.

Comment 33 by Jojo Serquina posted on 1/27/2010 at 9:16 PM

That's correct. I just didn't realized it was returning those comments, too. I ran firebug - lo and behold, there they are.

Comment 34 by David Jacobson posted on 5/10/2010 at 9:52 PM

OK Ray, it's me again. I ran this and I an getting the correct values returned from my cfc but it isn't displaying correctly within my form. I ran with cfdebug and Firebug and still see the values and data. Any thoughts/ideas?

Comment 35 by David Jacobson posted on 5/10/2010 at 11:18 PM

Ray, guess what? I'm stupid. :)
Javascript is case sensitive I was doing r.subject but the cfc was returning SUBJECT. Do you see a difference? Duh! So sorry to bug you.

Comment 36 by Raymond Camden posted on 5/11/2010 at 5:43 AM

No worries.

Comment 37 by Joe posted on 6/22/2010 at 11:02 AM

I got error: Error invoking CFC/art.cfc: Error Executing database Query.

Please help me remove the popup error.

Comment 38 by Joe posted on 6/22/2010 at 11:03 AM

Every time I reload the page I got the above mentioned error.

Comment 39 by Raymond Camden posted on 6/22/2010 at 3:09 PM

You have an error in art.cfc. You need to look at your exception log to see more details about the error. Or use a tool like Firebug to see the result in detail.

Comment 40 by Len Sparks posted on 8/17/2010 at 1:32 AM

Ray,
I need to do exactly what you are doing so I was very excited when I found this blog entry. I've closely adapted your code, but I get an error on the return from the from the cfc. Here is the error message:
error:bind: Bind failed for select box LocationID, bind value is not a 2D array or valid serialized query

info:LogReader: LogReader initialized

info:global: Logger initialized

Thanks in advance,

:-}
Len

Here is the call:
<cfselect bind="../cfc/cfc:hierchy.getLocs()" bindonload="true" name="Loc" size="1" value="LocationID" display="LocationName"/>

Here is the code in the CFC:
<cffunction access="remote" name="getLocs" returntype="string">
<cfquery name="getLocQry" datasource="PHREDsevenInternalSQL">
SELECT LocationName,Bu_Grp_ID
FROM Locations
Where IsActive=#TruVal#
</cfquery>
<cfreturn getLocQry>
</cffunction>

Comment 41 by Raymond Camden posted on 8/17/2010 at 2:13 AM

Your cffunction has returntype=string, but it's a query.

Comment 42 by Len Sparks posted on 8/17/2010 at 3:45 AM

Ray,
Thanks for getting back to me. I had already found that and fixed it but it didn't change the error.
:-}
Len

Comment 43 by Raymond Camden posted on 8/18/2010 at 3:17 PM

Use Firebug, or Chrome Dev tools, to see the JSON response. Cut and paste it into here - or if it is too big, use Pastebin.