I'M IN UR AJAX,

PROCESSIN' UR DATA!

Julian Aubourg
www.creative-area.net
jaubourg.net
@jaubourg

QUESTION

What is ajax exactly?

"Dude, you actually rewrote the thing and need to ask?"

Let's go back two years

"Err... OK, so it's like xhr but not xhr but still is somehow, you know..."

Err...

"Nevermind! Look, it's got cool callbacks and options and..."

But what does it *do*? What is it?

"Man, it's complicated, but you should use $.get and $.post and $.getJSON and..."

What ajax actually *is*?


a means to request data in a desired format:
request data: Transports
desired format: Converters dataFilter

It also has a sh... lot of options and helpers, but it's never enough:
extensibility is mandatory: Prefilters duck punching
extensions should apply to helpers
extensions shouldn't apply to helpers
Man, aren't the two last points like... conflicting or something?

Prefilters

Converters

Transports

wait... isn't it all backward now?

How and when to combine them and rule them all

yeah, yeah, and, in the darkness, bind them... then go grab something to eat, PLZ!

Let's assume you know:

that ajax now returns an xhr-like thingy

that it's called the "jqXHR object"

how do you even pronounce that?

that this object is a Promise:

then, done, fail, isResolved, isRejected, always1.6, pipe1.6
success and error (aliases for done and fail respectively)
complete (for completeness, so to speak)
Huuuungryyyy!

PREFILTERS

Scripts cached by default


	$.ajaxPrefilter( "script", function( options ) {
	 	if ( options.cache === undefined ) {
	 		options.cache = true;
	 	}
	});

Prefilters

A prefilter is a function that is called for each request.

It is called very early on, BEFORE the request is issued.

It can be used to take specific actions:

before the request is issued (obviously)
once or after the request returns (I Promise U)

Anatomy of a Prefilter (1/4)


$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
    // Your code here
});

$.ajaxPrefilter( "json", function( options, originalOptions, jqXHR ) {
    // Your code for json requests here
});

Anatomy of a Prefilter (2/4)


$.ajaxPrefilter( dataTypes, prefilterFN );

dataTypes (optional): space-separated list of dataTypes - "json script ..."

prefilterFN: "prefiltering" callback function

Anatomy of a Prefilter (3/4)


function( options, originalOptions, jqXHR ) {}

options is the extended options map (with ajaxSettings)

originalOptions are the options as given to ajax

jqXHR is the jqXHR object (who would have thought?)

Anatomy of a Prefilter (4/4)


$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
    // Your code here
});

$.ajaxPrefilter( "json", function( options, originalOptions, jqXHR ) {
    // Your code for json requests here
});

Remember this guy?


	$.ajaxPrefilter( "script", function( options ) {
	 	if ( options.cache === undefined ) {
	 		options.cache = true;
	 	}
	});

A simple logging Prefilter


	$.ajaxPrefilter(function( options, _, jqXHR ) {
	 	if ( options.log ) {
	 		output( "Starting request " + options.url + " " +options.data );
	 		jqXHR.success(function( data ) {
	 			output( "Success " + data );
	 		}).error(function( jqXHR, statusText, msg ) {
	 			output( "Error " + msg );
	 		});
	 	}
	});
	$.ajax( "srv/echo", {
		data: { random: 1, delay: 3 },
		log: true
	});
	
	$.ajax( "srv/echo", {
		data: { random: 1, delay: 10 },
		log: true
	});

Parse error messages


	$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
	  if ( options.parseError ) {
	    $.Deferred(function( defer ) {
	      jqXHR.done( defer.resolve )
	      	.fail(function( jqXHR, statusText, errorMsg ) {
		        var parsed = $.parseJSON( jqXHR.responseText );
		        defer.rejectWith( this, [ jqXHR, statusText, parsed ] );
		      });
	    }).promise( jqXHR );
	    jqXHR.success = jqXHR.done;
	    jqXHR.error = jqXHR.fail;
	  }
	});
	$.ajax( "srv/echo", {
		parseError: true,
		data: { error: 1, delay: 1, echo: '{ "field": "value" }' }
	}).fail(function( jqXHR, statusText, parsed ) {
		output( parsed.field );
	});

CONVERTERS

Override text to json converter


	$.ajaxSetup({
		converters: {
			// Install microsoft "enhanced" converter
			"text json": function( msg ) {
				var obj =
					Sys.Serialization.
					JavaScriptSerializer.deserialize( msg );
				return obj.hasOwnProperty( "d" ) ? obj.d : obj;
			}
		}
	});
	

Converters

A converter is a function called by ajax when a response
has to be converted from one dataType to another

So a Converter is... a Converter, GENIUS! When do we eat?

For instance:

text to JSON -> $.parseJSON
text to XML -> $.parseXML

You can:

override an existing Converter
create a new Converter

Default Converters

	// List of data converters
	// 1) key format is "source_type destination_type"
	//    (single space in-between)
	// 2) the catchall symbol "*" can be used for source_type
	converters: {
		// Convert anything to text
		"* text": window.String,

		// Text to html (true = no transformation)
		"text html": true,

		// Evaluate text as a json expression
		"text json": jQuery.parseJSON,

		// Parse text as xml
		"text xml": jQuery.parseXML
		
		// Execute a script
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}

Install a Converter


	$.ajaxSettings({
		converters: {
			"type1 type2": function( valueType1 ) {
				// Your logic
				return valueType2;
			}
		}
	});
	

	$.ajax( url, {
		converters: {
			"type1 type2": function( valueType1 ) {
				// Your logic
				return valueType2;
			}
		}
	});
	

Data validation


	$.ajax( "srv/echo", {
		dataType: "json",
		data: { delay: 3, random: 1 },
		converters: {
			"text json": function( _ ) {
				throw "DO NOT WANT";
			}
		}
	}).error(function( jqXHR, statusText, error ) {
		output( statusText, error );
	});
	

Override text to json converter


	$.ajaxSetup({
		converters: {
			// Install microsoft "enhanced" converter
			"text json": function( msg ) {
				var obj =
					Sys.Serialization.
					JavaScriptSerializer.deserialize( msg );
				return obj.hasOwnProperty( "d" ) ? obj.d : obj;
			}
		}
	});
	

Doing it right

	$.ajaxSetup({
		converters: {
			// Install microsoft "enhanced" converter
			"text json": function( msg ) {
				return Sys.Serialization.JavaScriptSerializer.deserialize( msg );
			},
			// Install new dataType
			"json jsond": function( json ) {
				return json && json.hasOwnProperty( "d" ) ? json.d : json;
			}
		}
	});

	$.ajax( url, {
		dataType: "jsond"
	});

	$.ajax( url, {
		dataType: "jsonp jsond"
	});

C-R-A-Z-Y


	$.ajaxPrefilter(function( options ) {
	    // If custom msjson option is true
	    if ( options.msService ) {
	        jQuery.ajaxSetup( options, {
	            converters: {
	                // Install microsoft based converter
	                "text json": function( msg ) {
	                     var obj =
	                     	Sys.Serialization.
	                     	JavaScriptSerializer.deserialize( msg );
	                     return obj.hasOwnProperty( "d" ) ? obj.d : obj;
	                }
	            },
	            // Serialize data using microsoft serializer
	            data:
	            	Sys.Serialization.
	            	JavaScriptSerializer.serialize( options.data ),
	            // Force type and contentType
	            type: "POST",
	            contentType: "application/json; charset=utf-8"
	        });
	    }
	});

Use this C-R-A-Z-Y Prefilter


	$.ajax( url, {
		msService: true, // Awesomesauce!
		data: {
			"hello": "world"
		}
	});

	$.ajaxSetup({ msService: true });
	
	$.post( url, { data: { "hello": "world" } });

TRANSPORTS

Transports

A Transport is a low-level Object

It implements the actual request/response cycle


	{
		send: function( requestHeaders, doneFN ) {
			// Initiate the request here
		},
		abort: function() {
			// Provide a mean to abort the request here
		}
	}

Anatomy of a Transport (1/4)


		send: function( requestHeaders, doneFN ) {}
	

requestHeaders - a map of headers

doneFN( status, statusText, responses, responseHeaders )

status code
status text
all potential responses:
			{
				text: rawText,
				xml: parsedText
			}
response headers as a single string

Anatomy of a Transport (2/4)


		abort()
	

OK, we got it man!

Anatomy of a Transport (3/4)

Each request will create a new Instance of a transport

The Transport Factory is attached using ajaxTransport:


	$.ajaxTransport(function( options, originalOptions, jqXHR ) {
		if ( /* I can handle the request */ ) {
			return {
				send: function(  requestHeaders, doneFN ) {
					// Initiate the request here
				},
				abort: function() {
					// Provide a mean to abort the request here
				}
			};
		}
	});

Anatomy of a Transport (4/4)


$.ajaxTransport( dataTypes, transportFactory );

dataTypes (optional): space-separated list of dataTypes - "json script ..."

transportFactory: function which serves as a factory


Wait... isn't it, like, exactly the same as ajaxPrefilter? Lazy speaker just copy/pasted! Oh the Horror!

Simple Transport example


	$.ajaxTransport( "img", function( options ) {
		if ( options.type === "GET" ) {
			var image;
			return {
				send: function( _, callback ) {
					image = new Image();
					image.onload = function() {
						image = image.onload = image.onerror = null;
						callback( 200, "OK", { img: this } ); 
					};
					image.onerror = function( abort ) {
						image = image.onload = image.onerror = null;
						callback( abort === "abort" ? 0 : 404,
							abort === "abort" ? "abort" : "Not Found" ); 
					};
					image.src = options.url; 
				},
				abort: function() {
					if ( image ) image.onerror( "abort" );
				}
			};
		}
	});

CONCLUSION

What to use and when

Data is not like I want it to: Converters

I need to do stuff prior or after every request: Prefilters

Need more tubes? Transports FTW

Micro-plugins approach

done properly, each transport, converter and prefilter
is independant from one another (orthogonality)

code is generally small (though transports can get technical)

build yourself a collection of ajax hooks

drop them into your app when needed

jaubourg