I plan to post a series of examples demonstrating how to use the new Ajax functionality in
ColdFusion 8 (many based on examples used during our recent usergroup tour). The first one I'll start with is the auto-suggest control. Auto-suggest is a modified text input box, one that displays suggestions as the user types. The auto-suggest control in ColdFusion 8 can be used in two ways, with local client-side data, and with asynchronous calls back to ColdFusion.
Here's a simple client-side data example (which uses one of the CF8 example databases, so this should work for you as is):
<!--- Get data --->
<cfquery datasource="cfartgallery" name="data">
SELECT artname
FROM art
ORDER BY artname
</cfquery>
<!--- The form --->
<cfform>
Art:
<!--- Populate auto-suggest control --->
<cfinput type="text"
name="artname"
autosuggest="#ValueList(data.artname)#">
</cfform>
This form displays a simple text box, but as text is entered, suggestions are displayed. The list of suggestions are passed to the autosuggest attribute which accepts a comma delimited list. The list could be hardcoded, but here ValueList() is being used to dynamically build a list based on a prior database lookup.
This is not an Ajax control in that lookups are not asynchronous, there is no communication back to the server to retrieve data, all data is local. This is actually a preferred form of auto-suggest for smaller lists.
For longer lists asynchronous interaction is indeed preferred, and the auto-suggest control supports this by allowing asynchronous calls to a ColdFusion component. Here is a sample CFC:
<cfcomponent output="false">
<cfset THIS.dsn="cfartgallery">
<!--- Lookup used for auto suggest --->
<cffunction name="lookupArt" 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="#THIS.dsn#" name="data">
SELECT artname
FROM art
WHERE UCase(artname) LIKE Ucase('#ARGUMENTS.search#%')
ORDER BY artname
</cfquery>
<!--- Build result array --->
<cfloop query="data">
<cfset ArrayAppend(result, artname)>
</cfloop>
<!--- And return it --->
<cfreturn result>
</cffunction>
</cfcomponent>
This CFC has a single method named lookupArt which accepts a string and performs a query to find all matches that start with the specified value. Auto-suggest requires that results be returns in a single dimensional array (for now, hopefully this will change before we ship the final product), and so the code populates an array with the results which are then returned.
Now for the modified form code to use this CFC and method:
<cfform>
Art:
<cfinput type="text"
name="artname"
autosuggest="cfc:art.lookupArt({cfautosuggestvalue})">
</cfform>
Here the autosuggest points to a CFC, and as the CFC (I named it art.cfc) is in the current folder, no path needs to be specified. When a user enters a value, generated JavaScript code triggers an asynchronous calls to the lookupArt method in art.cfc. {cfautosuggestvalue} gets automatically replaced with whatever value the user has entered, and that value is then used by the CFC in the lookup. When an array of results get returned the auto-suggest list gets populated.
Auto-suggest does not get any cleaner and simpler than this.
--- Ben
Downright awesome how easy it is to get it to do these things!
If only there was a cfinput type="file" with a upload progress meter....
I notice that if you put a DIV around all of your code and align it to center only the word Art: is centered. The form field remains on the left. Looks like a CSS issue (not your code's fault though).
Cheers for being a great CF evangelist.
Simon
<cfajaximport cssSrc="path to your resources folder">
If I had a list of names with last name, "SMITH", I'd want to type smith within the input field and get a listing of everyone with smith in their name.
Is this possible? I know it's possible with SPRY Autosuggest, just wondering if they're porting this over to CF8.
Thanks!
Chris
I have just tried this Ajax Tutorial.
But I cannot locate the cfc.
I have created the Artists.cfc and it is located in the same directory as the cfm code.
But when it runs I get an
error:http: Error invoking CFC /Art.cfc : Not Found
What is wrong?
Thanks, man.
I'm unsure why any of this occurs. I'd also love to know how to force the bind errors to cease popping up as in a real world environment I'm not likely to want customers seeing errors that they can't do anything about.
I created a simple .cfc called suggest.cfc. I store it in this directory:
c:\websites\sms\cfc\suggest.cfc
Then I created a simple cfm file with <CFINPUT> and did the autosuggest that you mention calling the cfc. I call it by putting the following:
<cfinput type="text" name="cust_search" autosuggest="cfc:sms.cfc.suggest.getCustomers({cfautosuggestvalue})">
I store this .cfm under:
c:\websites\sms\labs\ajax\autosuggest.cfm
Then I run this file.
I get an AJAX error (I used ?cfdebug) that it cannot find the suggest.cfc web service. So I do a view source, and I see the following code:
ColdFusion.Bind.cfcBindHandler(null, {'bindTo':ColdFusion.Autosuggest.loadAutoSuggest,'bindToAttr':'true','cfc':'/suggest.cfc','cfcFunction':'getCustomers','bindExpr':[['searchstring',cfautosuggestvalue]], 'bindToParams': { 'autosuggestid':"cust_search" }});
Notice the "/suggest.cfc". When I save the source as a CFM, change the /suggest.cfc to /sms/cfc/suggest.cfc it works like a charm.
Why is it calling in AJAX /suggest and not the directory path I stated in the dot notation in the CFC.
Any help would be greatly appreciate it.
Thanks!
Marcelo
marcelo@thedigitalmediadude.com
http://www.thedigitalmediadude.com/
The path that we specify in our cfm file does not propogate properly into the the javascript that is built by ColdFusion.
Perhaps this is a configuration error? (BTW: we are not mapping our cfc directory)
We use IIS on Windows 2003 Enterprise server.
Please help.
What really threw us off was that, with our original configuration, the cfc's were accessible via a cfinvoke and via the url...
Unfortunately, it will be alot of work to re-configure our other webservers so that we can utilize these new features.
I'm having the same problem as Kruse. I've the cfc inthe same directory as the cfm but i keep getting:
Error invoking CFC /<name>.cfc : Not Found
but when i introduce some error to the function in the cfc (such as a required parameter) the error is immidiately detected and flagged, which indicates that the cfc is being found.
regards,
Jesbin
--- Ben
Specifically, in the cfautosuggest.js file located in CFIDE/scripts/ajax/package - on or about line 68 - this code is what causes the issue:
if(!_bf.isContainerOpen()&&_c0.length>0&&(arg.keyCode>31||(arg.keyCode==8&&_bf.valuePresent==true)))
If you change it to not check the Open Container - as follows:
if(_c0.length>0&&(arg.keyCode>31||(arg.keyCode==8&&_bf.valuePresent==true)))
it should now function (at least most of the time).
Hope that helps.
I can invoke the CFC directly using the URL and also invoke methods using the CreateObject("component",...). Another thing I found out was that if I put the CFC in the webroot it seems to work but if I put it in the virtual directory it says CFC not found. Is that the normal behaviour. I thought CFC's could be placed anywhere and referenced using the path syntax.
regards,
Jesbin
--- Ben
Just to let you know, in my case the web server and the CFMX/v8 are on the same machine and still the error exists.
Jesbin
--- Ben
of course, without that WHERE clause, the autosuggesting is very very slow. strange, i copied the code directly and, except for fixing 1 code error, it's identical to Ben's code, but i get that error if the WHERE clause is there.
--- Ben
Any update on when the fix for the CFC not found error will be forthcoming?
Thanks,
Eric
Anyway I tried your suggestion about copying the resources folder to be able to CSS change the css for the AutoSuggest however I can't get it to find it.
I have my AutoSuggest template in a folder called AutoSuggest and have copied the resources folder into it and added the <cfajaximport cssSrc> tag at the top of the template.
I have tried the following to get it to find the css files but none have worked, am I missing something simple here?
<cfajaximport cssSrc="resources">
<cfajaximport cssSrc="resources/ext">
<cfajaximport cssSrc="C:\Inetpub\wwwroot\CF8\AutoSuggest\resources\ext">
Cheers
John
The "CFC not found error" is really a pain. I see that this may be a bug.
Do we know what the ETA of the fix is for this?
Is there a bug page on the Adobe Site like there was on the Macromedia website?
Thanks,
Gene
The Problem
I am running a template (http:// localserver/open/index.cfm) that is calling a cfc from within the same folder and I get the following debug output from the CF Ajax Debugger.
error:http: Error invoking CFC /open/places.cfc : Internal Server Error
info:widget: Created grid, id: reportsGrid
info:http: HTTP GET /open/places.cfc?method=getData&returnFormat=json&argumentCollection=%7B%22page%22%3A1%2C%22size%22%3A10%2C%22sortcol%22%3A%22%22%2C%22sortdir%22%3A%22%22%7D&_cf_nodebug=true&_cf_nocache=true&_cf_rc=0
info:http: Invoking CFC: /open/places.cfc , function: getData , arguments: {"page":1,"size":10,"sortcol":"","sortdir":""}
info:LogReader: LogReader initialized
info:global: Logger initialized
As you can see, it seems to invoke the cfc initially, but gets an Internal Server Error when trying to retrieve the data. So I thought I would call the cfc directly from the browser - http:// localserver/open/places.cfc?method=getData (this works on my set up of CF7). The error I get this time is a “JRun Servelet Error”.
500
java.lang.NullPointerException
at jrun.servlet.JRunRequestDispatcher.invoke(JRunRequestDispatcher.java:283)
at jrun.servlet.ServletEngineService.dispatch(ServletEngineService.java:543)
at jrun.servlet.jrpp.JRunProxyService.invokeRunnable(JRunProxyService.java:203)
at jrunx.scheduler.ThreadPool$ThreadThrottle.invokeRunnable(ThreadPool.java:428)
at jrunx.scheduler.WorkerThread.run(WorkerThread.java:66)
If I try and call a cfc that doesn’t exist, for example http:// localserver/open/places.cfc?method=getData then I get exactly the same error. Which led me to think that for some reason that the CF Server was having problems finding CFC’s. So I tried to call a non existant cfc from the initial template, http:// localserver/open/index.cfm and got the error message i would expect:
The specified CFC place could not be found.
The path to the CFC must be specified as a full path, or as a relative path from the current template, without the use of mappings.
I am now totally confused and would welcome any suggestions...
When we try and run the cfc (or the template containing the CFGRID), Apache.exe is suffering a Buffer Overflow and I guess this must be the problem.
--- Ben
Thanks for your reply, but I am unclear which updater you mean. Can you please clarify.
Regards,
Mark
Thanks
The interesting thing is that on the windows development machine I don't get the error; on the Redhat Linux production machine I do.
Same exact code structure on both machines.
Same CF 8 version.
===== START =====
<cffunction name="getUsers" displayname="getUsers" hint="Method to return users for a specified customer" returntype="query" access="public" output="no">
<cfargument name="custnum" displayname="custNum" hint="Customer number of users" type="numeric" required="yes" />
<cfargument name="userid" displayname="userID" hint="Specific ID of a user" type="numeric" required="no" default="0" />
<cfargument name="orderby" displayname="orderBy" hint="Column to sort records by" type="string" required="no" default="fullname" />
<cfquery name="getusers" datasource="conCourseGPS">
SELECT
userid,
username,
firstname,
lastname,
lastname + ', ' + firstname AS fullname,
photopath,
licensenumber,
cust_num,
modby,
moddate
FROM users
WHERE cust_num = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.custnum#" />
<cfif arguments.userid>
AND userid = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.userid#" />
</cfif>
ORDER BY #arguments.orderby#
</cfquery>
<cfreturn getusers>
</cffunction>
===== END =====
I would like to bind a <cfinput> with an asynchronous autosuggest from getusers.fullname, and I need to pass this function a variable customer number (custnum).
Is this even possible?
I may have to resort to calling the cfc and then setting the autosuggest to a valueList() on the column. By doing this, though, I lose the asynchronous functionality.
Any help would be much appreciated.
Thanks
Is there any workaround for the file not found using virtual dirs on IIS? I mean can I somehow use full path?
Thanks for this great article!
--- Ben
Btw I too got it to work when the file was inside a real IIS directory (x:\inetpub\wwwroot)
Figured it out this morning, here’s how I did it.
Created a folder called “ajaxScripts” under the application directory
Copied the “resources” directory to it.
Import using <cfajaximport cssSrc="/myWebApp/ajaxScripts">
see: http://livedocs.adobe.com/coldfusion/8/htmldocs/he...
- Ty
http://fusion.dominicwatson.co.uk/2008/04/tidy-aut...
If it isn't possible, could it be submitted as a new feature request for 9? A simple autosuggestdelimiter attribute for the cfinput tag would be great ;)
Dominic
Problem summary: If you write SQL in the bound autosuggest function like this:
WHERE d_name LIKE '%#srch#%'
the coldfusion cfinput autosuggest function forces the result to START WITH the search term, where the SQL is looking for containment of the search string. So, for example, my query returns 10 matches, 6 start with the search term and 4 contain the search term but not at the beginning. The cfinput box only lists the 6 that start with the search term. This is actually intended behavior by Coldfusion.
p658 of the cf8 developers guide reads:
"You do not have to limit the returned data to values that match the cfautosuggestvalue contents, because the client-side code displays only the values that match the user input."
Why force a filter on the results when we can do it ourselves? Not good.
The problem is that is Coldfusion failed to think to pass a parameter to the YUI component AutoComplete. It's defined in /CFIDE/scripts/ajax/yui/autocomplete/autocomplete-min.js.
Search for this line:
YAHOO.widget.DataSource.prototype.queryMatchContains=false;
Change the false to true.
That fixes the problem.
I'm not sure what other coldfusion functions use this component or what unexpected side effects this change will introduce.
However, this change allows the autosuggest feature to to function as it should, where you have the control in your SQL of what results are displayed in the dropdown. If you want the results to start with the search term, then search for
WHERE d_name LIKE '#srch#%'
like Mr. Forta does in his example above. You could even return results totally unrelated to the searched term if you wanted to.
I'll see if I can incorporate that property into my betterautosuggest custom tag on riaforge. At the moment, the tag simply wraps the cfinput tag and adds some javascript to set other autosuggest properties with JS. Hopefully I will be able to set this one
http://betterautosuggest.riaforge.org/