Ask a Jedi: ColdFusion Ajax example of retrieving fields of data

This post is more than 2 years old.

I have to apologize for the title of this blog entry. I can't honestly think of a nicer way to say it. But enough of that, let me just get to the question.

I have searched all over the web and read countless articles and documentation, tried different examples but i just cant seem to figure out what i first thought was a relatively simple request and solution, maybe it is but i cant seem to figure it out, anyway enough of my waffling here's the scenario:

What i have is a very straight forward form with say 5 different text inputs. What i would like to do is on the first input box, which say is an invoice number, is for the user to enter the invoice number and then hit a button next to the input box and with the power of Coldfusion's built in AJAX stuff go to a cfc which has a query in it which searches for the invoice number, if one is found return the invoice number plus its details back to the form via AJAX, so all of the 5 text boxes would have returned data in them and the form isnt refreshed.

As with most things in ColdFusion, there are a couple ways of doing this. Let's start with one example and then I'll show a way to simplify it. First though, we need a simple form. I'll use the cfartgallery sample database for my example.

<cfform>
id: <cfinput type="text" name="artid" id="artid"><br/> 
name: <cfinput type="text" name="artname" id="artname"><br/> 
description: <cftextarea name="description" id="description"></cftextarea><br/> 
price: <cfinput type="text" name="price" id="price"><br/> 
</cfform> 

I've got a form with 4 inputs. The first one is our primary ID field. When the user enters data there, we then want to load the 3 other fields with the corresponding data. One way to do that would be with binding.

<cfform> 
id: <cfinput type="text" name="artid" id="artid"><br/> 
name: <cfinput type="text" name="artname" id="artname" bind="cfc:test.getName({artid@keyup})" readonly="true"><br/>
description: <cftextarea name="description" id="description" bind="cfc:test.getDescription({artid@keyup})" readonly="true"></cftextarea><br/> 
price: <cfinput type="text" name="price" id="price" bind="cfc:test.getPrice({artid@keyup})" readonly="true"><br/> 
</cfform> 

I've modified the 3 form fields now so that each of them is bound to the ID field. (I also added a readonly flag just to make it clear to the user that these fields are bound to the back end data.) For each field we run a differnent method: getName, getDescription, and getPrice. These are all bound to one CFC:

<cfcomponent>

<cffunction name=”getData” access=”remote”> <cfargument name=”artid” required=”true”> <cfset var q = ““>

<cfquery name="q" datasource="cfartgallery">
select	*
from	art
where	artid = &lt;cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.artid#"&gt;
</cfquery>

<cfreturn q>
</cffunction>

<cfcomponent>

<cffunction name=”getDescription” access=”remote”> 
	<cfargument name=”id” type=”any”> 
	<cfif not isNumeric(arguments.id) or arguments.id lte 0> 
		<cfreturn ““> 
	</cfif> 
	<cfreturn getData(arguments.id).description> 
</cffunction>

<cffunction name=”getName” access=”remote”>
<cfargument name=”id” type=”any”> 
	<cfif not isNumeric(arguments.id) or arguments.id lte 0> 
	<cfreturn ““> 
	</cfif> 
	<cfreturn getData(arguments.id).artname> 
</cffunction>

<cffunction name=”getPrice” access=”remote” returntype=”string”>
<cfargument name=”id” type=”any”> 
	<cfif not isNumeric(arguments.id) or arguments.id lte 0> 
	<cfreturn ““> 
	</cfif> 
	<cfreturn getData(arguments.id).price> 
</cffunction>

</cfcomponent>

As you can see, the 3 methods all make use of a central getData() method. That method runs the query and then the individual queries all just return one field. (ColdFusion 9 users - please read my PS at the end.) This works, but as you can guess, each time we type into the ID field we perform 3 Ajax requests. This isn't horrible - and the data being returned is rather small, but multiple network requests for the same row of data could probably be done better. Let's look at a modified version.

