Skip to main content
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Jonathon Snook
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Jonathon Snook ( @snookca )

Learning AJAX From the Ground Up

By on

I am just now getting into AJAX, and no, I have not been living in cave in Mars with my fingers in my ears. But yes, it's time to get on the horse. For those of you who don't know, AJAX is a way to send and receive data from the server without having to reload the current web page. This is done through the XmlHttpRequest object and some sweet Javascript.

AJAX in and of itself is very simple. It's just a matter of creating the connection object, sending the data, and handling the response. This is where I am starting. Then, I will tackle the ins and outs of actually writing an effective AJAX module or application with DHTML and ColdFusion.

I am a hands-on learner. So, I must create to learn. As such, I have created my own brand of Http Connection object wrapper. I am sure this has been done a few million times before by other people, but now I know how it all works and I can tweak it suite my needs. In the code below, you will see that I do not use any serialization or variables or any WDDX or things of that nature. I leverage the fact that Javascript is an interpreted language and its code can be generated and evaluated on the fly. That's how this AJAX system handles data returns.

Data is expected to be returned as regular Javascript. So, for instance, on the server if you wanted to return a struct, you would return it in this fashion:

new Object({ ben:"is cool", molly:"is sexy", arnold:"is the man!" });

Then, on the data handler, the result is evaluated and returned:

this.OnLoad(
	eval( responseText )
	);

This way, you can mostly mirror the functionality of the server-side objects but with real Javascript objects. This is not required though. When you send the request, you can send a boolean that determines whether or not the responseText should be evaluated. As far as creating the Javascript strings on the server side, that's for a later post.

// This is the base class for the ajax HttpConnection object.
function HttpConnectionClass( strUrl ){
	// The url for the connection.
	this.Url = strUrl;

	// The parameters to send (the data).
	this.Parameters = new Object();

	// The connection object.
	this.Connection = this.CreateConnectionObject();

	// This handles the data load (completion) function.
	this.OnLoad = null;

	// Return This pointer.
	return( this );
}


// This builds the connection object.
HttpConnectionClass.prototype.CreateConnectionObject = function(){

	// Try to create the generic connection.
	try {

		this.Connection = new XMLHttpRequest();

	} catch ( errTryMicrosoft ){

		// Try the MS xml object.
		try {

			this.Connection = new ActiveXObject( "Msxml2.XMLHTTP" );

		} catch ( errTryOtherMicrosoft ){

			// Try secondary Microsoft method.
			try {

				this.Connection = new ActiveXObject( "Microsoft.XMLHTTP" );

			} catch ( errFailed ){

				// None of the connection objects were created. Be sure
				// to se the connection object back to null for later
				// reference.
				this.Connection = null;

			}
		}
	}


	// Check to see if the object is null. If so, alert the user that
	// it will not work.
	if (!this.Connection){
		alert( "Your browser cannot create XmlHttpRequests.\nPlease use a browser that was developed in the 21st century." );
	}

	// Return the connection object.
	return( this.Connection );
}


// This handles changes in the state of communication. Javascript is an
// interpreted language and therefore can be created and evaluated on
// the fly. Here, you can pass in a flag to tell the load handler to try
// and return an evaluation of the result or to just return the raw data.
// This system default to evaluation, however if the evaluation ever
// fails, the raw data is returned.
HttpConnectionClass.prototype.ReadyStateChangeHandler = function( blnEvaluateResponse ){
	var strResponseText = null;

	// Check to see if we are finished.
	if (this.Connection.readyState == 4){

		// Trim the response text.
		strResponseText = this.Connection.responseText.replace( new RegExp( "^[\\s]+|[\\s]+$", "g"), "" );

		// Check to see if we need to evaluate the response. If not,
		// then just send the the raw data.
		if ( blnEvaluateResponse ){

			// We are going to try and evaluate the returned data since
			// we are expecting some sort of javascript object. No matter
			// what happens, we will be sending the connection object as
			// a second argument so that the handler can check status
			// and raw data.
			try {

				// We are going to try to evaluate the returned data to see if
				// it is a javascript data object.
				this.OnLoad(
					eval( strResponseText ),
					this.Connection
					);

			} catch ( errBadDataEvaluation ){

				// The data was not meant to be evaluated, so try to
				// return the raw string.
				this.OnLoad(
					strResponseText,
					this.Connection
					);

			}

		} else {

			// The data was not meant to be evaluated, so return the
			// raw string.
			this.OnLoad(
				strResponseText,
				this.Connection
				);

		}

	}

	// Return out.
	return;
}


