Let's Make a Framework: Ajax Improvements
by
at 2011-09-08 07:00:00
original http://feedproxy.google.com/~r/dailyjs/~3/WDr1bdF2WBM/framework-79
Let’s Make a Framework is an ongoing series about building a JavaScript framework from the ground up.
These articles are tagged with lmaf. The project we’re creating is called Turing. Documentation is available at 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='<a id="a"><b id="b">hey!</b></a>';
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>A</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.