<cfajaxproxy bind="cfc:test.getData({artid@keyup})" onsuccess="showData">

<script>
function showData(d) { 
	//convert into a struct 
	var data = {} 
	for(var i=0; i < d.COLUMNS.length; i++) { 
		data[d.COLUMNS[i]] = d.DATA[0][i] 
		} 
	document.getElementById(‘artname’).value = data[“ARTNAME”] 
	document.getElementById(‘description’).value = data[“DESCRIPTION”] 
	document.getElementById(‘price’).value = data[“PRICE”]

} 
</script>

<cfform>
id: <cfinput type=”text” name=”artid” id=”artid”><br/> 
name: <cfinput type=”text” name=”artname” id=”artname” readonly=”true”><br/> 
description: <cftextarea name=”description” id=”description” readonly=”true”></cftextarea><br/> 
price: <cfinput type=”text” name=”price” id=”price” readonly=”true”><br/> 
</cfform>

This code removes all the bindings from the fields. Instead we use cfajaxproxy to create a binding between the ID field, a CFC, and a JavaScript function. Whenever the ID field is changed, we run the getData method from the CFC and pass the results to our JavaScript function. Then the function just has to parse the result and set the form fields. Much easier, right? Now we have one Ajax request that fetches all the data. Technically we are passing back the same amount of data, but with only network request we will be less prone to suffer from congestion over the intertubes.

Oh, and because I couldn't help myself, I whipped up a quick jQuery version. I'll just paste the script blocks as that's the only thing that changed.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {

	$("#artid").keyup(function() {
		var artid = $(this).val()
		if(isNaN(artid)) return
		
		$.getJSON("test.cfc?method=getdata&artid=" + artid + "&returnformat=json", {}, function(d,status) {
			var data = {}
			for(var i=0; i &lt; d.COLUMNS.length; i++) {
				data[d.COLUMNS[i]] = d.DATA[0][i]
			}
			$("#artname").val(data["ARTNAME"])
			$("#description").val(data["DESCRIPTION"])
			$("#price").val(data["PRICE"])
		})

	})
})
</script>

Enjoy!

PS: There is a fairly serious bug with ColdFusion 9 and returning JSON from CFCs. Before I post details though I'm waiting for the guy who found it to let me know if he has blogged it already. For now though note that the returnType="string" on getPrice is required in ColdFusion 9.

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 Matthew posted on 10/18/2009 at 9:18 PM

Hi Ray,
Many thanks for posting this example. I didn't expect to see an example posted so quickly.
This is really going to help me out and i appreciate your time taken to post these examples.
Also thank you for posting more than one variation, this also helps in understanding the best method to use.
From your example its seems so straight forward, i guess its true in what they say, knowledge is power :)
Many thanks again
Matt

Comment 2 by sam Farmer posted on 10/20/2009 at 7:19 PM

Very cool Ray. I didn't know about the onsuccess attribute of cfajaxproxy.

Comment 3 by Mohamad posted on 12/18/2009 at 6:48 AM

Ray, here's an interesting twist. What if I would like to achieve the same thing but using a cfhttp to get my data instead or a query (data comes from a webservice)? The webservice contains data for st type, st name, city, and state... the xml coming back needs to be stripped, and I'm using cfhttp because the webservice is a .net data set that cfinvoke can not return properly... any ideas on how that maybe achieved?

Here's a sample code:
<cfhttp method="get" url="webservice.asmx/method?string=#form.variable#"></cfhttp>
<cfset Result = xmlparse(cfhttp.FileContent)>
<cfset xmlRoot = Result>
<cfset nodes = xmlSearch(xmlRoot, "//tbCEP")>
<cfif arrayLen(nodes)>
<cfoutput>#nodes[1].logradouro#</cfoutput>
Results: <br />
<cfoutput>
stType = #nodes[1].sttype# <br/>
stName = #nodes[1].name# <br/>
</cfoutput>

this would then need to go back to the form page and populate a form. I'm using the cfoutput just forn illustration...