// Send the data through the connection. When you send the data, you
// have the option to tell the load handler to try and evaluate the
// return data. By default the load handler will try to evaluate the
// result. You can also send a flag as to whether you want to clear
// the parameters after the data send. By default the parameters will
// be cleared post-send.
HttpConnectionClass.prototype.Send = function( blnEvaluateResponse, blnClearParameters ){
	var objSelf = this;
	var strData = "";
	var strKey = null;
	var blnEvaluateResponse = (blnEvaluateResponse == null) ? true : false ;
	var blnClearParameters = (blnClearParameters == null) ? true : false ;

	// NOTE: objSelf
	// We set of a seemingly add variable (objSelf) here to point to
	// (this). We do this for variable-binding. We are going to create
	// a function that will be called after this function is called.
	// In that function, we will need to refer to variables in the THIS
	// scope. However, "this" is a scope that will mean something
	// completely different in the function to be called at a later date.
	// To get around this, we create the variable objSelf, which we can
	// then use to create local variable bindings to this function even
	// when the variables are referenced in the future function.

	// Check to make sure we have a connection.
	if (this.Connection){

		// Open the connection.
		this.Connection.open(
			"POST",      // Method of data delivery.
			this.Url,      // The Url we are posting to.
			true      // Perform this async.
			);

		// Set the ready state handler only if we have a connection
		// and Load Handler. If we do not have a load handler, then
		// no need to check the ready state.
		if (this.OnLoad){
			this.Connection.onreadystatechange = function(){ objSelf.ReadyStateChangeHandler( blnEvaluateResponse ); };
		}

		// Set the content type to be a form post. This is needed since
		// we are sending the data via a form post.
		this.Connection.setRequestHeader(
			"content-type",
			"application/x-www-form-urlencoded"
			);

		// Create the data string. Loop over all the value parameters
		// that were set.
		for (strKey in this.Parameters){

			// Add the name/value pair. Escape values.
			strData = (strData + strKey + "=" + escape( this.Parameters[ strKey ] ) + "&");

		}

		// Strip any extra "&" that were appended to the string and
		// send the data.
		this.Connection.send(
			strData.replace( new RegExp( "&$", "g" ), "" )
			);

	}

	// Check to see if we should clear the parameters post-send.
	if ( blnClearParameters ){
		this.Parameters = new Object();
	}

	// Return out.
	return;
}


// Sets the url that the data is sent to.
HttpConnectionClass.prototype.SetUrl = function( strUrl ){
	this.Url = strUrl;

	// Return out.
	return;
}


// This sets a data in key-value pairs.
HttpConnectionClass.prototype.SetValue = function( strName, strValue ){
	this.Parameters[ strName ] = strValue;

	// Return out.
	return;
}

And to use it, you would include the Javascript file and write:

// Create the connection object.
var objRequest = new HttpConnectionClass( "ajax.cfm" );

// Set a variable.
objRequest.SetValue( "ben_nadel", "is wicked cool" );

// Set the data handler.
objRequest.OnLoad = fnDataHandler;

// Send the data.
objRequest.Send();

Want to use code from this post? Check out the license.

Reader Comments

2 Comments

"// Check to see if the object is null. If so, alert the user that
// it will not work.
if (!this.Connection){
alert( "Your browser cannot create XmlHttpRequests.\nPlease use a browser that was developed in the 21st century." );
}"

This had me laughing for quite a few minutes. Im thinking the world shold implement it...

34 Comments

Hi Ben

A while back I took your advice and ordered the "Headrush Ajax" book.

I'm pleased to say I've finally gotten around to starting it and am loving it so far.

Just a quick question:

When I open a request with this URL, the result I get is fine (bar a bit of whitespace which I trim) :
var url = "http://localhost/myajaxexamples/ajaxfunctions.cfm?method=GetBoardsSold";

But, if I try calling the remote CFC directly, I get the result back as a WDDX packet:
var url = "http://localhost/myajaxexamples/ajaxfunctions.cfc?method=GetBoardsSold

Is there a way to get a result without the XML? I'm sure you've probably encountered this already which is why I ask.

Thanks and thanks for the post on this book. :)

15,674 Comments

@Paolo,

In ColdFusion 8, you can specify a ReturnFormat either in the CFFunction tag or in the URL call. Example:

http://localhost/myajaxexamples/ajaxfunctions.cfc?method=GetBoardsSold&returnformat=json

I believe the valid types are wddx, plain, and json.

If you are not on ColdFusion 8, I think WDDX and XML (custom format) are the only options. If this is a problem, you might want to create a proxy CFM page that returns your desired format but internally calls your CFC (locally).

34 Comments

Thanks for the quick response mate.

Currently, I do use a proxy cfm page.

Good news though is that I am on CF8 as well - and will most certianly make use of that JSON returnType!

Thanks a mill!! :)

15,674 Comments

@Paolo,

If you are on ColdFusion 8, then I would recommend using ReturnFormat as JSON (can be defaulted in CFFunction tag ReturnType="json" attribute so you don't need it in the URL) AND using jQuery to make your AJAX calls.

jQuery is badass if you have not looked into it yet and will automatically convert your JSON return data into native Javascript objects.

34 Comments

@Ben

Thanks - I will do so.

I have seen all your posts on jQuery but before I dabble in any prebuilt javascript classes, I want to first understand the basics and groundwork of AJAX.

I'm gonna mission through the book and then start looking into the prebuilt classes like jQuery and mootools.

The idea is to be able to do the same sorta things that Facebook does. I think they're pretty cool to be honest :)

Ta

15,674 Comments

@Paolo,

I'm down with that program; I think understanding the plumbing is essential to fully understanding the technology as a whole. Feel free to shoot any questions over this way if you get stuck.

34 Comments

Hi Ben - this may be a newbie question.

I have two CFC's being called remotely. Both have a returnType="String" and returnFormat="JSON".

The one returns a numericm value and the other a String.

When I receive the numeric result, it is fine and not enclosed in quotation marks, e.g 123.45

But when I get the String result back, it's enclosed in quotation marks, e.g. "My String Result"

Is there some sort of parsing going on here that CF does when the returnFormat is JSON that is causing this? How do people get around this - by stripping the quotation marks or am I missing something else?

Thanks
Paolo

15,674 Comments

@Paolo,

In JSON format, strings are returned in quotes. JSON is special encoding that using "implicit data type creation". The data that comes back from the web service needs to be "executed" in order to become the native Javascript objects.

Assuming you have an AJAX return value called "AjaxReturnData," you could get at the variables as such:

var returnData = eval( "(" + AjaxReturnData + ")" );

Now, "returnData" contains your actual native Javascript variable values returned from the web service.

You won't have to worry about the difference between numbers or strings or arrays or structs - this will work on all of them and store the translated value in the returnData variable.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel