Ask Ben: Building An AJAX, jQuery, And ColdFusion Powered Application
Posted March 3, 2009 at 10:37 AM
Mr. Nadel, I'm very eager to learn how to create apps like you've created. I'm just getting into AJAX and I'm more of a junior in Javascript. I've been working with Coldfusion for the past 8 years...But I really want to learn how to build my own apps using AJAX...to get a better understanding of how AJAX works, where do I start, and I want to continue growing from there...I've been trying to build a related select box using AJAX for the past month and I'm having no success...Can the Student learn from the Teacher, please...I live in the Washington DC metro area...Any advice from you would be appreciated to start on my way in becoming an expert in using AJAX and Coldfusion together and hopefully continue to learn from you as much as I can...
Building an AJAX application is not exactly a simple thing, so it's hard to demonstrate a full AJAX work flow while at the same time keeping it management. But, I'm gonna try to do it anyway! In this demo, I have a minimal ColdFusion back-end and a jQuery powered client (browser). The demonstration consists of a contact list that can be updated from a single page on the front end. And, just so you can see the birds-eye-view of what we're doing here, let's take a look at a video demonstration:
| | | | | |
| | | |||
| | | |
So far so good - nothing too crazy going on here. In this demonstration, we are using AJAX to handle the form submission, show the contact list, and delete contacts. I think for most of us, especially those of use that have been using ColdFusion for a while, the ColdFusion part is not the difficult part - that we can do in our sleep. The difficult part is the AJAX and the back-end communication. As such, I want to quickly cover the ColdFusion back-end so we can get into the more interesting aspects of a jQuery powered AJAX application.
The entire ColdFusion back-end is minimalist. The Contacts are stored as an array of structs directly in the APPLICATION scope:
Launch code in new window » Download code as text file »
- <cfcomponent
- output="false"
- hint="I define the application settings and application-level events.">
-
- <!--- Define application settings. --->
- <cfset THIS.Name = "AJAXDemo" />
- <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 10, 0 ) />
-
-
- <!--- Define application events. --->
-
- <cffunction
- name="OnApplicationStart"
- access="public"
- returntype="boolean"
- output="false"
- hint="I run when the application needs to be initialized.">
-
- <!--- Clear the application scope. --->
- <cfset StructClear( APPLICATION ) />
-
- <!---
- Create the array to store contacts. We are going
- to keep this demo as simple as possible and so,
- the contacts will be just an array or structs.
- --->
- <cfset APPLICATION.Contacts = [] />
-
- <!--- Return true so the page will keep loading. --->
- <cfreturn true />
- </cffunction>
-
- </cfcomponent>
Then, in order for the client to talk to the ColdFusion back-end, we are going to create some ColdFusion components with remote access methods. These are our remote API components. In my AJAX applications, I like all of my AJAX responses to have the same exact top-level structure:
- Success - a boolean flag to determine if the call was successful.
- Data - any kind of data that needs to be returned.
- Errors - an array of request errors.
I am never 100% sure how I feel about the kind of data returned in the errors (is it too tightly connected to the HTML front end?); but, to keep things simple, I am going to be returning user-friend errors in the Errors array. Now, to make it really easy for the remote API to use this response, I have created a BaseAPI.cfc ColdFusion component that all the API objects will extend:
Launch code in new window » Download code as text file »
- <cfcomponent
- output="false"
- hint="I provide the base API functionality.">
-
-
- <cffunction
- name="GetNewResponse"
- access="public"
- returntype="struct"
- output="false"
- hint="I return a new API response struct.">
-
- <!--- Define the local scope. --->
- <cfset var LOCAL = {} />
-
- <!--- Create new API response. --->
- <cfset LOCAL.Response = {
- Success = true,
- Errors = [],
- Data = ""
- } />
-
- <!--- Return the empty response object. --->
- <cfreturn LOCAL.Response />
- </cffunction>
-
- </cfcomponent>
As you can see, this just provides an internal method for creating new response objects.
Next, I created a Contacts.cfc remote API object to handle the contact-related AJAX requests. This, like all other API objects, extends the BaseAPI.cfc:
Launch code in new window » Download code as text file »
- <cfcomponent
- extends="BaseAPI"
- output="false"
- hint="I am the public API for contacts.">
-
-
- <cffunction
- name="AddContact"
- access="remote"
- returntype="struct"
- returnformat="json"
- output="false"
- hint="I add the given contact.">
-
- <!--- Define arguments. --->
- <cfargument
- name="Name"
- type="string"
- required="true"
- hint="I am the name of the contact."
- />
-
- <cfargument
- name="Hair"
- type="string"
- required="true"
- hint="I am the hair color of the contact."
- />
-
- <!--- Define the local scope. --->
- <cfset var LOCAL = {} />
-
- <!--- Get a new API resposne. --->
- <cfset LOCAL.Response = THIS.GetNewResponse() />
-
-
- <!--- Check to see if all the data is defined. --->
- <cfif NOT Len( ARGUMENTS.Name )>
-
- <cfset ArrayAppend(
- LOCAL.Response.Errors,
- "Please enter a contact name."
- ) />
-
- </cfif>
-
- <cfif NOT Len( ARGUMENTS.Hair )>
-
- <cfset ArrayAppend(
- LOCAL.Response.Errors,
- "Please enter a contact hair color."
- ) />
-
- </cfif>
-
-
- <!---
- Check to see if their are any errors. If there are
- none, then we can process the API request.
- --->
- <cfif NOT ArrayLen( LOCAL.Response.Errors )>
-
- <!--- Create a new contact. --->
- <cfset LOCAL.Contact = {
- ID = CreateUUID(),
- Name = ARGUMENTS.Name,
- Hair = ARGUMENTS.Hair
- } />
-
- <!--- Add the contact to the cache. --->
- <cfset ArrayAppend(
- APPLICATION.Contacts,
- LOCAL.Contact
- ) />
-
- <!--- Set the contact as the return data. --->
- <cfset LOCAL.Response.Data = LOCAL.Contact />
-
- </cfif>
-
-
- <!--- Check to see if we have any errors. --->
- <cfif ArrayLen( LOCAL.Response.Errors )>
-
- <!---
- At this point, if we have errors, we have to flag
- the request as not successful.
- --->
- <cfset LOCAL.Response.Success = false />
-
- </cfif>
-
- <!--- Return the response. --->
- <cfreturn LOCAL.Response />
- </cffunction>
-
-
- <cffunction
- name="DeleteContact"
- access="remote"
- returntype="struct"
- returnformat="json"
- output="false"
- hint="I delete the contact with the given ID.">
-
- <!--- Define arguments. --->
- <cfargument
- name="ID"
- type="string"
- required="true"
- hint="I am the ID of the contact to delete."
- />
-
- <!--- Define the local scope. --->
- <cfset var LOCAL = {} />
-
- <!--- Get a new API resposne. --->
- <cfset LOCAL.Response = THIS.GetNewResponse() />
-
- <!--- Set a default contact index. --->
- <cfset LOCAL.ContactIndex = 0 />
-
- <!---
- Loop over the contacts looking for the one with
- the given ID.
- --->
- <cfloop
- index="LOCAL.Index"
- from="1"
- to="#ArrayLen( APPLICATION.Contacts )#"
- step="1">
-
- <!--- Check the contact ID. --->
- <cfif (APPLICATION.Contacts[ LOCAL.Index ].ID EQ ARGUMENTS.ID)>
-
- <!--- Store this index as the target index. --->
- <cfset LOCAL.ContactIndex = LOCAL.Index />
-
- </cfif>
-
- </cfloop>
-
-
- <!--- Check to see if we found a contact. --->
- <cfif NOT LOCAL.ContactIndex>
-
- <cfset ArrayAppend(
- LOCAL.Response.Errors,
- "The given contact could not be found."
- ) />
-
- </cfif>
-
-
- <!---
- Check to see if their are any errors. If there are
- none, then we can process the API request.
- --->
- <cfif NOT ArrayLen( LOCAL.Response.Errors )>
-
- <!--- Get the contact. --->
- <cfset LOCAL.Contact = APPLICATION.Contacts[ LOCAL.ContactIndex ] />
-
- <!--- Delete the contact. --->
- <cfset ArrayDeleteAt(
- APPLICATION.Contacts,
- LOCAL.ContactIndex
- ) />
-
- <!--- Set the contact as the return data. --->
- <cfset LOCAL.Response.Data = LOCAL.Contact />
-
- </cfif>
-
-
- <!--- Check to see if we have any errors. --->
- <cfif ArrayLen( LOCAL.Response.Errors )>
-
- <!---
- At this point, if we have errors, we have to flag
- the request as not successful.
- --->
- <cfset LOCAL.Response.Success = false />
-
- </cfif>
-
- <!--- Return the response. --->
- <cfreturn LOCAL.Response />
- </cffunction>
-
-
- <cffunction
- name="GetContacts"
- access="remote"
- returntype="struct"
- returnformat="json"
- output="false"
- hint="I return the collection of contacts.">
-
- <!--- Define the local scope. --->
- <cfset var LOCAL = {} />
-
- <!--- Get a new API resposne. --->
- <cfset LOCAL.Response = THIS.GetNewResponse() />
-
- <!--- Store the contacts in the response. --->
- <cfset LOCAL.Response.Data = APPLICATION.Contacts />
-
- <!--- Return the response. --->
- <cfreturn LOCAL.Response />
- </cffunction>
-
- </cfcomponent>
This code is a bit long, but nothing much is going on here. Notice that all of the methods have remote access and that the returnFormat is set to JSON. This will allow me to leverage JSON deserialization on the client. The remote calls are validated and executed and every API method returns an instance of the API Response (as defined by the BaseAPI.cfc's GetNewResponse() method). This unified response makes working with the data on the client-side guess-free and easy.
Ok, so that's the ColdFusion part of the application. Fairly straightforward, right?
Now, let's get into the client-side coding - the more complex portion of the AJAX application. First, we'll take a quick look at the HTML:
Launch code in new window » Download code as text file »
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html>
- <head>
- <title>Simple ColdFusion And jQuery AJAX Demo</title>
-
- <!-- Linked files. -->
- <script type="text/javascript" src="jquery-1.3.2.min.js"></script>
- <script type="text/javascript" src="contact_form.js"></script>
- <script type="text/javascript" src="contact_list.js"></script>
- </head>
- <body>
-
- <h1>
- Simple ColdFusion And jQuery AJAX Demo
- </h1>
-
-
- <h2>
- Contact Form
- </h2>
-
- <form id="contact-form">
-
- <!--- This is the hidden ID of the contact. --->
- <input type="hidden" name="id" value="0" />
-
- <p>
- <label>Name:</label><br />
- <input type="text" name="name" value="" size="40" />
- </p>
-
- <p>
- <label>Hair:</label><br />
- <input type="text" name="hair" value="" size="40" />
- </p>
-
- <p>
- <button type="submit">Save Contact</button>
- </p>
-
- </form>
-
-
- <h2>
- Contact List
- </h2>
-
- <table id="contact-list" cellspacing="2" cellpadding="5" border="1">
- <thead>
- <tr>
- <th>
- Name
- </th>
- <th>
- Hair
- </th>
- <th>
- Actions
- </th>
- </tr>
- </thead>
-
- <!--- Data to be injected here. --->
- <tbody class="table-content" />
-
- <!---
- The following is our DOM template for pulling down
- data and populating the table.
- --->
- <tbody class="dom-template" style="display: none ;">
- <tr>
- <td class="name-column">
- <!-- Name. -->
- </td>
- <td class="hair-column">
- <!-- Hair. -->
- </td>
- <td class="actions-column">
- <!-- Actions. -->
- <a>Delete</a>
- </td>
- </tr>
- </tbody>
- </table>
-
- </body>
- </html>
I am trying to break up the code as much as possible to make these easily digestible parts. As such, I have completely separated the HTML from the Javascript. This HTML is fairly vanilla; the only thing to really take note of is that in the Contact List table, I have a non-displayed TBODY tag. This TBODY tag contains a template for the rows that we will be adding to the list. I am doing this because my AJAX calls return raw data and I have found that using templates is the easiest way to turn raw data into HTML.
Ok, now it's time to look at the Javascript. Again, to make this easy to digest, I have created a separate Javascript file for each HTML control (form and list). Before we look at the control scripts, I want to talk a bit about events and event subscription. To be honest, I have never used this methodology in production before; but, it is something that I learned last week at Hal Helms' Real World Object Oriented Development and I think it's something to be explored.
The idea here is that each control listens to events that get triggered on the Document object. Each control can also manually trigger events on the document object. What we are doing is centralizing the event subscription model. This allows us to decouple the controllers from each other (they never have to call each other directly - only trigger events on the document). In addition to decoupling, this centralized event delegation also allows additional objects to listen for events without the event announcer (the initiating control) having to know anything about it.
That may mean nothing to you, so don't get hung up on it. I think it will make a bit more sense as we start to look at the code.
First, let's look at the contact_form.js Javascript which controls the contact form:
Launch code in new window » Download code as text file »
- // Create a Javascript class to handle the contact form.
- function ContactForm(){
- var objSelf = this;
-
- // Get a jQuery reference to the form.
- this.Form = $( "#contact-form" );
-
- // Get jQuery references to the key fields in our contact
- // form. This way, we don't have to keep looking them up.
- // This will make it faster.
- this.DOMReferences = {
- Name: this.Form.find( "input[ name = 'name' ]" ),
- Hair: this.Form.find( "input[ name = 'hair' ]" )
- };
-
- // Bind the submission event on the form.
- this.Form.submit(
- function( objEvent ){
- // Submit the form via AJAX.
- objSelf.SubmitForm();
-
- // Cancel default event.
- return( false );
- }
- );
- }
-
-
- // Define a method to help display any errors.
- ContactForm.prototype.ShowErrors = function( arrErrors ){
- var strError = "Please review the following:\n";
-
- // Loop over each error to build up the error string.
- $.each(
- arrErrors,
- function( intI, strValue ){
- strError += ("\n- " + strValue);
- }
- );
-
- // Alert the error.
- alert( strError );
- }
-
-
- // Define a method to submit the form via AJAX.
- ContactForm.prototype.SubmitForm = function(){
- var objSelf = this;
-
- // Submit form via AJAX.
- $.ajax(
- {
- type: "get",
- url: "./api/Contacts.cfc",
- data: {
- method: "AddContact",
- name: this.DOMReferences.Name.val(),
- hair: this.DOMReferences.Hair.val()
- },
- dataType: "json",
-
- // Define request handlers.
- success: function( objResponse ){
- // Check to see if request was successful.
- if (objResponse.SUCCESS){
-
- // Clear the form.
- objSelf.Form.get( 0 ).reset();
-
- // Trigger the update event. This will allow
- // anyone who is listening for this event on
- // the document to react to it and update their
- // own display if needed.
- $( document ).trigger( "contactsUpdated" );
-
- // Focus the name field.
- objSelf.DOMReferences.Name.focus();
-
- } else {
-
- // The response was not successful. Show
- // an errors to the user.
- objSelf.ShowErrors( objResponse.ERRORS );
-
- }
- },
-
- error: function( objRequest, strError ){
- objSelf.ShowErrors( [ "An unknown connection error occurred." ] );
- }
- }
- );
- }
-
-
- // ----------------------------------------------------- //
- // ----------------------------------------------------- //
-
-
- // When the document is ready, init contact form. It is important
- // that we wait for the DOM loaded event to that we have access to
- // the DOM elements.
- $(
- function(){
-
- // Create an instance of the contact form.
- var objContactForm = new ContactForm();
-
- }
- );
Here, we are creating a Javascript class and instantiating it. This class instance acts as a controller to its HTML counterpart. When the class constructor executes, we get and cache some DOM reference (for speed in later reference) and bind the form submission to be handled by AJAX. The AJAX request, of course, uses jQuery to define the request and response handlers. I have talked a lot about this stuff, so I won't go into too much detail on it.
But, what you really want to take note of is that in the Success function, if the request was successful, the form controller triggers an event on the document: contactsUpdated. This is the centralized, event driven programming. The contact form knows that the contacts were updated (because that is what its AJAX call does); but, it has no idea who cares about this information. So, rather than communicating directly with any other HTML controls, it simply announces the contactsUpdated event. Now, any other controls (of which there might be more than one) that are interested in the contactsUpdated event will find out about it through document-event subscription.
Now that we have a way to add contacts, let's look at the Javascript controller for the list of contacts, contact_list.js:
Launch code in new window » Download code as text file »
- // Create a Javascript class to handle the contact list.
- function ContactList(){
- var objSelf = this;
-
- // Get a jQuery reference to the list.
- this.List = $( "#contact-list" );
-
- // Get jQuery references to the key parts of our table.
- // This way, we don't have to keep looking them up.
- // This will make it faster.
- this.DOMReferences = {
- Template: this.List.find( "tbody.dom-template" ),
- Content: this.List.find( "tbody.table-content" )
- };
-
- // Bind the listener for the contacts updated event.
- $( document ).bind(
- "contactsUpdated",
- function(){
- // Get the contacts for the contact list.
- objSelf.GetContacts();
- }
- );
-
- // Get the initial contact list.
- this.GetContacts();
- }
-
-
- // Define a method to delete the given contact.
- ContactList.prototype.DeleteContact = function( ID ){
- var objSelf = this;
-
- // Get contacts via AJAX.
- $.ajax(
- {
- type: "get",
- url: "./api/Contacts.cfc",
- data: {
- method: "DeleteContact",
- id: ID
- },
- dataType: "json",
-
- // Define request handlers.
- success: function( objResponse ){
- // Check to see if request was successful.
- if (objResponse.SUCCESS){
-
- // The request was successful. Trigger the
- // contacts updated event on the document.
- // That will trigger our own update as well
- // as anyone else listening.
- $( document ).trigger( "contactsUpdated" );
-
- } else {
-
- // The response was not successful. Show
- // an errors to the user.
- objSelf.ShowErrors( objResponse.ERRORS );
-
- }
- },
-
- error: function( objRequest, strError ){
- objSelf.ShowErrors( [ "An unknown connection error occurred." ] );
- }
- }
- );
- }
-
-
- // Define a method to get the contacts.
- ContactList.prototype.GetContacts = function(){
- var objSelf = this;
-
- // Get contacts via AJAX.
- $.ajax(
- {
- type: "get",
- url: "./api/Contacts.cfc",
- data: {
- method: "GetContacts"
- },
- dataType: "json",
-
- // Define request handlers.
- success: function( objResponse ){
- // Check to see if request was successful.
- if (objResponse.SUCCESS){
-
- // The request was successful. Show the
- // contacts in our table.
- objSelf.ShowContacts( objResponse.DATA );
-
- } else {
-
- // The response was not successful. Show
- // an errors to the user.
- objSelf.ShowErrors( objResponse.ERRORS );
-
- }
- },
-
- error: function( objRequest, strError ){
- objSelf.ShowErrors( [ "An unknown connection error occurred." ] );
- }
- }
- );
- }
-
-
- // Define a method to show the given contacts in the table. This
- // will clear and repopulate the table.
- ContactList.prototype.ShowContacts = function( arrContacts ){
- var objSelf = this;
-
- // Map the new contacts to an array of jQuery table rows.
- var arrRows = $.map(
- arrContacts,
- function( objContact, intIndex ){
- // Create a duplicate of our template row.
- var jRow = objSelf.DOMReferences.Template.children( "tr" ).clone();
-
- // Set the ID of the row.
- jRow.attr( "id", objContact.ID );
-
- // Store column values.
- jRow.find( "td.name-column" ).text( objContact.NAME );
- jRow.find( "td.hair-column" ).text( objContact.HAIR );
-
- // Bind the delete link.
- jRow.find( "td.actions-column a" )
- .attr( "href", "javascript:void( 0 )" )
- .click(
- function( objEvent ){
- // Delete the contact.
- objSelf.DeleteContact( objContact.ID );
-
- // Stop default event.
- return( false );
- }
- )
- ;
-
- // Return the raw DOM row.
- return( jRow.get( 0 ) );
- }
- );
-
- // Append the initalized rows to the table.
- this.DOMReferences.Content
- .empty()
- .append( arrRows )
- ;
- }
-
-
- // Define a method to help display any errors.
- ContactList.prototype.ShowErrors = function( arrErrors ){
- var strError = "Please review the following:\n";
-
- // Loop over each error to build up the error string.
- $.each(
- arrErrors,
- function( intI, strValue ){
- strError += ("\n- " + strValue);
- }
- );
-
- // Alert the error.
- alert( strError );
- }
-
-
- // ----------------------------------------------------- //
- // ----------------------------------------------------- //
-
-
- // When the document is ready, init contact list. It is important
- // that we wait for the DOM loaded event to that we have access to
- // the DOM elements.
- $(
- function(){
-
- // Create an instance of the contact list.
- var objContactList = new ContactList();
-
- }
- );
Like the previous controller, we are creating an instance of it which will then act as a controller to its HTML counterpart (the contact list). The first thing I want you to see here is that in the constructor, we are binding a listener to the contactsUpdated event on the document. This is the other half of the event driven programming. This controller is going to be listening to the document for events of type contactsUpdated; what this means it that when our contact form manually triggers that event on the document, our list controller is going to hear about it and is going to fire its internal GetContacts() method in response.
The GetContacts() method then makes an AJAX request to our ColdFusion API and gets back an API response object. It then passes the array of contacts off to the ShowContacts() method. This ShowContacts() method then loops over each contact and clones our row template. Remember back to the HTML? This row template was the non-displayed TBODY. Like I said, this is the easiest way to turn raw data into HTML. The cloned template is then populated with the given contact data and the raw TR reference is returned. The collection of resultant rows is then appended to the content TBODY of the table.
As part of the row template, you can see that a Delete method is also hooked up. This delete link executes the DeleteContacts() method which itself makes an AJAX call. Now, here's something really interesting - when the delete request comes back as successful, the list control doesn't turn around and show the contacts again; what it does is trigger the contactsUpdated event on the document. Because the list control is also listening for this event, this trigger causes the list to re-display itself. Now, you might ask why we don't just turn around and call the GetContacts() manually? Well, what if other elements on the page need to hear about this event? This event-announcement allows us to create scalable pages via centralized event delegation.
There's a lot going on here - AJAX applications are hairy beasts. But, I hope that by breaking this down into bite-sized files, it will make the work flow much easier to understand. I am sure you will have many questions, so feel free to post them below.
Download Code Snippet ZIP File
Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Newer Post
Reconciling Different Types Of APIs And What Data They Return
Older Post
Extending Classes In Object Oriented Javascript
Reader Comments
How on earth do you have time to develop these demonstrations?! I really appreciate how much you have put into this community. If Adobe isn't paying you, they should be.
A couple of notes-
You are looping over the array of id's looking for a match. What would you do differently if this was to become production? (I know that isn't the point of this demo at all.) Just seems like that could get huge if you had to loop over a ton a values. I really hope that CF implements some sort of native indexOf() soon.
How do you handle the templates if a user has css turned off (not likely, but this could happen)? I really like the template idea, but I could see some accessibility issues.
Great post, Ben.
@Brandon,
Thank man - I do enjoy this stuff a lot! This one took a few hours (a bit more than I expected when I first embarked on it).
First, the template issue. From what I have seen, there is no great way to create a template if the person has CSS turned off. You can store it in a textarea ( http://www.bennadel.com/index.cfm?dax=blog:1393.view ) but this still has to be hidden via CSS.
You can create the HTML using jQuery by passing in RAW HTML, as in:
$( "<tr><td>....</td></tr>" );
... but let's face it, that method sucks, has no readability, and even less maintainability :)
When it comes down to it, if I had to create templates and CSS was NOT a requirement, I would end up returning HTML directly in my AJAX response. Meaning, I would build the HTML string in ColdFusion and pass it back as my data (or part of my data).
If you don't need access to the RAW data, this is actually a nice way to go. The reason I try to use RAW data is only so I can keep the design and business logic separated.
Now, as far as looping over the Contacts array, first, in production, hopefully my API object wouldn't be doing that at all. Really, the API object should just be an entry point into the system and should turn around and call the application itself. That said, the API component, would have something like this:
<cfset LOCAL.Contact = VARIABLES.ContactService.GetContact( ID ) />
... where VARIABLES.ContactService is some sort of cached service object that handles that iteration for me.
Of course, now, we're just moving the responsibility down the line and we haven't really answered the question (I am just trying to emphasize the "thinness" of the API layer). In reality, I would probably be hitting a database to get this data rather than using an array of structs. Now, we get more into the application architecture itself and away from the API aspects of it.... which is a whole other conversation. And, since I am only learning OOP at this point, I can't say one way or the other; but, if I had to move this to production right now, I'd simply hit a database where the ID = ID.
Great Example,
I always enjoy seeing how other people solve a problem it definitely gives me perspective.
My approach to an ajax app with jquery and CF when it involves updating UI elements so to functionalize the generation of UI element. This way I can load the element server side or client side using the same code base.
In the above example I would have a function that returns a complete contact row. on the server side i would call <cfmodule .../> passing all the necessary variables to create a completely populated <tr>...</tr>
From the client side I would <cfc> that would proxy for custom partial template and I could do an ajax hit will all the nessary varables to return an complete contact '<tr>' and inject that into the Dom of the <table>
Thoughts?
@Peter,
I have no problems with that methodology at all. I have often times in AJAX returned the actual HTML string to simply be injected as you are saying. As I was just explaining to Brandon, that is especially crucial when you cannot rely on CSS for templates.
I don't think one way is better than the other; they have different levels of separation (between UI and back-end), but since they are both API calls, you simply have to make a decision if you care about that or not.
I would say with a public-facing API (ex. Twitter), you would tend to return raw data. But, when you are using the API internally for UI, I would say it is totally reasonable to return HTML strings.
@Ben
Great point about public vs. internal apps. If Twitter was to return anything but the RAW data, their app would not have been as successful.
I really like that you are extending the BaseAPI cfc. That seems like a great way to avoid duplicating code and keeping things consistent throughout the app.
Man, I really wish that I got to go to FL for the conference. I can see that you are all growing a lot. Do you know when the next one is going to be?
@Brandon,
Yeah, the Florida stuff was good. I'm still trying to wrap my head around the Event Driven Programming (EDP), but I can see some real value in it.
I believe the next one is going to be in Las Vegas, but I think that is purely client-side programming. I'll get some more information.
I know jQuery is the latest coolness, but if folks haven't looked into dojo (dojotoolkit.org), they should.
How the code is modularized and organized is swell. The templating system it uses kicks arse. dojo.hitch and whatnot are *awesome*... and all this stuff is right there at your fingertips.
Just seems like I'm seeing a lot of repetition out there in jQuery land- reminds me of prototype's "here's 100 ways to do the same thing!". That might be good, so who knows, but I love dojo's completeness.
Course, I guess you really have to bang your head on scope conflicts and whatnot to grok some of the finer points anyways, might as well have your nose close to the grindstone for the first bit, as it were.
Eh. =]
I second what Brandon said: Ben-- you are the man! Great stuff, as always. You're a boon to the community, no doubt. Thanks.
@Denny,
I have never personally tried Dojo, so I can't really speak on any of the aspects. jQuery was really my first advanced Javascript library; so maybe for me, I was just in the right place at the right time. I gather that they all do much of the same stuff, so it just comes down to which one you like personally.
One thing that I love about jQuery, however, has nothing to do with the technology - their documentation is outstanding. It's probably the best documentation I have seen for any technology. Period. So, while that is not related to what jQuery can do, it is definitely something that I personally take into account.
@Brandon,
You can access the underlying Java array indexOf method directly:
http://blog.critical-web.com/blog/index.cfm/2008/8/28/Using-indexOf-To-Find-Values-In-An-Array
It works really great, and is much faster than looping over the array to find your value's position. Just make sure and use javacast() to make sure you pass in the right type.
@Francois
I actually wrote an article about ColdFusion and arrays (http://melissa-brandon.com/2009/02/coldfusion-vs-php/) and noted that in order to find values you must access the underlying java. Some people have a real problem with this. I don't personally, but I really wish that the guys over at Adobe would add a lot of array functionality into CF9.
@Brandon,
Totally agree with you. At least there's a solution out there.
How would your coding be different in the .cfc and .js files if you were using a database instead of arrays
@Melvin,
I would be hitting a database with CFQuery to get the given contact any time an ID was passed-in. Also, I would have to create an array of structs manually from a query object when I wanted to return the contact list.
All of these changes would be done on the server; from the Client (browser) point of view, nothing would change. That's the beauty of an API - you can change how it works internally and nothing else needs to know about it.
Hey Ben, Thanks very much for posting this. I'm trying to wrap my head around jQuery, specifically for dynamically updating content, and your example is very helpful.
I have a specific use case where I'd like to implement it that isn't working very well cross browser-wise with my current CFAjax approach, and that is in a dropdown that is bound to a query. Users are able to add records via a cfwindow popup and they should appear immediately as a new choice from the dropdown.
I get pretty much how one would generate the HTML for the dropdown from your excellent example. What escapes me is how the select mechanism would work when the dropdown is dynamically generated. This is what currently isn't working in Safari. In Safari, you open the dropdown to select something and your choice isn't selected when you click on it.
Here is the approach I'm currently using, in case you are interested :
http://www.stevenksavage.com/content/500/3.en.cfm
I know I may be asking a lot, but any pointers you may have would be very much appreciated.
Thanks,
Nando
@Nando,
You either have two choices:
1. Once the new data is entered, you can get the entire set of options back from the server to repopulate the drop down.
2. You can add ONLY the new option to the bottom of the drop down.
While #2 might seem more efficient, sometimes #1 is more simple such that you don't have to pass data back and forth between the main page on the cfwindow page.
Ok.....I am a complete novice when it comes to AJAX (and I'm okay with that.) I would love to get a working example of this set up. I saved off the files, but nothing... Aside from these files, is there anything else needed to enable or set up AJAX to run? Is there some AJAX framework needed? Can I simple put these files where they are needed and see the example? Again, I am a complete novice with AJAX, so can you please hold my hand with this!!?
By the way, you go well beyond to explain and notate your code and its functionality. I really appreciate that!! I was just clueless on this whole AJAX thangy!
@Jaeger,
Later today, I can try to dig up the original files and send them to you. You can't just save the code above - you would have to save the files with the appropriate names.
I'm glad you're liking the explanations!
Suppose i want to populate the state tables based on result selected in country drop down.
My select is a function (mentioned below), how do i call the values of state inside a javascript form submit? ? can you pls specify?
#select(objectname ="state",options="select*from states" ???
OR do i need to mention a function in options part and then define that function to select values from the databse
<cffunction name="select" returntype="string" access="public" output="false" hint="Builds and returns a string containing a select form control based on the supplied `objectName` and `property`.">
<cfargument name="objectName" type="string" required="true" >
<cfargument name="property" type="string" required="true" >
<cfargument name="options" type="any" required="true" >
<cfargument name="label" type="string" required="false" default="" >
<cfargument name="wrapLabel" type="boolean" required="false" default="true" >
<cfargument name="prepend" type="string" required="false" default="" >
<cfargument name="append" type="string" required="false" default="" >
<cfargument name="prependToLabel" type="string" required="false" default="" >
<cfargument name="appendToLabel" type="string" required="false" default="" >
<cfargument name="errorElement" type="string" required="false" default="div" >
<cfargument name="includeBlank" type="boolean" required="false" default="false" >
<cfargument name="multiple" type="boolean" required="false" default="false" >
<cfargument name="valueField" type="string" required="false" default="id" >
<cfargument name="textField" type="string" required="false" default="name" hint="The column to use for the value of each list element that the end user will see, used only when a query has been supplied in the `options` argument">
<cfset var loc = {}>
<cfset arguments.$namedArguments = "objectName,property,options,includeBlank,multiple,valueField,textField,label,wrapLabel,prepend,append,prependToLabel,appendToLabel,errorElement">
<cfset loc.attributes = $getAttributes(argumentCollection=arguments)>
<cfset loc.output = "">
<cfset loc.output = loc.output & $formBeforeElement(argumentCollection=arguments)>
<cfset loc.output = loc.output & "<select name=""#listLast(arguments.objectName,'.')#[#arguments.property#]"" id=""#listLast(arguments.objectName,'.')#-#arguments.property#""">
<cfif arguments.multiple>
<cfset loc.output = loc.output & " multiple">
</cfif>
<cfset loc.output = loc.output & loc.attributes & ">">
<cfif NOT IsBoolean(arguments.includeBlank) OR arguments.includeBlank>
<cfif NOT IsBoolean(arguments.includeBlank)>
<cfset loc.text = arguments.includeBlank>
<cfelse>
<cfset loc.text = "">
</cfif>
<cfset loc.output = loc.output & "<option value="""">#loc.text#</option>">
</cfif>
<cfset loc.output = loc.output & $optionsForSelect(argumentCollection=arguments)>
<cfset loc.output = loc.output & "</select>">
<cfset loc.output = loc.output & $formAfterElement(argumentCollection=arguments)>
<cfreturn loc.output>
</cffunction>
Thanks Ben, I've been looking for something like this for while this is a great AJAX/Coldfusion tutorial. Keep up the great work!
@Brad,
Glad to help!



