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 = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.artid#">
</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 < 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.
Archived Comments
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
Very cool Ray. I didn't know about the onsuccess attribute of cfajaxproxy.
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...
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.
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... :/
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!
What does Firebug show you?
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! :/
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);
Solved. It works perfectly. The when changing the fieldnames in the js, I did not capitalize them! Many thanks!
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
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?
yup Rai im familiar with those tools and this is what i got
http://imageshack.us/photo/...
Use the network tool to view the CFC request and examine the response.
hi again d:
this is what im getting
http://imageshack.us/photo/...
http://imageshack.us/photo/...
http://imageshack.us/photo/...
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.
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 :/
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.
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.
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.
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 ? :)
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.
aahhh cool, thanks, you help me a lot!
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!
Glad it helped!
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
@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.
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...
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.
I seriously don't know how to do that.
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",
Sorry Raymond, should i call that in the <cfajaxproxy> bind ?
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.
ok, is there another alternative to the cfajaxproxy binding?
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...
i have a feeling i can use this if i could find a way to binding to the tbody tr
You save me! Thank you Jedi master!
You are welcome.
I am getting a return from the call but it's not populating the fields. Any suggestions?
Is it online where I can see?
No...but I realized that I didn't have everything in place. I added the other 3 functions: ex; <cffunction name="getDescription">
I added the missing functions and now I am getting a syntax error:
cfinit/$X.processresponse
cfinit/$X.callback
cfinit/$A.sendMessage/req.onreadystatechange
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...
You got me then. If you can share the URL I'll look, but I strongly urge you to move away from this.
Yes I am good. Thank you.
And I am no longer using cfform. :)
The code fragments are very broken here in late 2018 =-\ Like they don't display right or anything.
Sorry - 8 year old post and my blog engine has changed multiple times. ;) I'll try to fix this week.
Updated. Hope it helps!