Custom AJAX Content Handling with the Dojo Toolkit
If you were to ask me for the top five words that should describe any JavaScript framework, one of them would be flexible. The Dojo Toolkit is ultra-flexible in just about every way, using customizable classes and dojo
-namespaced objects to to allow for maximal flexibility. One of those dojo
-namespaced objects, dojo.contentHandlers
, is an object containing key->value pairs for handling the result of AJAX requests. Let me show you how to use these content handlers and how you can create your own!
dojo.xhr
and handleAs
Making AJAX requests is done with Dojo's dojo.xhr
methods. Sending a basic GET request would look like:
dojo.xhrGet({ url: "/ajax.php", load: function(result) { // Do something with the result here } });
The request above assumes that the response should be handled as plain text, as you would expect. Dojo's dojo.xhr
methods all accept an object with properties for handling the request, and one property you can add is handleAs
. The handleAs
property should be a string representing the type of parsing that should be done to the result before its passed to the load
method or deferred callback. Values for the handleAs property could be json, javascript, xml, or other variants of json. If I want my request to be handled as JSON, I'd code:
dojo.xhrGet({ url: "/ajax.php", handleAs: "json", load: function(result) { // result is a JS object // Do something with the result here } });
The resulting object provided to the load handler is text parsed into JavaScript object. Likewise, if I want the result to be handled as XML, I'd code:
dojo.xhrGet({ url: "/ajax.php", handleAs: "xml", load: function(result) { // result is a XMLDocument object // Do something with the result here } });
The load callback is provided a XMLDocument
object. One simple parameter changes the way the request response is parsed. So how is this possible, and how can you create custom handleAs
methods? Simple!
dojo.contentHandlers
The dojo.contentHandlers
object acts as dictionary for ajax request parsing. The handleAs parameter you supply maps to the key within dojo.contentHandlers
. The dojo.contentHandlers
object comes with the following content handlers: javascript, json, json-comment-filtered, json-comment-optional, text, and xml. Here's a snippet containing those "parsers":
var handlers = dojo._contentHandlers = dojo.contentHandlers = { text: function(xhr){ // summary: A contentHandler which simply returns the plaintext response data return xhr.responseText; }, json: function(xhr){ // summary: A contentHandler which returns a JavaScript object created from the response data return _d.fromJson(xhr.responseText || null); }, "json-comment-filtered": function(xhr){ if(!dojo.config.useCommentedJson){ console.warn("Consider using the standard mimetype:application/json." + " json-commenting can introduce security issues. To" + " decrease the chances of hijacking, use the standard the 'json' handler and" + " prefix your json with: {}&&\n" + "Use djConfig.useCommentedJson=true to turn off this message."); } var value = xhr.responseText; var cStartIdx = value.indexOf("\/*"); var cEndIdx = value.lastIndexOf("*\/"); if(cStartIdx == -1 || cEndIdx == -1){ throw new Error("JSON was not comment filtered"); } return _d.fromJson(value.substring(cStartIdx+2, cEndIdx)); }, javascript: function(xhr){ // summary: A contentHandler which evaluates the response data, expecting it to be valid JavaScript // FIXME: try Moz and IE specific eval variants? return _d.eval(xhr.responseText); }, xml: function(xhr){ // summary: A contentHandler returning an XML Document parsed from the response data var result = xhr.responseXML; //>>excludeStart("webkitMobile", kwArgs.webkitMobile); if(_d.isIE && (!result || !result.documentElement)){ //WARNING: this branch used by the xml handling in dojo.io.iframe, //so be sure to test dojo.io.iframe if making changes below. var ms = function(n){ return "MSXML" + n + ".DOMDocument"; } var dp = ["Microsoft.XMLDOM", ms(6), ms(4), ms(3), ms(2)]; _d.some(dp, function(p){ try{ var dom = new ActiveXObject(p); dom.async = false; dom.loadXML(xhr.responseText); result = dom; }catch(e){ return false; } return true; }); } //>>excludeEnd("webkitMobile"); return result; // DOMDocument }, "json-comment-optional": function(xhr){ // summary: A contentHandler which checks the presence of comment-filtered JSON and // alternates between the `json` and `json-comment-filtered` contentHandlers. if(xhr.responseText && /^[^{\[]*\/\*/.test(xhr.responseText)){ return handlers["json-comment-filtered"](xhr); }else{ return handlers["json"](xhr); } } };
What if we want to add our own content handler though? All you need to do is add the key=>parser to the dojo.contentHandlers
object!
// CSV parsing found at: http://stackoverflow.com/questions/1293147/javascript-code-to-parse-csv-data dojo.contentHandlers.csv = function(xhr) { // Set the data var responseText = xhr.responseText; var delimiter = ","; // Create a regular expression to parse the CSV values. var objPattern = new RegExp( ( // Delimiters. "(\\" + delimiter + "|\\r?\\n|\\r|^)" + // Quoted fields. "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + // Standard fields. "([^\"\\" + delimiter + "\\r\\n]*))" ), "gi"); // Create an array to hold our data. Give the array // a default empty first row. var arrData = [[]]; // Create an array to hold our individual pattern // matching groups. var arrMatches = null; // Keep looping over the regular expression matches // until we can no longer find a match. while (arrMatches = objPattern.exec(responseText)){ // Get the delimiter that was found. var strMatchedDelimiter = arrMatches[1]; // Check to see if the given delimiter has a length // (is not the start of string) and if it matches // field delimiter. If id does not, then we know // that this delimiter is a row delimiter. if (strMatchedDelimiter.length && (strMatchedDelimiter != delimiter)){ // Since we have reached a new row of data, // add an empty row to our data array. arrData.push([]); } // Now that we have our delimiter out of the way, // let's check to see which kind of value we // captured (quoted or unquoted). if (arrMatches[2]){ // We found a quoted value. When we capture // this value, unescape any double quotes. var strMatchedValue = arrMatches[2].replace( new RegExp("\"\"", "g"), "\"" ); } else { // We found a non-quoted value. var strMatchedValue = arrMatches[3]; } // Now that we have our value string, let's add // it to the data array. arrData[arrData.length - 1].push(strMatchedValue); } // Return the parsed data. return(arrData); }
The code snippet above allows you to have your XHR request's result be parsed as CSV content; the result becomes a JavaScript object representing the CSV data. Here's how you'd use it:
dojo.xhrGet({ url: "/ajax.php", handleAs: "csv", load: function(result) { // result is a JS object // Do something with the result here } });
One key to flexibility within JavaScript framework is "dictionaries" or "property bags", allowing for adding, removing, and modifying of existing properties. Thanks to Dojo's use of dojo.contentHandlers
and dojo.xhr
's handleAs
property, you can handle the result of your AJAX requests before they are passed to a callback!
Nice article David !
Is there a way to override the
errorHandling
function in dojo. Similar to what we have$.ajax.prefilter
??