Posted in ColdFusion | Posted on 04-21-2008 | 7,334 views
David asks what I think is a pretty interesting question concerning ColdFusion, Ajax, and auto-complete style functionality.
I know you have covered the CF8 autocomplete tag, but I am looking to take it one step further, but haven't been able to find any good direction.
I have been building an application for our amateur radio emergency services group that allows us to track our weather spotters while they are in the field. Logging ham operators to the net requires collecting the same information from many time the same people over and over.
I would like to use a form that when the first field is enter (the callsign of the operator) it does a trip to the database and if the ham has checked into a net before, bring back his information and auto complete the remaining form fields.
Example, the first field is Call Sign, I would enter mine, N9CTO and the next three fields would become populated. Those fields would be First Name, Trained Spotter, and my car information to be verified.
I know this is a very specific example, but with CF8, Autocomplete, AJAX I figure this can problem be completed. If I knew Javascript more, I could probably do that as well, just curious to know if the autocomplete can be extended to not only suggest in the first field, but drive answers in the remaining fields. Obviously, if the first field doesn't match in the database, we would enter the details then in the future, our weather spotter would be in the database.
So this was a pretty interesting question. I decided to start it simply first. Instead of worrying about the auto-complete, how can we use ColdFusion 8 and AJAX to simply say, if I do callsign foo, prepopulate these values?
Let's start with our form:
2<table>
3 <tr>
4 <td>Call Sign:</td>
5 <td><input type="text" name="callsign" id="callsign"></td>
6 </tr>
7 <tr>
8 <td>First Name:</td>
9 <td><input type="text" name="firstname" id="firstname"></td>
10 </tr>
11 <tr>
12 <td>Trained Spotted:</td>
13 <td><input type="checkbox" name="trainedspotter" id="trainedspotter"></td>
14 </tr>
15 <tr>
16 <td>Car License Plate:</td>
17 <td><input type="text" name="licenseplate" id="licenseplate"></td>
18 </tr>
19</table>
20</form>
We have four form fields. Our call sign is first, then we have three related fields. The first thing we want to do is notice a change to our callsign field. This is easily achieved with cfajaxproxy:
This says - when callsign changes, run a JavaScript function called loatit. We want to talk to the back end - and while we have multiple ways of doing it, I'm just going to use another cfajaxproxy. As I've said before, cfajaxproxy really has two very disparate styles. One acts like a binding (as you see above) and another creates a connection between your JavaScript code and a CFC. Like so:
So now that we have a connection, let's look at the JavaScript:
2var radioPeopleService = new radiopeopleservice();
3
4function loadit(cs) {
5 var data = radioPeopleService.getProfile(cs);
6 if(data.FIRSTNAME != null) document.getElementById('firstname').value = data.FIRSTNAME;
7 if(data.TRAINEDSPOTTER != null) if(data.TRAINEDSPOTTER) document.getElementById('trainedspotter').checked = true;
8 if(data.LICENSEPLATE != null) document.getElementById('licenseplate').value = data.LICENSEPLATE;
9 console.dir(data);
10}
11</script>
We begin by creating an instance of our proxy. Once we have this proxy, we can run any method on the CFC that has access=remote. Even without looking at the CFC yet you can guess what is going on here. We have a CFC method, getProfile, that will look up a call sign and return a structure of data. The rest of the code is simply JavaScript.
Now take a look at the CFC method:
2 <cfargument name="callsign" type="string" required="false" default="">
3 <cfset var result = {}>
4
5 <cfswitch expression="#arguments.callsign#">
6 <cfcase value="iceman">
7 <cfset result = {firstname="Raymond",trainedspotter=false,licenseplate="XXX11"}>
8 </cfcase>
9 <cfcase value="maverick">
10 <cfset result = {firstname="Tom",trainedspotter=true,licenseplate="AAA11"}>
11 </cfcase>
12
13 <cfcase value="goose">
14 <cfset result = {firstname="Fred",trainedspotter=false,licenseplate="GGG11"}>
15 </cfcase>
16
17 </cfswitch>
18
19 <cfreturn result>
20</cffunction>
As you can see, the code recognized 3 call signs: iceman, maverick, and goose. For each it returns a structure of data. So that's it. If you run the form, enter iceman, you will see the form populate. If you change to goose, you will see other values. You can see an online demo of this here.
Ok, so that's halfway there. David had asked about using autocomplete as well. Luckily I don't have to do much more. First off - I just changed my form to a cfform. Then I changed my first form field to be an autosuggest field. Here is the new, complete, form:
2<table>
3 <tr>
4 <td>Call Sign:</td>
5 <td><cfinput type="text" name="callsign" id="callsign" autosuggest="cfc:radiopeople.getCallSigns({cfautosuggestvalue})"></td>
6 </tr>
7 <tr>
8 <td>First Name:</td>
9 <td><input type="text" name="firstname" id="firstname"></td>
10 </tr>
11 <tr>
12 <td>Trained Spotted:</td>
13 <td><input type="checkbox" name="trainedspotter" id="trainedspotter"></td>
14 </tr>
15 <tr>
16 <td>Car License Plate:</td>
17 <td><input type="text" name="licenseplate" id="licenseplate"></td>
18 </tr>
19</table>
20</cfform>
As you can see, the autosuggest calls the same CFC we used before, now using a method named getCallSigns. This method looks like so:
2 <cfargument name="callsign" type="string" required="false" default="">
3 <!--- create a fake query --->
4 <cfset var q = queryNew("callsign")>
5 <cfset var r = "">
6
7 <cfset queryAddRow(q)>
8 <cfset querySetCell(q, "callsign", "iceman")>
9 <cfset queryAddRow(q)>
10 <cfset querySetCell(q, "callsign", "maverick")>
11 <cfset queryAddRow(q)>
12 <cfset querySetCell(q, "callsign", "goose")>
13
14 <cfquery name="r" dbtype="query">
15 select callsign
16 from q
17 where upper(callsign) like <cfqueryparam cfsqltype="cf_sql_varchar" value="#ucase(arguments.callsign)#%">
18 </cfquery>
19
20 <cfreturn valueList(r.callsign)>
21
22</cffunction>
Obviously I'd use a real query for this method (and the other one), but you can see that basically I'm creating a query and filtering the results based on the text typed into the autosuggest. You can see an example of this here.


