From 5ec5de6953a22b33de46897c8e915eed300338e6 Mon Sep 17 00:00:00 2001
From: John Resig <jeresig@gmail.com>
Date: Thu, 17 Aug 2006 04:18:32 +0000
Subject: Added in a mess of AJAX fixes and optimisations, made possible by
 Taku Sano. On top of his patch I touched up some of the code, made some bug
 fixes, and added some additional functionality (including the ability to load
 and eval a remote script file.

---
 src/ajax/ajax.js | 154 ++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 112 insertions(+), 42 deletions(-)

diff --git a/src/ajax/ajax.js b/src/ajax/ajax.js
index be9c32006..d8494d3fb 100644
--- a/src/ajax/ajax.js
+++ b/src/ajax/ajax.js
@@ -5,12 +5,16 @@
 /**
  * Load HTML from a remote file and inject it into the DOM
  */
-jQuery.fn.load = function( url, params, callback ) {
-	// I overwrote the event plugin's .load
-	// this won't happen again, I hope -John
-	if ( url && url.constructor == Function )
+jQuery.fn.loadIfModified = function( url, params, callback ) {
+	this.load( url, params, callback, 1 );
+};
+
+jQuery.fn.load = function( url, params, callback, ifModified ) {
+	if ( url.constructor == Function )
 		return this.bind("load", url);
 
+	callback = callback || function(){};
+
 	// Default to a GET request
 	var type = "GET";
 
@@ -32,22 +36,23 @@ jQuery.fn.load = function( url, params, callback ) {
 	var self = this;
 	
 	// Request the remote document
-	jQuery.ajax( type, url, params,function(res){
-			
-		// Inject the HTML into all the matched elements
-		self.html(res.responseText).each(function(){
-			// If a callback function was provided
-			if ( callback && callback.constructor == Function )
-				// Execute it within the context of the element
-				callback.apply( self, [res.responseText] );
-		});
+	jQuery.ajax( type, url, params,function(res, status){
 		
-		// Execute all the scripts inside of the newly-injected HTML
-		$("script", self).each(function(){
-			eval( this.text || this.textContent || this.innerHTML || "");
-		});
+		if ( status == "success" || !ifModified && status == "notmodified" ) {
+			// Inject the HTML into all the matched elements
+			self.html(res.responseText).each( callback, [res.responseText, status] );
+			
+			// Execute all the scripts inside of the newly-injected HTML
+			$("script", self).each(function(){
+				if ( this.src )
+					$.getScript( this.src );
+				else
+					eval.call( window, this.text || this.textContent || this.innerHTML || "" );
+			});
+		} else
+			callback.apply( self, [res.responseText, status] );
 
-	});
+	}, ifModified);
 	
 	return this;
 };
@@ -70,7 +75,7 @@ new function(){
 		jQuery.fn[o] = function(f){
 			return this.bind(o, f);
 		};
-	}
+	};
 };
 
 jQuery.extend({
@@ -78,19 +83,27 @@ jQuery.extend({
 	/**
 	 * Load a remote page using a GET request
 	 */
-	get: function( url, data, callback, type ) {
+	get: function( url, data, callback, type, ifModified ) {
 		if ( data.constructor == Function ) {
+			type = callback;
 			callback = data;
 			data = null;
 		}
 		
-		if ( data )
-			url += "?" + jQuery.param(data);
+		if ( data ) url += "?" + jQuery.param(data);
 		
 		// Build and start the HTTP Request
-		jQuery.ajax( "GET", url, null, function(r) {
-			if ( callback ) callback( jQuery.httpData(r,type) );
-		});
+		jQuery.ajax( "GET", url, null, function(r, status) {
+			if ( callback ) callback( jQuery.httpData(r,type), status );
+		}, ifModified);
+	},
+
+	getIfModified: function( url, data, callback, type ) {
+		jQuery.get(url, data, callback, type, 1);
+	},
+
+	getScript: function( url, data, callback ) {
+		jQuery.get(url, data, callback, "script");
 	},
 	
 	/**
@@ -98,15 +111,25 @@ jQuery.extend({
 	 */
 	post: function( url, data, callback, type ) {
 		// Build and start the HTTP Request
-		jQuery.ajax( "POST", url, jQuery.param(data), function(r) {
-			if ( callback ) callback( jQuery.httpData(r,type) );
+		jQuery.ajax( "POST", url, jQuery.param(data), function(r, status) {
+			if ( callback ) callback( jQuery.httpData(r,type), status );
 		});
 	},
 	
+	// timeout (ms)
+	timeout: 0,
+
+	ajaxTimeout: function(timeout) {
+		jQuery.timeout = timeout;
+	},
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	
 	/**
 	 * A common wrapper for making XMLHttpRequests
 	 */
-	ajax: function( type, url, data, ret ) {
+	ajax: function( type, url, data, ret, ifModified ) {
 		// If only a single argument was passed in,
 		// assume that it is a object of key/value pairs
 		if ( !url ) {
@@ -131,23 +154,34 @@ jQuery.extend({
 		// Set the correct header, if data is being sent
 		if ( data )
 			xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-	
+		
+		// Set the If-Modified-Since header, if ifModified mode.
+		if ( ifModified )
+			xml.setRequestHeader("If-Modified-Since",
+				jQuery.lastModified[url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+		
 		// Set header so calling script knows that it's an XMLHttpRequest
 		xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
 	
 		// Make sure the browser sends the right content length
 		if ( xml.overrideMimeType )
 			xml.setRequestHeader("Connection", "close");
-	
+		
 		// Wait for a response to come back
-		xml.onreadystatechange = function(){
-			// The transfer is complete and the data is available
-			if ( xml.readyState == 4 ) {
-				// Make sure that the request was successful
-				if ( jQuery.httpSuccess( xml ) ) {
+		var onreadystatechange = function(istimeout){
+			// The transfer is complete and the data is available, or the request timed out
+			if ( xml && (xml.readyState == 4 || istimeout) ) {
+				var status = jQuery.httpSuccess( xml ) && !istimeout ?
+					ifModified && jQuery.httpNotModified( xml, url ) ? "notmodified" : "success" : "error";
 				
+				// Make sure that the request was successful or notmodified
+				if ( status != "error" ) {
+					// Cache Last-Modified header, if ifModified mode.
+					var modRes = xml.getResponseHeader("Last-Modified");
+					if ( ifModified && modRes ) jQuery.lastModified[url] = modRes;
+					
 					// If a local callback was specified, fire it
-					if ( success ) success( xml );
+					if ( success ) success( xml, status );
 					
 					// Fire the global callback
 					jQuery.event.trigger( "ajaxSuccess" );
@@ -155,7 +189,7 @@ jQuery.extend({
 				// Otherwise, the request was not successful
 				} else {
 					// If a local callback was specified, fire it
-					if ( error ) error( xml );
+					if ( error ) error( xml, status );
 					
 					// Fire the global callback
 					jQuery.event.trigger( "ajaxError" );
@@ -169,14 +203,32 @@ jQuery.extend({
 					jQuery.event.trigger( "ajaxStop" );
 	
 				// Process result
-				if ( ret ) ret(xml);
-
+				if ( ret ) ret(xml, status);
+				
 				// Stop memory leaks
 				xml.onreadystatechange = function(){};
 				xml = null;
+				
 			}
 		};
-	
+		xml.onreadystatechange = onreadystatechange;
+		
+		// Timeout checker
+		if(jQuery.timeout > 0)
+			setTimeout(function(){
+				// Check to see if the request is still happening
+				if (xml) {
+					// Cancel the request
+					xml.abort();
+
+					// for Opera. Opera does't call onreadystatechange when aborted.
+					if (xml) onreadystatechange(1);
+
+					// Clear from memory
+					xml = null;
+				}
+			}, jQuery.timeout);
+		
 		// Send the data
 		xml.send(data);
 	},
@@ -191,6 +243,19 @@ jQuery.extend({
 				( r.status >= 200 && r.status < 300 ) || r.status == 304 :
 				location.protocol == "file:";
 		} catch(e){}
+
+		return false;
+	},
+
+	// Determines if an XMLHttpRequest returns NotModified
+	httpNotModified: function(xml, url) {
+		try {
+			var xmlRes = xml.getResponseHeader("Last-Modified");
+
+			// Firefox always returns 200. check Last-Modified date
+			if( xml.status == 304 || xmlRes == jQuery.lastModified[url]  ) return true;
+		} catch(e){}
+
 		return false;
 	},
 	
@@ -199,8 +264,13 @@ jQuery.extend({
 	// otherwise return plain text.
 	httpData: function(r,type) {
 		var ct = r.getResponseHeader("content-type");
-		var xml = ( !type || type == "xml" ) && ct && ct.indexOf("xml") >= 0;
-		return xml ? r.responseXML : r.responseText;
+		var data = ( !type || type == "xml" ) && ct && ct.indexOf("xml") >= 0;
+		data = data ? r.responseXML : r.responseText;
+
+		// If the type is "script", eval it
+		if ( type == "script" ) eval.call( window, data );
+
+		return data;
 	},
 	
 	// Serialize an array of form elements or a set of
-- 
cgit v1.2.3