Accessibility
Ben Forta

Ben Forta

forta.com

Created:
13 August 2007
User Level:
Beginner
Products:
ColdFusion

ColdFusion powered Ajax – Part 1: Coding auto-suggest and select controls

Ajax (which stands for "Asynchronous JavaScript and XML") is not a product or a technology. Actually, it's not any single thing you can point to. Rather, it is a technique that combines DHTML and JavaScript with the (until fairly recently unnoticed and unused) ability of web browsers to make asynchronous HTTP calls.

If that sounds obscure, don't worry. What it simply means is that JavaScript (the scripting language supported by just about every web browser) has the ability to make web page requests under programmatic control, requests that don't actually update or refresh the screen. By coupling that ability with some clever JavaScript and DHTML, web browser can be made to do some very interesting things, including addressing the scenarios just mentioned.

And, as you've come to expect from ColdFusion, if you want to leverage Ajax controls within your own applications, ColdFusion 8 can help. In this article, I show you how to do just that.

Requirements

To complete this tutorial you will need to install the following software and files:

ColdFusion 8

Sample files: 

Download and install the trial of ColdFusion 8. During the installation process, the installation wizard will ask if you want to install sample code. Select this option and the data sources referred to in the code samples in this article will already be defined in your ColdFusion administrator.  You need only to create the CFML files in the web root of your server to run these code samples.

Using the client-side auto-suggest control

Let's start by coding an 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. I'll be looking at both.

Here's a simple client-side data example (which uses one of the ColdFusion 8 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 is 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.

Using Ajax-powered auto-suggest

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="string">
          <cfargument name="search" type="any" required="false" default="">

          <!--- Define variables --->
          <cfset var data="">

          <!--- Do search --->
          <cfquery datasource="#THIS.dsn#" name="data">
          SELECT artname
          FROM art
          WHERE UCase(artname) LIKE Ucase('#ARGUMENTS.search#%')
          ORDER BY artname
          </cfquery>

          <!--- And return it --->
          <cfreturn ValueList(data.artname)>
</cffunction>
   
</cfcomponent>

This CFC has a single method named lookupArt that accepts a string and performs a query to find all matches that start with the specified value. Auto-suggest accepts a ColdFusion list, and the code uses ValueList() to create a list of query column values 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 attribute specifies 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 call to the lookupArt method in art.cfc. The code, {cfautosuggestvalue} gets automatically replaced with whatever value the user has entered, and that value is then used by the CFC in the lookup. When results are returned, the auto-suggest list gets populated.

Auto-suggest does not get any cleaner and simpler than this.

Using Ajax-powered <SELECT> controls

Many of you have built related select controls, forms with two (or more) drop-down Ajax SELECT controls, where making a change in one control causes the available selections in the related control to change. For example, selecting a category in one control displays category products in a related control, or selecting a state in one control updates a related control with the cities in that state.

These controls are typically implemented using client-side JavaScript to process arrays of data embedded in the page itself. Every possible combination and option is embedded in JavaScript in the page, and client-side scripts update controls based on selection changes in other controls.

In ColdFusion 8, the new Ajax functionality makes this kind of interface really easy, without requiring any client-side scripting, and without requiring that all of the data be embedded in the generated page. Rather, CFSELECT controls may be bound to ColdFusion component methods that are asynchronously invoked as needed.

To demonstrate this, here is a complete working example that uses one of the example databases that comes with ColdFusion. First, here is the ColdFusion component:

<cfcomponent output="false">

   <cfset THIS.dsn="cfartgallery">

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

      <!--- Get data --->
      <cfquery name="data" datasource="#THIS.dsn#">
      SELECT mediaid, mediatype
      FROM media
      ORDER BY mediatype
      </cfquery>

      <!--- And return it --->
      <cfreturn data>
   </cffunction>

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

      <!--- Define variables --->
      <cfset var data="">

      <!--- Get data --->
      <cfquery name="data" datasource="#THIS.dsn#">
      SELECT artid, artname
      FROM art
      WHERE mediaid = #ARGUMENTS.mediaid#
      ORDER BY artname
      </cfquery>

      <!--- And return it --->
      <cfreturn data>
   </cffunction>

</cfcomponent>

This CFC contains two methods. The getMedia method returns all of the media types in the art catalog database, and the getArt method accepts a media id and returns any art that is associated with that passed id. Both methods simply return queries containing the results.

Now for the form itself:

<cfform>
 
 <table>
    <tr>
       <td>Select Media Type:</td>
       <td><cfselect name="mediaid"
             bind="cfc:art.getMedia()"
             value="mediaid"  
             display="mediatype"
             bindonload="true" /></td>
    </tr>
    <tr>
       <td>Select Art:</td>
       <td><cfselect name="artid"
             value="artid"
             display="artname"
             bind="cfc:art.getArt({mediaid})" /></td>
    </tr>
 </table>
 
 </cfform>  

The form contains two CFSELECT controls, one named mediaid and the other named artid.

The  mediaid control is bound to cfc:art.getMedia(), and to obtain the list of media types to populate the control, the client makes an asynchronous call to the getMedia method in art.cfc, and populates the list with the returned query, using the columns specified in the VALUE and DISPLAY attributes. As you would want this control to be automatically populated when the form loads, the bindonload attribute is set to true. This way, the getMedia() call is executed automatically when the form loads.

The artid control is bound to the getArt method in art.cfc. This method requires that a mediaid be passed to it, and so {mediaid} is used to pass the currently selected value of control mediaid (the first CFSELECT control). Because these two controls are bound together, the second dependant on the first, ColdFusion automatically generates JavaScript code that forces artid to be repopulated with newly retrieved data whenever mediaid changes.

This example binds just two controls, but this mechanism can be used to relate as many controls as needed, and not just CFSELECT controls either.

Ajax made easy

And this is just the start of it. By combining simple tag abstractions in ColdFusion, asynchronous calls to CFCs, and client-side bindings, coding Ajax-type user interfaces just got a whole lot simpler.

About the author

Ben Forta is the Adobe senior product evangelist and the author of numerous books, including ColdFusion Web Application Construction Kit and its sequel Advanced ColdFusion Application Development, as well as books on SQL, JavaServer Pages, WAP, Windows development, and more. Ben co-authored the official ColdFusion training material, the certification tests and Macromedia Press study guides for those tests, and now spends a considerable amount of time lecturing, speaking, and writing about application development worldwide. Visit Ben's blog to read his regular postings on ColdFusion and more.