Encapsulating Ajax XMLHTTPRequest Calls within JavaScript classes

The guys over at Fold posted an article on how to use multiple XMLHTTPRequest (XHR) calls within a single page. This is something that I have had to tackle in my own work, and though I would write up how I solve this issue.

This post describes a simple technique for encapsulating XMLHTTPRequest calls within JavaScript classes. In general, this makes code more maintainable, allows for the creation of easier to use APIs, and makes it much easier to create components / widgets that load data from the server.


Basically, the technique associates and encapsulates XMLHTTPRequest instances within individual JavaScript class instances.For this post, we will create a simple widget that uses XHR and Ajax techniques to dynamically load some data from the server, and then render it in the page. Nothing super exciting, but it provides enough to get an idea of how everything works.First, the examples contains the following files:

  • index.html : The main HTML page for the example.
  • scripts/MessageLoader.js : A JavaScript file that includes the JavaScript class used in the example.
  • styles/MessageLoader.css : A Simple Cascading StyleSheet that tries to make the end result look pretty.
  • data.txt : The data that will be loaded by the JavaScript class. It just contains raw text that will be displayed in the page.

You can download all of the files from here.First, lets look at what the final HTML file will look like:[code]indexvar ml = new MessageLoader();ml.load();[/code]Pretty simple. We link to the style sheet and MessageLoader.js file in the head tag. Then, we just have a simple script block in the body that will render the content.The main work goes on in the scripts/MessageLoader.js file.[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(dataURL){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;//ID for the html div we will create to display the dataMessageLoader.prototype._containerID = “container”;//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 CSSdocument.write(“

“);}//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]I have tried to comment the code to make it self documenting, but lets do a quick overview of what it going on.First, the class constructor does two main things:[code]function MessageLoader(dataURL){if(dataURL != undefined){this._dataURL = dataURL;}this._writeContainer();}[/code]It allows the developer to optionally specify the path to the data to be loaded, and it calls the this._writeContainer(); method. All this method does is write a small piece of content into the HTML page into which the dynamic data that will be loaded will be attached to.Everything is set in motion when the load() method is called.[code]/*** Tells the class to load its data and render the results.*/MessageLoader.prototype.load = function(){//get a new XMLHTTPRequest and store it in an isntance 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);}[/code]This is pretty standard XHR code, but there are two things worth calling out.First, we store the XHR object within a local variable named _request. This will allow it to be accessed later within the class instance.[code]this._request = this._getXMLHTTPRequest();[/code]Second, we create a local variable named _this that points to the class instance, and then create an anonymouse function to use as the onreadystatechange callback handler.[code] //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()};[/code]This is necessary because of how scoping works within JavaScript. Basically, any functions created contain references to variables within their scope. Thus, when we define the anonymouse function here:[code]this._request.onreadystatechange = function(){_this._onData()};[/code]The _this variable is copied into the function. This allows us to essentially proxy the data handler call to data handler that is called within the scope of our class instance.This makes it much, much easier to encapsulate the XHR call, as if we did not do this, then “this” within the callback would refer to the browser’s global window object, and NOT to our class instances.You can see this in the data handler which is called when the data is loaded from the server:[code]MessageLoader.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;}}[/code]This handler is called within the scope of our class instance. This allows us to get a reference to the XHR instance through this._request, and to call isntance methods directly.All this code does is check to make sure the data has completed loading. If it has, then it calls the _render method, which renders the content in the page. If an error occurs, then we throw an error event that the developer can listen for.Once we have retrieved the data we no longer need the XHR instance hanging around within our class instance, so we delete it.That is pretty much all there is to it. You could now instantiate multiple instances of the class, and each would contain its own XHR isntance. Furthermore, you can completely abstract away the XHR object, and provide a simple API specific to whatever you code / widget does.Now, in this example, you can only really crete one instance of the widget in a page, since we don’t do anything to create unique IDs for the dynamically generated DIVs / HTML content. However, that is easy enough to solve, and I will update this example to do this in the next day or two.Post any questions / corrections or suggestions in the comments.

