Encapsulating Ajax Calls in an XHTML Friendly Way

In the comments for my post from the other day titled “Encapsulating Ajax XMLHTTPRequest Calls within JavaScript classes“, Kevin F noted that using document.write was not allowed within an XHTML document.

Now, this isn’t directly related to Encapsulating Ajax calls, as you could have a non-visual class that doesn’t need to write visual content out to the browser. However, I wanted to post an update to the original example that shows how to do this that would be valid within an XHTML document.

First, why is document.write not allowed within XHTML documents? Well, once you think about it is actually pretty easy to understand. Basically, XHTML is XML, and as such, if you are dynamically writing elements into the container, and by-passing the parser, then there is really no easy way for the XML engine to be sure that you are going to be writing valid XML. Because of this, you have to use the DOM APIs to manipulate / insert nodes into an XHTML document. This makes it more likely that valid XML is going to be created, and also ensures that the DOM / XML engine will know about the changes.The W3C has a good FAQ item that discusses this issue.In the previous example, we were dynamically using document.write from within the JavaScript class to write out the DIV / container that the content would be added to. The advantage to this is that it makes the class completely self contained, but the (big) disadvantage is that it won’t work when writing to an XHTML document.The work around is quite simple though, and just requires passing a reference to the XHTML node that will be written to into the class. This class will then use this node to dynamically generate its content.Here is the modified example. First, the HTML.[code]index

var ml = new MessageLoader(“container”);ml.load();

[/code]Two minor but significant changes here. First, we wrap the Script block that contains our class inside of a DIV. This is the DIV that the class will dynamically write its content to.Next, we pass the ID of the DIV into the constructor of the JavaScript class. This is how we tell the class where it can writes its info.Couple of notes. First, it is not necessary that the Script tags containing the class be nested within the DIV tag. I am doing it to make the code a little more organized and make it clear that these two elements are related.Second, you could either pass a String with the name of the ID for the DIV, or a reference to the actual div. I choose a string because it is a little easier for the user (requires one less line of code), and I wanted to abstract away some of that complexity.Now, lets look at the (slightly) modified JavaScript class.[code]/*** @fileoverview Simple example that shows how to encapsulate* XMLHTTPRequestCalls** @author Mike Chambers (mesh@adobe.com)*//*** Constructor for the class.** @param {String} dataURL The path to the data that the class* will load (OPTIONAL)** @constructor*/function MessageLoader(outerContainerID, dataURL){this._outerContainer = document.getElementById(outerContainerID);if(dataURL != undefined){this._dataURL = dataURL;}this._writeContainer();}//where to load the data fromMessageLoader.prototype._dataURL = “data.txt”;//var to hold an instance of the XMLHTTPRequest objectMessageLoader.prototype._request = undefined;MessageLoader.prototype._outerContainer = undefined;//ID for the html div we will create to display the dataMessageLoader.prototype._containerID = “innerContainer”;//name of the css class for the HTML containerMessageLoader.prototype._containerClass = “ml_container”;/**************** Public APIs **********************//*** Tells the class to load its data and render the results.*/MessageLoader.prototype.load = function(){//get a new XMLHTTPRequest and store it in an instance var.this._request = this._getXMLHTTPRequest();//set the var so we can scope the callbackvar _this = this;//callback will be an anonymous function that calls back into our class//this allows the call back in which we handle the response (_onData())// to have the correct scope.this._request.onreadystatechange = function(){_this._onData()};this._request.open(“GET”, this._generateDataUrl(), true);this._request.send(null);}/***************Private Rendering APIs ********************///writes the top level div for the class / widgetMessageLoader.prototype._writeContainer = function(){//styles should be in external CSS//document.write(“

