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
    Facebook Open Graph META Tags

    It's no secret that Facebook has become a major traffic driver for all types of websites.  Nowadays even large corporations steer consumers toward their Facebook pages instead of the corporate websites directly.  And of course there are Facebook "Like" and "Recommend" widgets on every website.  One...

  • By
    Introducing MooTools Templated

    One major problem with creating UI components with the MooTools JavaScript framework is that there isn't a great way of allowing customization of template and ease of node creation. As of today, there are two ways of creating: new Element Madness The first way to create UI-driven...

Incredible Demos

  • By
    Page Visibility API

    One event that's always been lacking within the document is a signal for when the user is looking at a given tab, or another tab. When does the user switch off our site to look at something else? When do they come back?

  • By
    Retrieve Google Analytics Visits and PageViews with PHP

    Google Analytics is an outstanding website analytics tool that gives you way more information about your website than you probably need. Better to get more than you want than not enough, right? Anyways I check my website statistics more often than I should and...

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!