Regards,
Dave
N9CTO
To follow up on this. I'm also working with autosuggest where the user can search by last name. The problem I have is when I have two last names that are the same I need the ID of the record to grab the data for the selected person. Also I would like to show the first name on the same line. So the line might look like:
Ray, Terri
Ray, Mike
I've tried different things and I have also looked around for examples on how to do this. I don't really want to show the ID in the autosuggest list I just need to pass it.
thanks,
This seemed to work a treat, by stacking the results the user can type in Lastname then Firstname then an ID, and while they are doing it auto predicts and so shows the options; All i had to do was add a little bit of text to the standard example.
<cfcomponent output="false">
<!--- Lookup used for auto suggest --->
<cffunction name="lookupContact" access="remote" returntype="array">
<cfargument name="search" type="any" required="false" default="">
<!--- Define variables --->
<cfset var data="">
<cfset var result=ArrayNew(1)>
<!--- Do search --->
<cfquery datasource="#application.datasrc#" name="data">
SELECT ContactLastName, ContactFirstname, ContactID
FROM Contact
WHERE UCase(ContactLastname) LIKE Ucase('#ARGUMENTS.search#%')
ORDER BY ContactLastname, ContactFirstname
</cfquery>
<!--- Build result array --->
<cfloop query="data">
<!--- this the new line, all it needed --->
<cfset name = ' #ContactLastName#, #ContactFirstname# #ContactID#'>
<cfset ArrayAppend(result, #name#)>
</cfloop>
<!--- And return it --->
<cfreturn result>
</cffunction>
</cfcomponent>
Stupid me, should be...
<cfset name = ' #ContactLastName# #ContactFirstname# #ContactID#'>
Thanks!
Gregory
html file:
----------------------------------------
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
</head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Untitled Document</title>
<br><br>
<body>
<form>
<table>
<tr>
<td>Call Sign:</td>
<td><input type="text" name="callsign" id="callsign" autosuggest="cfc:radioPeople.getCallSigns({cfautosuggestvalue})"></td>
</tr>
<tr>
<td>First Name:</td>
<td><input type="text" name="firstname" id="firstname"></td>
</tr>
<tr>
<td>Trained Spotted:</td>
<td><input type="checkbox" name="trainedspotter" id="trainedspotter"></td>
</tr>
<tr>
<td>Car License Plate:</td>
<td><input type="text" name="licenseplate" id="licenseplate"></td>
</tr>
</table>
<cfajaxproxy bind="javaScript:loadit({callsign})">
<cfajaxproxy cfc="radiopeople" jsclassname="radiopeopleservice">
</form>
</body>
</html>
<script>
var radioPeopleService = new radiopeopleservice();
function loadit(cs) {
var data = radioPeopleService.getProfile(cs);
if(data.FIRSTNAME != null) document.getElementById('firstname').value = data.FIRSTNAME;
if(data.TRAINEDSPOTTER != null) if(data.TRAINEDSPOTTER) document.getElementById('trainedspotter').checked = true;
if(data.LICENSEPLATE != null) document.getElementById('licenseplate').value = data.LICENSEPLATE;
console.dir(data);
}
</script>
radioPeople.cfc File:
-----------------------------
<cfcomponent hint="Owner database functions">
<cffunction name="getProfile" output="false" returnType="struct" hint="I return information based on a call sign" access="remote">
<cfargument name="callsign" type="string" required="false" default="">
<cfset var result = {}>
<cfswitch expression="#arguments.callsign#">
<cfcase value="iceman">
<cfset result = {firstname="Raymond",trainedspotter=false,licenseplate="XXX11"}>
</cfcase>
<cfcase value="maverick">
<cfset result = {firstname="Tom",trainedspotter=true,licenseplate="AAA11"}>
</cfcase>
<cfcase value="goose">
<cfset result = {firstname="Fred",trainedspotter=false,licenseplate="GGG11"}>
</cfcase>
</cfswitch>
<cfreturn result>
</cffunction>
<cffunction name="getCallSigns" output="false" returnType="string" hint="I suggest call signs" access="remote">
<cfargument name="callsign" type="string" required="false" default="">
<!--- create a fake query --->
<cfset var q = queryNew("callsign")>
<cfset var r = "">
<cfset queryAddRow(q)>
<cfset querySetCell(q, "callsign", "iceman")>
<cfset queryAddRow(q)>
<cfset querySetCell(q, "callsign", "maverick")>
<cfset queryAddRow(q)>
<cfset querySetCell(q, "callsign", "goose")>
<cfquery name="r" dbtype="query">
select callsign
from q
where upper(callsign) like <cfqueryparam cfsqltype="cf_sql_varchar" value="#ucase(arguments.callsign)#%">
</cfquery>
<cfreturn valueList(r.callsign)>
</cffunction>
</cfcomponent>
My index page is calling the CFC to populate the cfinput textbox. Do you suggest using the ID as a hidden field that is passed when the form posts? If so, do I obtain the ID from the CFC using <cfinvoke> to return the ID? I have not used CFCs before and have been examining the docs to better understand.
Thanks in advance.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitiona...;
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<body>
</body>
</html>
{maverick}
Any idea why? I posted the url directly in a browser, radiopeople.cfc?method=getProfile&argumentCollection={"callsign":"m"}&_cf_nodebug=true and I still get the same response.
[Add Comment] [Subscribe to Comments]