23 Responses to Encapsulating Ajax XMLHTTPRequest Calls within JavaScript classes

  1. Kevin F says:

    Great article and examples Mike.Question: If the data.txt file contains html data, it doesn’t appear to display properly. Any ideas here?Cheers.Kevin

  2. document.write() is not an option when using XHTML, why not just include the container in the markup source?

  3. mike chambers says:

    >document.write() is not an option when using XHTMLHmm… that is interesting. Do you happen to have a link about why this is the case (since it is writing out valid xml / html).Regardless, if you cant use that, then just wrap the script within a DIV and pass a reference to the div into the class. You can then dynamically build off of that (i’ll try and post an updated example).thanks…mike chambersmesh@adobe.com

  4. mike chambers says:

    >Do you happen to have a link about why this is the case (since it is writing out valid xml / html).Found the answer myself:http://www.w3.org/MarkUp/2004/xhtml-faq#docwritemake sense. Thanks for pointing it out. I will update the example in another post shortly.mike chambersmesh@adobe.com

  5. When serving XHTML with the correct MIME type (application/xhtml+xml), document.write() is deprecated and does not work.The logic is that it would be all too easy to insert invalid markup into the document, thus breaking it. Same for writing to .innerHTML. (It may just be that true XML DOM doesn’t support those calls, for those reasons.)

  6. Mike Laurence says:

    Mike,Thanks so much for posting the article. I just spent an hour trying to figure out why my XHR object kept failing, but thankfully I stumbled across your code and fixed everything with a happy little _this reference. Thanks again! I’m so relieved that I think I’ll have to drink some beer to celebrate…Mike Laurence

  7. <body id=”parent”>…MessageLoader.prototype._parentID = ‘parent’;…var div = document.createElement(‘div’);div.setAttribute(‘id’, this._containerID);div.className = this._containerClass;document.getElementById(this._parentID).appendChild(div);

  8. Or even skip the id on the body element (unless you want to stuff the results elsewhere) and use:body = document.getElementsByTagName(‘body’)[0];body.appendChild(div);Personally, I avoid all of this if I can by just generating the container element in the document, but rather than using document.write(), create your markup with PHP or a similar server-side scripting language.Okay, I’ll shup up now. 😉

  9. Axel Wolf says:

    Thanks for mentioning Fold. We have released some screenshots and a screencast to demonstrate some of the key features of this new Rails-based portal.http://www.fold.comHave fun!

  10. Mark Mandel says:

    Whooo..I was just beating my head against a wall trying to work out how to get the onreadystatechange to call back a method in an object.Thanks alot Mike, much appreciated!

  11. Thank you for showing how to scope var _this = this properly. I had written about encapsulation a while ago, but did not use prototypes – used closures. Lately someone asked me to provide prototype implementation and I did after looking at scoping example in your code.Thanks.

  12. Slav Zat says:

    You can then dynamically build off of that (i’ll try and post an updated example).thanks…

  13. Tom Carden says:

    The var _this = this tip put an end to an hour of frustrated javascript hacking here. Thanks a bundle!

  14. h says:

    I am new to JS and was digging for sometime before finding this posting. Great information! Unfortunately, it doesn’t seem to work for me. I tried updating the code to include two DIV areas and load two files. no dice… They all seemed to be jammed into the first DIV.Also, tried a variant of my own code along with the technique of specifying the DIV id (and in my case, a src URL) Basically ended up with the following in a html page script:var ajax1 = new MyClass();var ajax2 = new MyClass();ajax1.load(….);ajax2.load(….);for some reason, only the last load completes. I put alerts inplace of calling the ‘onreadystatechange’ and it looks like the change only gets called once for all but the last of the load calls. Perhaps the httpRequest object of the first is being over written somehow?

  15. h says:

    bingo!I had”_this = this;”not”var _this = this;”could someone explain what happens in js with/without the “var”?thanks

  16. mike chambers says:

    >could someone explain what happens in js with/without the “var”?Yes. If you don’t put the var, then _this is scoped to the global scope. adding var makes it scoped to the function scope, which is included in the function enclosure.Hope that helps…mike chambersmesh@adobe.com

  17. james says:

    mike, thanks for ‘var _this = this;’ i am another developer who was running into this (_this?) issue.

  18. Vitaly says:

    Works fine in IE but I don’t receive response from the server when using Mozilla Firefox 1.5.05Could it be some Firefox settings? JS is enabled with all grants. I debugged with alerts, the xmlhttprequest is created with the last option:xmlHttp = new XMLHttpRequest();but still no luck.Even more weird:in Opera I get till onData but the this._request.responseText is “” – nothing, zilch, zero.

  19. jack says:

    I dont understand the reason for:this._request.onreadystatechange = function(){_this._onData()};I think I need some more basic JS knowledge. Could you point me at a resource the explains what is going on there.

  20. Jorge Luna says:

    Hi there, your article has proven very useful. And programming using OO is just a breeze.I have a question that i havent been able to find an answer to, and was hopping any of you could help:Im loading some content dynamically using AJAX into some page. Whithin the dynamic content I want to make another dynamic call and fill another element. The problem is, the element from the (first) dynamic content is not in the DOM tree! so I cant do a getElementById.Is there a way to do this that doesn’t involve parsing the conteng into the dom tree?Thank you!

  21. fernando trasvina says:

    thanks for the lil class im being able to correctly use the callback function now to my constructor

  22. Frank C says:

    PLEASE NOTE THERE IS A SMALL SYNTAX BUG!!!!!I confirmed this with Mike, the “onData” section has a small bug in the code, the line:this.onError({status:this_request.status,actually should bethis.onError({status:this._request.status,with a period after this.Thanks all,fc