Ask Ben: Building An AJAX, jQuery, And ColdFusion Powered Application
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:
<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:
<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:
<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:
<!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:
// 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:
// 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.
Want to use code from this post? Check out the license.
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 ( 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!
Hey Ben,
I feel like a jerk if I don't tell you every time I comment how great the site is and how helpful your posts are to me. So, thanks... AGAIN! :)
Anyhow, I saw that you used curly braces in a CFSET above to define the LOCAL scope??? I said wtf is that and wrote my favorite test below to find out:
<cfset var LOCAL = {} />
<cfset LOCAL.shit = "poop" />
<cfdump var = "He called the shit #shit#!" />
Unfortunately, I got an error. :( I guess it's because my company's still using CF7. I googled a bit and found out what the LOCAL scope was, but what's the deal with curly braces?
Thanks Man
@Grant,
Ha ha ha, thanks :) The meaning of LOCAL has actually changed of the past few versions of ColdFusion. If you are CF7, you would need to do something like this:
<cfset var local = structNew() />
Essentially, this just creates a locally-scoped struct into which other variables can be put *without* being var'd.
In CF8, we got implicit struct and array notation. In CF8, {} is the implicit struct and [] is the implicit array. As such, in CF8, the line above became:
<cfset var local = {} />
Notice that {} and structNew() are equivalent.
In CF9 now, the local scope is actually implicitly created; meaning, once you are inside a function scope, you can refer to "local" without manually creating it (it's a scope just like Variables or This or Request).
I hope that clears it up a bit.
@Ben
Yup, perfectly. Thanks.
@Ben
While we're on the subject of scopes and vars... I was just implementing some of this code in an application I'm building and I ran across this question:
"When I write models [beans] or data objects, I've been using the THIS scope to keep my variables protected. Other snippets I've picked up along the way have used VARIABLES.instance to keep the variables contained within the object. What's the difference between THIS, VARIABLES.instance and var?"
Here's an example from the init function of my contact bean:
<cfscript>
this.id(arguments.id);
this.name(arguments.name);
this.email(arguments.email);
</cfscript>
Here's an example getter/setter from my contact bean:
<cffunction name="email" access="public">
<cfargument name="email" required="false" />
<cfif IsDefined('arguments.email')>
<cfset variables.instance.email = arguments.email />
<cfelse>
<cfreturn variables.instance.email />
</cfif>
</cffunction>
This all works and I can encapsulate the data and [as much as CF7 allows] the functionality of the application, but I don't want to be in the dark on something I'm using so regularly in my code.
Can you please explain?
thanks!
Note: Single getter/setter code was learned from an article by Hal Helms [ http://www.adobe.com/devnet/coldfusion/articles/oo_coldfusion.html ]
@Grant,
The THIS scope is the "public" scope of the Components. This means that this-scoped variables can be access both internally and externally to the component (it is not private data).
The VARIABLES scope, on the other hand, is the "private" scope of the Components. This means that variables-scoped variables can *only* be accessed from within the component; any attempt at variable access from an external point will lead to an undefined variable exception.
As far as "Variables.instance", this is sometimes a convention used to package private properties within the private scope. This could be useful if you have properties that are the same name as functions (so they don't conflict).
With the release of ColdFusion 9, however, the implicit getters / setters get from and store variables directly into the Variables scope; as such, I am thinking the "Variable.instance" concept is a bit out dates. To be forward-thinking (and also to perhaps align more closely with other programming languages), probably using the Variables scope (without any special sub-packaging) is preferable.
I am very new to ColdFusion and this post is exactly what I was looking for. Thanks!
Does using access="remote" create a security risk?
Specifically I am using jquery to access a function that inputs data into a SQL database. I make sure the users are logged in before they can view/use the page, but with access="remote" couldn't someone with no login rights to the page craft thier own webpage to make updates to my database? (provided they knew that the function existed)
@Nathan,
Not saying this is the correct way to do this, but I've handled this in many different ways depending on how "secure" the function needs to be.
In a case like yours (users needs to be logged in anyways), I would probably use a controller layer (another function) as the remote function, which does a little bit of business logic for you before submitting to the database. Since the user needs to be logged in to view/use the page, you could re-check their creditenials in the controller function, if they pass then call your input query function (which you now set to a public or private function). The reason I wouldn't do the business logic in the same function as your db logic, is for code re-use. You may want to have another function call that db function somewhere else in your app. You could also go a token based route. Let me know if this makes any sense or if I'm just confusing the crap out of you (or anyone else for that matter).
@Peter,
Your post makes perfect sense, thanks. I am still struggling with the controller function syntax. All of my login and verification related functions are within my Application.cfc file. One of the functions simply verifies if the user has previously logged in before serving up the page. I thought I could call this function from within the JSON DB Edit function via <cfinvoke> like my pages do, but it doesn't seem to work. I know this is remedial, but it has me stumped.
@all,
Why does ColdFusion JSON requests insist on returning column row names in all upper case? On the query level, I alias the true column names names that make more sense for the end user. This information is then entered into the page with the appropriate look and feel (and all caps stick out like a sore thumb). Writing javascript to re-condition the data in this manner seems like hack to me. Is there a way to force CF to return the column names exactly as I specify?
I've struggled with returning JSON from ColdFusion CFCs for a while because I (mysteriously) get lots of white space/new lines that appear before the actual JSON result (check the response in Firebug under the Console or Net tab). Not really a problem in the example above since dataType: "json" cleans up the response *after* it is received. But the purist in me still wanted to send a clean JSON object and not rely on the requestor.
One trick I learned recently was to put the following in the CFC that returns the JSON.
<cfcontent type="application/json">
This prevents the white space/new lines from being included in the response, and the response is a true JSON file type now (check Firebug; you'll see a new JSON tab under the Console/Net views). Also, the response header correctly has:
Content-Type application/json
There's my warm and fuzzy!
Ben, thanks for all your helpful posts!
Eric P.
@Eric,
Neat trick, I was able to get rid of most of the lines of whitespace following your advice. Some whitespace still remains. With a bit of playing around, I found that the remaining whitespace is from the whitespace within your function. This doesn't sit well with me, so I did some more stuff:
1) Put a html comment (<!-- test -->) inside your function. Now check in firebug. First you will notice your ajax got the JSON data it expected plus that comment. You will probably notice that you have just sent yourself invalid JSON and your page probably didn't load properly.
2)Now remove your <cfreturn whatever> line. Your function still returns that comment but not your query. So for whatever reason, CF returns more than you specify.
The obvious answer is don't use html comments in coldfusion functions. But keep in mind that you may be getting more than just the JSON you were looking for when it comes to debugging a non-functioning script.
@Nathan,
In the function that returns the JSON, you can try placing output="false" as a parameter to the function and then use a cfsavecontent tag surrounding your desired output (you'll need cfoutput tags in the function) and then as a last touch, strip out the whitespace from the variable before returning it. Use CF comments to keep your comments out of the output returned.
@Nathan,
There is nothing inherently less "secure" about a remote CFC method than there is a CFM page. If you take a step back and think about what's going on, realize that a CFC and CFM request are all just page requests; the only difference at all is that the CFC "remote" request does a little of the nitty-gritty work for you (ie. packaging the return value in an alternative data type).
As such, there's nothing special, security-wise, about CFC method. As @Peter says, you just need to take the same precautions regarding security as you would with a CFM request. You can perform security checks in the Application.cfc or in a more controller-level CFC method (as @Peter alludes to).
As far as queries, I've found returning arrays-of-structs more client-friendly for data returns. Of course, this would require you to translate that yourself (there is no auto-conversion for query-to-array).
@Eric,
Nice tip with the CFContent. Not only does that reset the content buffer, jQuery requires the "json" mime type to be set (or rather, it tells you it prefers it if you do) for JSON data. So, it's good for a number of reasons.
I wonder if returnFormat="json" will set the mimetype in the response?
> I wonder if returnFormat="json" will set the mimetype in the response?
Just checked; it does not.
I show:
Content-Type text/html; charset=UTF-8
I usually use returnFormat="json" with returntype="Struct", and it appears the only thing you get from doing that is you can return a struct and CF will automatically convert that to JSON format. You still need the cfcontent tag to force the content type to JSON.
Eric P.
@Eric,
Thanks for looking into that. I know that jQuery depends on the "json" mime-type. You *can* use the dataType:"json" setting to force it; but, it is documented to expect the proper mime-type.
I'm new to CFCs and AJAX and am running into what I'm assuming is a rudimentary problem: when I load the html file from my cf server I get the "unknown communication error" message.
A quick look at application.log reveals:
"Element CONTACTS is undefined in APPLICATION. The specific sequence of files included or processed is: E:\WEBROOT\askben\api\Contacts.cfc, line: 209"
For reference, here's how I named & structured the files (I'm guessing this is when my problem is coming from):
./index.cfm
./contact_form.js
./contact_list.js
./jquery-1.3.2.min.js
./api/
./api/AJAXDemo.cfc
./api/BaseAPI.cfc
./api/Contacts.cfc
thanks for any insight =)
Peter
Ben - thanks for this great example. I am using CF8 and was getting an error in the BaseAPI component when trying to use the implicit array creation code Errors = []. I used Firebug to isolate the problem, and had to explicitly define the array like Errors = ArrayNew(1).
Now the example is running in my localhost.
@Peter,
It looks like you are making some sort of rerence to "Application.contacts" that is not working. It looks like Application's onApplicationStart() method is not executing properly. Have a look at your application log again and see if something is erring in Application.cfc.
@James,
Yeah, sounds like your localhost is running CF7? Or perhaps is missing some patch that updated the implicit array / struct notation. I can't remember if there were any bugs fixed post-CF8-launch. Glad you got it working.
Ben, long time reader, second time writer...
Is there a way you can call a CFC function in AJAX?
I'm trying to do a very simple task and am at the end of my rope.
Based off the W3C ajax_example.asp, I figured I could retrieve a function result from a CFM page. So, I put in <cfoutput>#Session.AcctID#</cfoutput> (there wasn't anything except that single tag in the CFM file). But, I got back 2,000 lines of white space before the result, "2215". Adding cfprocessingdirective suppressWhiteSpace did nothing.
Secondly, I tried adding a function to a CFC, and calling. That resulted in, "404 Cannot find URL "/site/page.cfm#CFC.Function()#" That made sense, but another roadblock.
Third, I reverted down to a simple text file. Problem with that? YES. The 2nd time around through the function (called from a button), the value remained the same even though the text file had been changed/saved.
We're still on 6.1, upgrading to 9 ...someday.
The W3C made it look incredibly simple, you said it was complicated. I was hoping you were wrong :-/ Can you shed light?
@Randall - All you have to do is use something like $.getJSON (jQuery) to do your call and pass any arguments along. You pass the function name in the parameters and use the variable "method" to specify your function. Something like this:
$("/site/page.cfc?method=MYFUNCTION&MYVAR=0",function( result, status) {
... handle the callback
});
Obviously, this is a JSON call. But you could use AJAX or any other jQuery calls. Looks like you were just curious how to call the CF function.
Hope this helps.
@Randall
With CFCs there's a simple (but not immediately obvious) url request method:
http://path/to/[CFCname].cfc?method=[cffunctionName]&[URL_Param]=whatever
The cffunction that you select has to be one that allows remote queries.
Here's a jquery example:
var senduser = $("#userNameField").val();
$.ajax({
type: 'GET',
dataType: 'JSON',
url: 'aJSONresult.cfc?method=getJSON&ReturnFormat=json&USER_ID=' + senduser,
success: function(returnedMoveList) {
/* your callback stuff here /*
});
The cfc in question could be set up like
<cfcomponent>
<cffunction name="getJSON" access="remote" returntype="string" returnformat="json">
<!---Declare Variables--->
<cfargument name="USER_ID" type="string" required="yes" default="1">
<cfset var sendJSON = "" >
<!---whatever you need to do --->
<cfreturn serializeJSON(sendJSON)>
</cffunction>
</cfcomponent>
One thing you have to be wary of is that the coldfusion serializeJSON() will mess with numerical types (i.e. a serial number 0012423 will come in the JSON file as 12423.00). There isn't a really great solution other than to work around the bug or roll your own JSON with cfoutput, as far as I could tell.
@Doug - Appreciated, but I was hoping to steer clear of jQ and JSON. We don't have the most flexible environment here, and I am concerned about conflicts if we add jQuery to our mix. I'm a 1.5 man band so any screwups can quickly eat up my 40 hour work week.
@Randall - No problem, you don't have to use jQuery. I was just using that as an example. Like Peter Hanley mentioned you can call any function in a CFC by using the url:
http://path/to/[CFCname].cfc?method=[cffunctionName]&[URL_Param]=whatever
So whatever your using to do your AJAX call, just use the URL format above and it should work.
@Peter: You can also specify returnFormat=JSON.
@Randall - Forgot to mention. You might have to set the access type for the function. i.e. access="remote" or access="public".
This post is primarily for informative purposes for anybody who might be in the same boat as this post (in other words, it's not a question):
I was able to get the URL cfc call after switching access="public" to access="remote" (thanks Doug!). However, what is returned is...
2000+ blank lines and
<wddxPacket version='1.0'><header/><data><number>2343.0</number></data></wddxPacket>
where 2343.0 is my account ID. So, the /simple/ result includes a decimal point and 2000 blank lines I didn't want.
I think you guys are right about jQuery. Looks like it might do the trick. Haven't gotten that far just yet.
@Randall, you either need to output text/plain content like:
<cfset binResponse = ToBinary( ToBase64(strOutput ) ) />
<cfcontent reset="true" />
<cfheader name="content-length" value="#ArrayLen( binResponse )#" />
<cfcontent type="text/html" variable="#binResponse#" />
or request the return variable in JSON like:
returnFormat=JSON (You put this in the URL string in the jQuery call)
p.s. the CFONTENT RESET=TRUE will get rid of all your lines.
@Randall -
The decimal point thing is a problem with Cold Fusion's serializeJSON() function. You can work around it by either custom writing a json file, using a cfc that will make a json file for you, or just using the numbers as is.
You can load the numbers into an int to strip off the decimal points - I think the main problem is when you have serial numbers with leading 0s.
As far as the blank lines go, you can also try adding this to your cfc function:
<cfprocessingdirective suppresswhitespace="yes">
<cfsetting showdebugoutput="no">
( see the example on livedocs for more context:
http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-79fa.html )
Ben,
I've been using Coldfusion for years. However I have just been using it as a means to an end. I would like to harness the power more and understand more about it. Specifically, the Application.cfc. I remember using the Application.cfm. Stored some site wide variables in there and that was about it. I am completely lost with the Application.cfc. Is that literally my application in that file and I point to other CFCs? If so, how do I go about invoking other CFCs, communicating between them and pretty much getting my application to function. If that is not how it is used, can you please enlighten me.
Thanks!
@James,
Application.cfc is the ColdFusion application framework component. It is not your application; but, it allows you to define your application settings and hook into application level events. Try taking a look at these two links:
Application.cfc Tutorial
www.bennadel.com/blog/726-ColdFusion-Application-cfc-Tutorial-And-Application-cfc-Reference.htm
Mastering the ColdFusion Application Framework
www.bennadel.com/blog/1933-Mastering-The-ColdFusion-Application-Framework.htm
The latter one might be a bit above your head if you are just getting into it. Hopefully, this weekend or something, I'll record a video for the latter link (it was a presentation I gave).
Hi Ben
Long time reader first time poster :)
Im changing a cfc access attribute from public to remote to allow it to be called using jquery. I noticed some people mentioning security issues above - Changing the access attribute opens this function up to the world via webservice calls/ external xmlhttprequests etc but i only want this function to be called from my application.
If i was using coldfusions ajax functions i could use the verifyclient attribute but this is not possible as it has to be jquery and if you use jquery with this verifyclient attribute set to true i get an unauthorised request error.
Also in this app there is no login/session management so i cant simply boot them out for not being logged in.I was thinking on the lines of some sort of (unique per request) generated token that i could pass along with each request. How would you or some fellow readers approach this issue? How would i block external xmlhttpRequests and users using my function as a webservice?
Im really just worried about Denial of Service attacks and screenscrapers stealing the content (the content is free but only free from this website). I just dont want to gift them another method that they can query at will.
Many Thanks
Ethan
mockfunction.cfc - what could i add for some security checks here
<cfcomponent name="mockFunction" hint="mockfunctions">
<cffunction name="getResults" access="remote" returntype="struct">
<cfargument name="kw1" required="true">
<cfargument name="kw2" required="true">
<cfset var stReturn = structNew()>
<cfset stReturn.success = true>
<cfset stReturn.errors = arrayNew(1)>
<cfset stReturn.data = "">
<!--- some security checks here --->
<!--- fetch results here --->
<cfreturn stReturn>
</cffunction>
</cfcomponent>
@enda,
Now when you say No session management / login, i'm assuming you just mean you don't force your users to login. What you could do is setup some kind of session token / variable when the page loads up and check to make sure that session variable exists from within your remote cfc function before executing the function. You don't really want to pass the token with jQuery as anyone can read it. I would setup a global check session token function, that you could call from any of your remote cfc functions that you only want your server to have access (this is not required but smart for code re-use). You could also check the referrer of the request, but I wouldn't rely on this as it can easily be manipulated. I personally think the session route is the most secure way, but others may disagree! Hope this helps!
Peter
Sorry just noticed your name is Ethan, I just looked at the heading line and saw enda.
@Enda,
I have the same first question that I think @Peter had - although you are not requiring a login, do you at least have session management turned on? If you do, that means that each use will get a CFID/CFTOKEN cookie (assuming you are using standard session cookies).
If that is the case, then jQuery *will* pass those session cookies along with every AJAX request (remember AJAX is really just an HTTP request complete with headers and cookies). Once you accept that fact, you can take steps to ensure that no remote CFC call invokes a *new* session.
CFC-based requests still get routed through Application.cfc. What I might do is something like this:
<cffunction name="onSessionStart">
<cfset request.isInitialRequest = true />
</cffunction>
<cffunction name="onRequestStart">
<cfif (... first request AND cfc-method)>
<cfheader statuscode="401" statustext="Unauthorized" />
<cfabort />
</cfif>
</cffunction>
... The pseudo-code here is basically throwing a 401-Unauthorized exception if a remote-CFC call invokes a *new* session.
Does that make any sense?
Hi Ben
Perhaps a silly question, but could you please explain the use of the word "prototype" in the example?
For example:
ContactForm.prototype.ShowErrors()
Why not just ContactForm.ShowErrors()
I'm sure there's a reason for doing it this way - I just can't see it :)
Thanks
@Paolo,
The prototype is a form of inheritance. Javascript uses prototypal inheritance instead of class-based inheritance. When you define a Function's prototype, it's like saying "when you create an instance of this function, use the class methods defined within the prototype."
If you were to try and define the ShowErrors function off of the ContactForm itself, it wouldn't be used a class method on new instances of ContacForm (ie. new ContactForm()); rather, it would just remain a property of the ContactForm class definition.
There's a lot going on there, but just realize that the prototype object is somewhat akin to defining class methods in normal inheritance.... somewhat.
Hi Ben,
I am facing a strange problem.
I have a test.cfm page which I m calling as a url using jquery jax.
In test.cfm, I am invoking a test.cfc which is under components folder like this:
<cfscript> obj=CreateObject("component","component.cfcpath#test");
getDetails=test.getEmpDetails();
</cfscript>
I just simply get the error that components.test does not exist though this invoke procedure works fine in a normal cfm page.
I just had to write inline queries then to make my code work.
I know cfcs can be directly called using jquery ajax but I have to call it in a cfm page.
Please advise.
Sorry ,
the cfscript is like this:
<cfscript> obj=CreateObject("component","component.test");
getDetails=obj.getEmpDetails();
</cfscript>
Sorry for mistyping
@Abhijit,
I am not sure I understand what you mean. Are you saying that when you call the CFM page directly (as in the browser's URL bar) it works; but when you call the same CFM page via AJAX, it gives you this error? I can't imagine why that would happen.
@abhijit,
Does the .cfm file work if you use $.load instead of .ajax?
(substitute #ajaxDestination for whatever selector makes sense to contain the results):
@Ben, what i meant was If call the same CFM page via AJAX, it works.But if i invoke a cfc in the same ajax page, the ajax page fails to recognise the cfc path........although the cfc path is correct...it says like the cfc does not exist
@Peter Hanley..will try that
@Abhijit,
Are you in a sub-directory and trying to go up a directory to get to the components directory? You can't use dot-notation to move "up" a directory structure.
@Ben,
This
<cfscript> obj=CreateObject("component","component.test");
getDetails=obj.getEmpDetails();
</cfscript>
works fine if I just call it inside a cfm page and run it...no error...but if i call this cfm page via ajax, it throws up error........
Directory structure is something like this:
sitename/component/test.cfc
@Abhijit,
What directory is the page *making* the AJAX request in?
@Ben,
it will be sitename/module/index.cfm....here i am calling the cfm page via ajax
@Abhijit,
Right, so your directory structure is like this:
sitename/module/index.cfm
sitename/component/test.cfm
If you are trying to create the CFC from *within* the Module folder, you cannot go UP a directory then go back DOWN into the component directory by simply using dot-class-path.
You have to create a mapping for your application. In your Application.cfc, try creating something like this:
<cfset this.mappings[ "/component" ] = "full/path/to/components" />
Then, you should be able to use "component.yourCFC" from anywhere within your application.
@Ben,
Awesome Ben.thanks always for your help.
I think this should work.
@Abhijit,
Let us know if that works.
@Ben,
It works wonderfully ben................thanks for the gr8 help
@Abhijit,
No problem - glad we could get it solved. Application-specific path mappings are your friend :)
Just wanted to drop a line to say thanks for the great site, this article was immensely useful!
Also, a tip of the hat for responding to questions well after the article was written :)
Thanks!
@Cliff,
My pleasure. I'm always excited to hear that posts like this continue to deliver value long after their publication; and I honestly do get a lot out of the continued conversation. Thanks for being part of it :)
I have an OnRequest method in my application.cfc and it will not allow me to make ajax calls or I cant get the data. When i comment it out, it works fine. Is there something that needs to be in the OnRequest so that it does not mess with the ajax calls?
Ben observed a peculiar behaviour, I might be wrong....
In cf8 and cf9, i have a site inside my webroot webroot/sitename.
Sitename\components folder also I have.
to access any cfc from components, I had to create the required mapping to call a component like component.test...if no mapping is there i can always use sitename.components.test
But in cf9, I did not create any mapping..still was able to use components.test.
Please correct me
@Kevin,
OnRequest() is a way to manage ALL of the CFM/CFC requests that come through the application. Basically, it's your way of saying: "Hey ColdFusion, I'm going to tell YOU how to execute this request." The reason you're not getting any data is because you are not returning any data explicitly. By commenting out the event handler, you allow ColdFusion to handle the request execution (which will package the response for you).
If you are not using the onRequest() event handler, just comment it out.
You might also want to check out my "Mastering the ColdFusion Application Framework" presentation if you want some more insight into how the Application.cfc component works:
www.bennadel.com/blog/1933-Mastering-The-ColdFusion-Application-Framework.htm
That presentation details how you might handle returning data from a CFC ***if*** you want to / have a need to do it manually.
Also, here's another way to "imagine" the onRequest() event handler:
www.bennadel.com/blog/805-ColdFusion-Application-cfc-OnRequest-Creates-A-Component-Mixin.htm
@Abhijit,
That is very strange indeed. I don't have CF9 on this computer, so I can't confirm. Are you sure you're not just in the root of a particular site in which the "components" folder is within reach? If you are in the a file that is in the root of the site in the same directory as the components directory, you can access the components directory.
@Ben,
Right I figured out what the problem was. I some illogical layout for what I was trying to do. I can't remember the exact cause at this time. It had something to do with me using onRequest() and onRequestStart()
Hi Ben,
Thank you for all your brilliant work. Really enjoy it and appreciate the amount of work you put into it.
I have one quick question: how do you manage back/forward browser buttons with ajax type of application?
Thanks and keep up the good work
Richard
Hi Ben,
I would appreciate it if you could send me these files, I really need to play with this stuff!
Thanks
I was just debugging some code made from this post and found that jquery doesn't seam to be able to parse a secure josn data response so I turned it off in the cfc functions but do you know of a way other than manually removing the secure bit and then parsing it
I don't seem to be able to get this example to work.
I'm using CF8 (still), and have the environment setup as:
On load (and any submit), I'm getting the "An unknown connection error occurred" error.
If I dump out the strError, it just says "parseerror".
Firebug shows 5 request, all with 200 OK status:
The Contacts.cfc request has headers, but nothing in the response.
APPLICATION.contacts exists (though it's empty).
And nothing shows in application.log or exception.log.
Can't figure out what's going on. Obviously I'm using a newer version of jQuery... was there possibly a change in the $.ajax method between the two versions that is creating an incompatibility??
Anyone else run into this?
Any insight would be appreciated,
-Carl
@Carl Steinhilber,
this is what was happening to me also, the only way I could get around it was to add secureJSON="no" to the cfc methods I might go ask the jQuery dudes if they have any idea what is up
Thanks @Ben!
Turning off secureJSON worked nicely.
I certainly am curious as to what's going on, though.
Thanks again!
'keeping it management.' instead of 'keeping it manageable'?
Little bit of trivial bad grammar on www.bennadel.com/blog/1515-Ask-Ben-Building-An-AJAX-jQuery-And-ColdFusion-Powered-Application.htm
but since you are a consultant, I figured you'd want to be really polished in your communication.
best wishes.
@Andrew,
Pointless comment. I can see that you have made two comments on this site with one being about the misspelling of a word. Don't troll here here. Go fix the mistake you made in your other comment first before being grammar troll.
First, excellent site Ben. Ben following it for years, and whenever I Google a CF question 9 times out of 10 yours is always the firs to come up.
I put the example on my dev server at work. I was playing with it in IE 9 and it didn't seem to be working. I troubleshooted for about an hour, and then decided to try it in Firefox and Chrome. Of course, it works just fine.
What's interesting is that the only thing that DOES NOT seem to work is the return of the Contact List to display on the IE 9 window. I can add data in the page in IE9 and I'll see it reflected on the Firefox and Chrome sessions after I load the page or add a Contact on those sessions. I've now also tested this in IE 8 and get similar results.
Unfortunately I have to support all major browsers for the sites I develop, so it's important for me to figure out what's wrong with this functionality in IE before I can implement it in production in any future endeavor.
Anyone experience something similar, or know of a work-around? Maybe there is some security setting applied by AD on our corporate network that could be having an effect? I haven't had a chance to try this outside of the corporate network, but will likely try tonight when I get home.
I wanted to add a few things to my previous post, and normally being grammar/spelling/punctuation Nazi I apologize for the previous posts mistakes and any I might make here.
Anyway, I took Ben's example and wrote a version of it that pulls data from a users table in our test environment. The curious thing about how this appears to be working/failing in IE is that the initial layout and data pull come through. For example, when the page loads initially I have it pull a set listing of users, about 64, very similar to how the original code will pull the current Name/Hair list if the Application hasn't timed out. However, after that initial pull any filtering I do on the recordset isn't reflected in IE. I can wach the code process in the IE 9 debug console and everything appears to be working as intended, right down to the empty() and append(), but nothing shows up on the page. Even if I filter the recordset in another browser and come back to Ctrl+F5 IE it won't reflect the new data. Only when I close the tab/browser and completely reload do I get a different recordset on the page.
More than hoping I'll use this process in any future code endeavors I'm now just baffled at why this is happening. I want to understand it as to enrich my knowledge of jQuery and using it with ColdFusion.
If anyone has any idea's I'd love to hear them. Else I might post this over on stackoverflow to see what that community might have to say.
@Matt - Sounds like a caching problem. I think jQuery has a "cache" true/false feature, but not 100% sure. Anyway, I always just add "?cache=#TimeFormat(Now(),'hhmmss')#" to all of my ajax URL's.
@Doug,
Freaking awesome, that did it, thank you! I added 'cache: false' to the ajax request for GetContacts and it immediately solved the problem.
I've been digging around the net for the past two days trying to figure this out. I'm new to jQuery, and obviously have much to learn. Thank you for imparting your wisdom.
Thanks for this very useful post (& comments). Much appreciated.
All goes well, until I try to load the page using jquery load or via a cfdiv. Does anyone know if this could work ?
Thanks Ben for this post. I refer to your blogs all the time when programming.
I was so lost though with the prototype stuff and all the OO javascript stuff. I picked up "Javascript the good parts" by Douglas Crockford and WOW, now I get it. I recommend this for any of your readers that are struggling with this.
Anyway,
I am writing my first heavy JS application that will use AJAX to add items to a list much like your sample. My question is that in an event driven framework, it seems to me that the add event could trigger a broadcast.
Trigger ="Add Contact"
Bindings"
1) -> Insert into contacts database table
2) -> add row to HTML table.
Now, I realize if something gets hozed up in either 1) or 2) above then the html table would be out of sync with the DB table. I expect that this approach would require extensive error handling.
My concern is that in my app, the overhead of purging the html table after each add request may cause unnecessary overhead.
Thoughts?
Hey Ben great post. Just like @Carl Steinhilber I am having the same trouble with the -'parseerror'. But I can only reach
I have added
on all my fuctions in Application.cfc, BaseAPI.cfc, and Contacts.cfc. I am also new to jquery, I might be doing something else wrong. If you can think of an easier example with jquery/ajax and cf lmk.
Thank again for all you do.
Hi,Ben,Thanks posting this,but $.ajax method has been submited twice,how to salved it?
Always great work Ben! I'd hate to think where many of us would be without you : )
I have taken an application using your BaseAPI / extends logic and moved it to my local installation for better/faster development for my client. HOWEVER, I am about to rip my hair out because I CANNOT get this error to go away: "Could not find the ColdFusion component or interface BaseAPI". It is in the root. I have tried moving it to a subdirectory, several attempts at mapping all to no avail.
I even looked into the CF components.cfc file and it is empty on the production server. I'm completely stumped!
This tutorial is still proving helpful. Just wanted to say thanks.
I am trying to use the basics of your demo here to create a dynamic vehicle addition page for our subscribers. I got it working fantastically in Firefox. And then I tried it in IE and it does not work. Most of my research has not found a solution to this most unfortunate IE problem.
I realize this article is from 2009 but thought you might have some insight into what is different about IE 8/9 from Firefox that is causing me no amount of grief!
Sincerely,
Leslie
@Myself! Just read a previous comment about caching. That solved the IE problem!
First thanks Ben for doing all these posts. Very helpful to the community.
I'm currently building a site and wanted to implement something like this for the login section. I've followed your example I am able to call the cfc. I know this because every time it's called I email myself the arguments and the struct that's being created and returned. However, I always end up falling in the "error" portion when the response comes back. The objRequest is empty and the "textStatus" of the error message displays "error". If I input the url to the cfc in a browser with the method name and arguments it correctly outputs the returning struct in a json format. I don't know what's going on.
Please help if you can. Thank you.
@Myself,
I'd like to add that I am trying this using a POST. I've tried GET as well and same result.