Custom AJAX Content Handling with the Dojo Toolkit

By  on  

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!

Recent Features

  • By
    JavaScript Promise API

    While synchronous code is easier to follow and debug, async is generally better for performance and flexibility. Why "hold up the show" when you can trigger numerous requests at once and then handle them when each is ready?  Promises are becoming a big part of the JavaScript world...

  • By
    7 Essential JavaScript Functions

    I remember the early days of JavaScript where you needed a simple function for just about everything because the browser vendors implemented features differently, and not just edge features, basic features, like addEventListener and attachEvent.  Times have changed but there are still a few functions each developer should...

Incredible Demos

  • By
    Cross Browser CSS Box Shadows

    Box shadows have been used on the web for quite a while, but they weren't created with CSS -- we needed to utilize some Photoshop game to create them.  For someone with no design talent, a.k.a me, the need to use Photoshop sucked.  Just because we...

  • By
    Save Web Form Content Using Control + S

    We've all used word processing applications like Microsoft Word and if there's one thing they've taught you it's that you need to save every few seconds in anticipation of the inevitable crash. WordPress has mimicked this functionality within their WYSIWYG editor and I use it...

Discussion

  1. Nice article David !

  2. Anon

    Is there a way to override the errorHandling function in dojo. Similar to what we have $.ajax.prefilter ??

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!