Comment 4 by Raymond Camden posted on 12/18/2009 at 7:42 AM

Any ideas? Sure. Use the same code. :) Seriously - it doesn't matter _where_ the data comes from. It matters how the data _looks_. Or the form of it. My client side code expected a query of data back. You can either convert the web service result into a query (using queryNew), or edit the client side code to work with the different form of data.

Comment 5 by Mohamad posted on 12/18/2009 at 8:05 AM

Hey Ray,

I did that. I used QueryNew and tested the code several times... it worked fine until the Ajax part... the fields are populating, but they are populating with "undefined"...

Here is a snippet of my form page: http://i3.photobucket.com/a...

And here is a snippet of my component page:
http://i3.photobucket.com/a...

I'm really not sure why it's returning undefined... :/

Comment 6 by Mohamad posted on 12/18/2009 at 8:52 AM

It maybe worth mentioning that the code works only if a valid cep is insert... 04735005 works for example... if the cep is not valid the page errors out.. but replacing the ajax with this:

<cfif IsDefined("Form.GetInfo")>
<cfinvoke component="CheckCEP" method="ReturnAddress" returnvariable="q_AddressData">
<cfinvokeargument name="strcep" value="#form.strcep#">
</cfinvoke>
<cfdump var="#q_AddressData#">
</cfif>
And using a correct CEP actually returns a good result!

Comment 7 by Raymond Camden posted on 12/18/2009 at 9:07 AM

What does Firebug show you?

Comment 8 by Mohamad posted on 12/18/2009 at 9:33 AM

I'm a bit stumped to be honest, because my Firebug knowledge does not go farther than CSS... embarrassed to say this, I don't know what to look for, but I found this while tinkering in the Scripts tag:
({COLUMNS: ["STTYPE", "STNAME", "AREA", "CITY", "STATE"], DATA: [["Rua", "Sao Benedito", "Santo Amaro", "Sao Paulo", "SP"]]});
2 /* !eval(new String("("+json+");)) */

The values you see under, well, they are correct. If I query 04735005, I do get the data shown above from the webservice... however, it's coming back to the fields as undefined for some reason! :/

Comment 9 by Raymond Camden posted on 12/18/2009 at 7:06 PM

Ok, so this means the data comes to the client ok. The bug then is in how you use the data to write it back out on the page. So let's debug. You are aware of how to use console.log(), right? I will assume so.

First, notice my main loop. Let's see if it is doing anything.Before

for(var i=0; i < d.COLUMNS.length; i++) {

add

console.log("The length of cols is "+d.COLUMNS.length);

Comment 10 by Mohamad posted on 12/18/2009 at 7:41 PM

Solved. It works perfectly. The when changing the fieldnames in the js, I did not capitalize them! Many thanks!

Comment 11 by Andrew posted on 9/27/2011 at 7:43 PM

I did the whole example step by step and im getting the next error

"Error invoking CFC test.cfc : Internal Server Error [enable debugging by adding 'cfdebug' to your URL parameters to see more information]"

actually im getting into CF coz i always was a php fan .. and i dont have a clue of what im doing wrong so if you can help me .. thnx a lot :D

c yas

Comment 12 by Raymond Camden posted on 9/27/2011 at 7:49 PM

Former (or considering to be former) PHP folks are ALWAYS welcome here. ;)

Ok, so the best thing to do is use Firebug, or Chrome Dev tools, to inspect what was returned. Are you familiar with those tools?

Comment 13 by Andrew posted on 9/27/2011 at 8:40 PM

yup Rai im familiar with those tools and this is what i got

http://imageshack.us/photo/...

Comment 14 by Raymond Camden posted on 9/27/2011 at 8:44 PM

Use the network tool to view the CFC request and examine the response.

Comment 15 by Andrew posted on 9/27/2011 at 8:56 PM
Comment 16 by Raymond Camden posted on 9/27/2011 at 9:33 PM

