DailyJS

Let's Make a Framework: Ajax Improvements

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials frameworks lmaf ajax

Let's Make a Framework: Ajax Improvements

Posted by Alex R. Young on .
Featured

tutorials frameworks lmaf ajax

Let's Make a Framework: Ajax Improvements

Posted by Alex R. Young on .
*Let's Make a Framework* is an ongoing series about building a JavaScript framework from the ground up. These articles are tagged with [lmaf](http://dailyjs.com/tags.html#lmaf). The project we're creating is called [Turing](http://github.com/alexyoung/turing.js). Documentation is available at [turingjs.com](http://turingjs.com/).

I was impressed by the
Superagent HTTP library, so I decided to see what improvements I could make to
turing.net based on it. Along the way I found some IE
compatibility issues that I hadn't spotted before, and improved the
functional testing script.

Response and Requests

Both Express and Superagent pass abstracted
response and request objects back to callbacks. I found the need for
this arose when I noticed IE6 didn't like me adding properties to the
ActiveXObject it uses to support
XMLHttpRequest. To fix this I decided to take some
inspiration from these projects and provide an abstracted response
object to callbacks:

var response = {};

response.status = request.status;
response.responseText = request.responseText;
if (/json/.test(contentType)) {
  response.responseJSON = net.parseJSON(request.responseText);
} else if (/xml/.test(contentType)) {
  response.responseXML = net.parseXML(request.responseText);
}

if (successfulRequest(request)) {
  if (options.success) options.success(response, request);
  if (promise) promise.resolve(response, request);
} else {
  if (options.error) options.error(response, request);
  if (promise) promise.reject(response, request);
}

Now the callbacks get a more friendly response object, as well as the
original XMLHttpRequest request object. The JSON and XML
tests were also added here -- previously only JSON was parsed, but I
added support for XML as well.

Parsing XML

In Parsing and serializing
XML
on
MDN, the following fragment is suggested for parsing XML:

var theString='hey!';
var parser = new DOMParser();
var dom = parser.parseFromString(theString, "text/xml");
// print the name of the root element or error message
dump(dom.documentElement.nodeName == "parsererror" ? "error while parsing" : dom.documentElement.nodeName);

The resulting XML isn't parsed into a plain JavaScript
Object but a Document instead, which means
methods and properties like nodeName are available.

Microsoft's approach is again to use ActiveXObject:

// Instantiate a DOM object at run time.
var dom = new ActiveXObject("msxml2.DOMDocument.6.0");
dom.async = false;
dom.resolveExternals = false;
dom.loadXML("A");

This is from InstantiateDOM.js at
MSDN
.

I decided to define a parseXML method once based on browser
support:

  /**
    * Parses XML represented as a string.
    *
    * @param {String} string The original string
    * @returns {Object} A JavaScript object
    */
  if (window.DOMParser) {
    net.parseXML = function(text) {
      return new DOMParser().parseFromString(text, 'text/xml');
    };
  } else {
    net.parseXML = function(text) {
      var xml = new ActiveXObject('Microsoft.XMLDOM');
      xml.async = 'false';
      xml.loadXML(text);
      return xml;
    };
  }

To test this, I wrote the following:

'test xml parsing': function() {
  $t.post('/give-me-xml', {
    contentType: 'application/xml',
    success: function(r) {
      assert.equal('key', r.responseXML.documentElement.nodeName);
    }
  });
}

This runs through an Express app, in
test/functional/ajax.js.

Promises

As I mentioned, IE doesn't like modifying the
XMLHttpRequest object it provides through ActiveX. I was
setting a then property on request objects to support
promises which was only used because the XMLHttpRequest was being returned from the
network-related methods. To get around the IE issue, I decided just to
return an empty object with a then property, so this is
still possible:

$t.get('/get-test').then(
  function(r) { assert.equal('Sample text', r.responseText); },
  function(r) { assert.ok(false); }
);

I made the documentation slightly ambiguous about what the network
methods return because I suspect returning the current
turing object to allow other chaining might be more useful.
It currently reads @returns {Object} An object for further
chaining with promises
, which is what I intended it to do in the
first place.

Conclusion

After all that, I never really got to use any of TJ's ideas from
Superagent, other than abstracting response objects. The most important
thing to remember when creating cross-browser code is to be careful
about extending native objects. That's a good rule of thumb for
JavaScript in general, and I feel doubly embarrassed about doing it in
the first place because I've been preaching this for a while!

You can get this week's code in commit
93a5d75
.