“);var innerContainer = document.createElement(“div”);innerContainer.setAttribute(“id”, this._containerID);innerContainer.setAttribute(“class”, this._containerClass);//need this for IEinnerContainer.setAttribute(“className”, this._containerClass);this._outerContainer.appendChild(innerContainer);}//renders the entire widgetMessageLoader.prototype._render = function(title){var content = document.getElementById(this._containerID);content.appendChild(document.createTextNode(title));}/***************Private Data Loading Handlers*******************///return the URL from which the data will be loadedMessageLoader.prototype._generateDataUrl = function(){return this._dataURL;}//callback for when the data is loaded from the serverMessageLoader.prototype._onData = function(){if(this._request.readyState == 4){if(this._request.status == “200”){this._render(this._request.responseText);//if the onDraw callback has been defined//call it to let the listener know//that we are done creating the listif(this.onDraw != undefined){this.onDraw();}}else{//check if an error callback handler has been definedif(this.onError != undefined){//pass an object to the callback handler with info//about the errorthis.onError({status:this_request.status,statusText:this._request.statusText});}}//clean updelete this._request;}}/***************Private Data Util Functions ********************///returns an XMLHTTPRequest instance (based on browser)MessageLoader.prototype._getXMLHTTPRequest = function(){var xmlHttp;try{xmlHttp = new ActiveXObject(“Msxml2.XMLHttp”);}catch(e){try{xmlHttp = new ActiveXObject(“Microsoft.XMLHttp”);}catch(e2){}}if(xmlHttp == undefined && (typeof XMLHttpRequest != ‘undefined’)){xmlHttp = new XMLHttpRequest();}return xmlHttp;}[/code]There were really only two methods that have changes.First, the constructor:[code]function MessageLoader(outerContainerID, dataURL){this._outerContainer = document.getElementById(outerContainerID);if(dataURL != undefined){this._dataURL = dataURL;}this._writeContainer();}[/code]It has been modified to take the ID name of the DIV it will write to as its first argument. We then use that ID to get a reference to the actual div using document.getElementById.Next, the _writeContainer method has also been changed.[code]MessageLoader.prototype._writeContainer = function(){var innerContainer = document.createElement(“div”);innerContainer.setAttribute(“id”, this._containerID);innerContainer.setAttribute(“class”, this._containerClass);//need this for IEinnerContainer.setAttribute(“className”, this._containerClass);this._outerContainer.appendChild(innerContainer);}[/code]Instead of using document.write to create the DIV that we will write to, we now use the DOM APIs to create the DIV, appending them to the DIV passed into the class.Notice that I am actually appending an innerContainer div onto the one passed in. This is what the content will be built within, verses the container passed in. This is done so that we can completely encapsulate the class / code, and have complete control over the styling (i.e. we don’t have to have the outerContainer DIV implement specific CSS classes.Those are the only two changes that we need to make. The code / class works exactly the same, only now it is compatible with XHTML documents.Post any suggestions / corrections / improvements in the comments. In particular, does anyone know of a way to get access to the outer DIV in a DOM friendly way from within the JavaScript class without having the user pass in a reference to it?

5 Responses to Encapsulating Ajax Calls in an XHTML Friendly Way

  1. Geoff says:

    Is anyone actually writing *real* xhtml pages (that is, with the ‘correct’ mime types and all that?I have yet to see anyone but xhtml hobbyists (for lack of a better term) using it. If you are using xhtml 1.0 sent as text/html mime type it’s perfectly fine (and probably faster, and will probably save you some kbs) to use innerHTML when writing responses.

  2. John says:

    Come come Mike, what if I don’t want to render html on response, a MessageLoader should have nothing to do with HTML. If you split it up properly you could even have a HTMLRenderer and a XHTMLRenderer using either innerHTML or using DOM, you could even go further and automatically use one or the other, but thats a bit out of the scope of this I feel. Also a..var o = MessageLoader.prototype;o.load = function(){};etc, etc wouldn’t go amiss!Soon you’ll be realising you can use your AS classes in JS and you JS classes in AS and have one single ECMA class lib, oh you’ve probably changed all your AS classes to AS2, shame 😉

  3. mike chambers says:

    >Come come Mike, what if I don’t want to render html on responseWell, the same thing applies regardless of where your XMLHttpRequest object lives.The point is, that you need to pass in a reference to the dom node that you will be appending content to.mike chambersmesh@adobe.com

  4. Ward Ruth says:

    Mike:Thanks for the excellent approach. I’m new to Ajax etc., having been a pretty heads-down flash coder, but I’m starting to branch out a little. I used your class with a little modification for my portfolio site. It worked really well.I do think the idea of separating the responsibility for loading the XMLHTTPRequest object and rendering it are most flexibly and reusably handled by handing off the rendering to a dedicated Renderer object. This is what I did at least, and seemed to work well.For instance, in the constructor of my WRMessageLoader I have[code]function WRMessageLoader( pDataURL, pRenderer, pErrorHandler ) {this._dataURL = pDataURL;this._renderer = pRenderer;if( pErrorHandler != undefined ) {this._errorHandler = pErrorHandler;}}[/code]Then in my _onData method I do this (I’ve used the convenience variable p for WRMessageLoader.prototype):[code]p._onData = function() {if( this._request.readyState == 4 ) {if( this._request.status == “200” ) {this._renderer.render( this._request );}else {this._handleError({ loaderMessage: “Load Failed.”,status: this._request.status,statusText: this._request.statusText } );}delete this._request;}};[/code]So my renderer just implements a render() method that tackes the XMLHTTPRequest object as a parameter, and it can then use the responseText or responseXML on that depending on how it wants to render.Here’s what _handleError() does, btw:[code]p._handleError = function( pMessageObject ) {pMessageObject.source = this;if( this._errorHandler != undefined ) {if( typeof this._errorHandler == “function” ) {this._errorHandler( pMessageObject );}else {this._errorHandler.onError( pMessageObject );}}};[/code]I also potentially call that from load():[code]p.load = function() {this._request = this._getXMLHTTPRequest();if( !this._request ) {this._handleError( { loaderMessage: “Could not create HTTPRequest object.” } );return false;}var ml = this;this._request.onreadystatechange = function() {ml._onData();};this._request.open( “GET”, this._dataURL, true );this._request.send(“”);return true;};[/code]Thanks again Mike — this helped quite a lot!

  5. Mohit says:

    HiThanks for the great approach.I was wondering:Can we use this approach to pass a HandlerFunction to the class and have this class call the HandlerFunction after it recives the response from the server (maybe returning an object parsed from the responseText).Also what is the overhead (or performance bottleneck) of having multiple XMLhttp objects.ThanksMohit