I don't think that's right. Those look like the main CFM requests. In the network tab, click on XHR, and you should see the CFC request there.

Btw, if this is online, I can do it for you.

Comment 17 by Andrew posted on 9/27/2011 at 10:00 PM

ya're right i was wrong
here there're the screens (the correct ones)

http://imageshack.us/photo/...
http://imageshack.us/photo/...
http://imageshack.us/photo/...

and no .. it's not online coz my boss says that Ajax leave a hole in the server security and im not allow to put ajax content online :/

Comment 18 by Raymond Camden posted on 9/27/2011 at 10:11 PM

If you read the result there it's telling you to enable robust exception info. This is done in the CF Admin. Once you do, there is no need to post screen shots. You are in the right area. Just look at the detailed error. It's going to be raw html, but you should be able to read it.

Or - I think you can also right click in Chrome/Firebug and open the request in a new tab.

Either way - enable robust exception info and it will tell you the line # of your error and what it is.

Also, your boss is wrong. Completely. A AJAX request is no more or less secure than a non-AJAX request.

Comment 19 by Andrew posted on 9/27/2011 at 10:20 PM

Ok thanks for your help,

now :P

what is "arguments" in the code?
i see that you pass it in the getData function query and then you use it in the other function, but i dont see where it come from.

Comment 20 by Raymond Camden posted on 9/27/2011 at 10:21 PM

Inside a CFC method, the Arguments scope represents everything passed in. You don't have to use arguments.x to refer to the X argument, but it helps differentiate it in code against locally created variables, or global variables in the Variables scope.

Comment 21 by Andrew posted on 9/27/2011 at 10:28 PM

waahoo!!

i just wanted to ask coz i removed the "arguments" from the test.cfc and now it works well, as long as my "artid" field is not null, when it's empty i get the same error that i told ya .. so is there any way to make it i dont know ... with a button or something ? :)

Comment 22 by Raymond Camden posted on 9/27/2011 at 11:11 PM

Well, if arguments.id didn't work, it implies your front end code isn't passing it along when it should be. You want to keep that in there for sure.

I'd restore the CFC code to match mine. Run it again and tell us what the error is exactly. If it complains that arguments artid doesn't exist, then you need to use Firebug/CHrome again to look at the XHR but this time focus on the Request, not the Response. If you don't see artid in there something is amiss. Ensure you didn't typo when copying the client portion of the code.

Again - if I could see this in action, it would be a lot quicker to debug. Maybe ask your boss if you can put it up someplace temporary and then email me directly.

Comment 23 by Matthew Wang posted on 10/6/2011 at 9:59 PM

aahhh cool, thanks, you help me a lot!

Comment 24 by Paul posted on 3/6/2014 at 7:10 AM

I know this is old, but I have to say this is a phenomenal post. Clear, concise, and very efficient coding. This is EXACTLY what I needed, so many thanks to you Raymond!

Comment 25 by Raymond Camden posted on 3/6/2014 at 6:02 PM

Glad it helped!

Comment 26 by Arthur A posted on 9/17/2014 at 4:32 PM

Hello Raymond i have been following your coldfusion posts over the years. You've been an inspiration.i have been developing RIAs with flex and coldfusion for sometime now and recently i wanted to move to html5 with coldfusion. i have a small project am working on where i populate a dataTable with data from an access database using a cfc. i would like that when i click on a row in the dataTable, the details are shown in text boxes or inputs or cfdivs for that matter. how can i get that. below is my code

Comment 27 by Raymond Camden posted on 9/17/2014 at 5:54 PM

@Arthur: Please do not post blocks of code to the comments section. Instead use Pastebin or a Gist. I have edited your comment to remove the code. I have not used the dataTable plugin you describe so it may be best to ask the author of that plugin. In general the process should be simple:

Detect table row click
In the event handler, note the row of data
Apply that data to form fields.

Comment 28 by Arthur A posted on 9/18/2014 at 10:57 AM

Hi Raymond, tried to follow your example of the button and applied it to my table (id of table is expenseList). when i run the app and click on one of the rows of the table i get this error "Element not found: ID ". I could use your guidance on this. attached are the links to my test.cfm page and crud.cfc component.

https://gist.github.com/ano...

https://gist.github.com/ano...

Comment 29 by Raymond Camden posted on 9/18/2014 at 2:21 PM

So, first off, I don't recommend using ColdFusion's Ajax stuff anymore. I know this blog post uses it, but just keep that in mind. You have code that is bound to the click event of the table. But that's not how I'd do it. I'd add a class event for clicking on the table row.

Comment 30 by Arthur A posted on 9/18/2014 at 2:31 PM

I seriously don't know how to do that.

Comment 31 by Raymond Camden posted on 9/18/2014 at 2:36 PM

Well, you are using a plugin to generate the table. You would need to check its docs to see if it is possible. Or, actually, given that the table has an id of tableList, this should work

$("#tableList tr").on("click",

Comment 32 by Arthur A posted on 9/18/2014 at 3:06 PM

Sorry Raymond, should i call that in the <cfajaxproxy> bind ?

Comment 33 by Raymond Camden posted on 9/18/2014 at 3:11 PM

You can't, as far as I know. I no longer recommend using that tag. It doesn't help you now I guess, but it is honest.

Comment 34 by Arthur A posted on 9/18/2014 at 3:13 PM

ok, is there another alternative to the cfajaxproxy binding?

Comment 35 by Raymond Camden posted on 9/18/2014 at 3:20 PM

Well yes - the CF Ajax stuff is simply abstracting away the "pure" JS code you could write. As a beginning, see the "ColdFusion UI the Right Way" project: http://static.raymondcamden...

Comment 36 by Arthur A posted on 9/18/2014 at 5:50 PM

i have a feeling i can use this if i could find a way to binding to the tbody tr

Comment 37 by Xiuhcoatl posted on 3/15/2017 at 12:39 AM

You save me! Thank you Jedi master!

Comment 38 (In reply to #37) by Raymond Camden posted on 3/15/2017 at 1:29 AM

You are welcome.

Comment 39 by Monique posted on 10/24/2017 at 9:01 PM

I am getting a return from the call but it's not populating the fields. Any suggestions?

Comment 40 (In reply to #39) by Raymond Camden posted on 10/25/2017 at 12:09 AM

Is it online where I can see?

Comment 41 (In reply to #40) by Monique posted on 10/25/2017 at 1:23 PM

No...but I realized that I didn't have everything in place. I added the other 3 functions: ex; <cffunction name="getDescription">

Comment 42 (In reply to #40) by Monique posted on 10/25/2017 at 1:25 PM

I added the missing functions and now I am getting a syntax error:

cfinit/$X.processresponse
cfinit/$X.callback
cfinit/$A.sendMessage/req.onreadystatechange

Comment 43 (In reply to #41) by Raymond Camden posted on 10/25/2017 at 2:58 PM

So are you good to go? I want to be very, very clear though - you should not use cfform or cfajaxproxy or any CF client-side code. If you don't know why, check out https://github.com/cfjedima...

Comment 44 (In reply to #42) by Raymond Camden posted on 10/25/2017 at 3:08 PM

You got me then. If you can share the URL I'll look, but I strongly urge you to move away from this.

Comment 45 (In reply to #43) by Monique posted on 10/25/2017 at 6:41 PM

Yes I am good. Thank you.

And I am no longer using cfform. :)

Comment 46 by JM posted on 10/8/2018 at 8:00 PM

The code fragments are very broken here in late 2018 =-\ Like they don't display right or anything.

Comment 47 (In reply to #46) by Raymond Camden posted on 10/8/2018 at 8:05 PM

Sorry - 8 year old post and my blog engine has changed multiple times. ;) I'll try to fix this week.

Comment 48 (In reply to #46) by Raymond Camden posted on 10/9/2018 at 2:00 PM

Updated. Hope it helps!