Refactored test suite to allow async tests (use stop() before starting an async reque...
[jquery.git] / src / ajax / ajax.js
1 // AJAX Plugin
2 // Docs Here:
3 // http://jquery.com/docs/ajax/
4
5 /**
6  * Load HTML from a remote file and inject it into the DOM, only if it's
7  * been modified by the server.
8  *
9  * @example $("#feeds").loadIfModified("feeds.html")
10  * @before <div id="feeds"></div>
11  * @result <div id="feeds"><b>45</b> feeds found.</div>
12  *
13  * @name loadIfModified
14  * @type jQuery
15  * @param String url The URL of the HTML file to load.
16  * @param Hash params A set of key/value pairs that will be sent to the server.
17  * @param Function callback A function to be executed whenever the data is loaded.
18  * @cat AJAX
19  */
20 jQuery.fn.loadIfModified = function( url, params, callback ) {
21         this.load( url, params, callback, 1 );
22 };
23
24 /**
25  * Load HTML from a remote file and inject it into the DOM.
26  *
27  * @example $("#feeds").load("feeds.html")
28  * @before <div id="feeds"></div>
29  * @result <div id="feeds"><b>45</b> feeds found.</div>
30  *
31  * @name load
32  * @type jQuery
33  * @param String url The URL of the HTML file to load.
34  * @param Hash params A set of key/value pairs that will be sent to the server.
35  * @param Function callback A function to be executed whenever the data is loaded.
36  * @cat AJAX
37  */
38 jQuery.fn.load = function( url, params, callback, ifModified ) {
39         if ( url.constructor == Function )
40                 return this.bind("load", url);
41
42         callback = callback || function(){};
43
44         // Default to a GET request
45         var type = "GET";
46
47         // If the second parameter was provided
48         if ( params ) {
49                 // If it's a function
50                 if ( params.constructor == Function ) {
51                         // We assume that it's the callback
52                         callback = params;
53                         params = null;
54                         
55                 // Otherwise, build a param string
56                 } else {
57                         params = jQuery.param( params );
58                         type = "POST";
59                 }
60         }
61         
62         var self = this;
63         
64         // Request the remote document
65         jQuery.ajax( type, url, params,function(res, status){
66                 
67                 if ( status == "success" || !ifModified && status == "notmodified" ) {
68                         // Inject the HTML into all the matched elements
69                         self.html(res.responseText).each( callback, [res.responseText, status] );
70                         
71                         // Execute all the scripts inside of the newly-injected HTML
72                         $("script", self).each(function(){
73                                 if ( this.src )
74                                         $.getScript( this.src );
75                                 else
76                                         eval.call( window, this.text || this.textContent || this.innerHTML || "" );
77                         });
78                 } else
79                         callback.apply( self, [res.responseText, status] );
80
81         }, ifModified);
82         
83         return this;
84 };
85
86 /**
87  * A function for serializing a set of input elements into
88  * a string of data.
89  *
90  * @example $("input[@type=text]").serialize();
91  * @before <input type='text' name='name' value='John'/>
92  * <input type='text' name='location' value='Boston'/>
93  * @after name=John&location=Boston
94  * @desc Serialize a selection of input elements to a string
95  *
96  * @name serialize
97  * @type String
98  * @cat AJAX
99  */
100 jQuery.fn.serialize = function(){
101         return $.param( this );
102 };
103
104 // If IE is used, create a wrapper for the XMLHttpRequest object
105 if ( jQuery.browser.msie && typeof XMLHttpRequest == "undefined" )
106         XMLHttpRequest = function(){
107                 return new ActiveXObject(
108                         navigator.userAgent.indexOf("MSIE 5") >= 0 ?
109                         "Microsoft.XMLHTTP" : "Msxml2.XMLHTTP"
110                 );
111         };
112
113 // Attach a bunch of functions for handling common AJAX events
114
115 /**
116  * Attach a function to be executed whenever an AJAX request begins.
117  *
118  * @example $("#loading").ajaxStart(function(){
119  *   $(this).show();
120  * });
121  * @desc Show a loading message whenever an AJAX request starts.
122  *
123  * @name ajaxStart
124  * @type jQuery
125  * @param Function callback The function to execute.
126  * @cat AJAX
127  */
128  
129 /**
130  * Attach a function to be executed whenever all AJAX requests have ended.
131  *
132  * @example $("#loading").ajaxStop(function(){
133  *   $(this).hide();
134  * });
135  * @desc Hide a loading message after all the AJAX requests have stopped.
136  *
137  * @name ajaxStop
138  * @type jQuery
139  * @param Function callback The function to execute.
140  * @cat AJAX
141  */
142  
143 /**
144  * Attach a function to be executed whenever an AJAX request completes.
145  *
146  * @example $("#msg").ajaxComplete(function(){
147  *   $(this).append("<li>Request Complete.</li>");
148  * });
149  * @desc Show a message when an AJAX request completes.
150  *
151  * @name ajaxComplete
152  * @type jQuery
153  * @param Function callback The function to execute.
154  * @cat AJAX
155  */
156  
157 /**
158  * Attach a function to be executed whenever an AJAX request completes
159  * successfully.
160  *
161  * @example $("#msg").ajaxSuccess(function(){
162  *   $(this).append("<li>Successful Request!</li>");
163  * });
164  * @desc Show a message when an AJAX request completes successfully.
165  *
166  * @name ajaxSuccess
167  * @type jQuery
168  * @param Function callback The function to execute.
169  * @cat AJAX
170  */
171  
172 /**
173  * Attach a function to be executed whenever an AJAX request fails.
174  *
175  * @example $("#msg").ajaxError(function(){
176  *   $(this).append("<li>Error requesting page.</li>");
177  * });
178  * @desc Show a message when an AJAX request fails.
179  *
180  * @name ajaxError
181  * @type jQuery
182  * @param Function callback The function to execute.
183  * @cat AJAX
184  */
185
186 new function(){
187         var e = "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess".split(",");
188         
189         for ( var i = 0; i < e.length; i++ ) new function(){
190                 var o = e[i];
191                 jQuery.fn[o] = function(f){
192                         return this.bind(o, f);
193                 };
194         };
195 };
196
197 jQuery.extend({
198
199         /**
200          * Load a remote page using an HTTP GET request. All of the arguments to
201          * the method (except URL) are optional.
202          *
203          * @example $.get("test.cgi")
204          *
205          * @example $.get("test.cgi", { name: "John", time: "2pm" } )
206          *
207          * @example $.get("test.cgi", function(data){
208          *   alert("Data Loaded: " + data);
209          * })
210          *
211          * @example $.get("test.cgi",
212          *   { name: "John", time: "2pm" },
213          *   function(data){
214          *     alert("Data Loaded: " + data);
215          *   }
216          * )
217          *
218          * @test stop();
219          * $.get("data/dashboard.xml", function(xml) {
220          *     var content = [];
221      *     $('tab', xml).each(function(k) {
222      *         // workaround for IE needed here, $(this).text() throws an error
223      *         // content[k] = $.trim(this.firstChild.data) || $(this).text();
224      *         content[k] = $(this).text();
225      *     });
226          *         ok( content[0].match(/blabla/), 'Check first tab' );
227          *     ok( content[1].match(/blublu/), 'Check second tab' );
228          *     start();
229          * });
230          *
231          * @name $.get
232          * @type jQuery
233          * @param String url The URL of the page to load.
234          * @param Hash params A set of key/value pairs that will be sent to the server.
235          * @param Function callback A function to be executed whenever the data is loaded.
236          * @cat AJAX
237          */
238         get: function( url, data, callback, type, ifModified ) {
239                 if ( data.constructor == Function ) {
240                         type = callback;
241                         callback = data;
242                         data = null;
243                 }
244                 
245                 if ( data ) url += "?" + jQuery.param(data);
246                 
247                 // Build and start the HTTP Request
248                 jQuery.ajax( "GET", url, null, function(r, status) {
249                         if ( callback ) callback( jQuery.httpData(r,type), status );
250                 }, ifModified);
251         },
252         
253         /**
254          * Load a remote page using an HTTP GET request, only if it hasn't
255          * been modified since it was last retrieved. All of the arguments to
256          * the method (except URL) are optional.
257          *
258          * @example $.getIfModified("test.html")
259          *
260          * @example $.getIfModified("test.html", { name: "John", time: "2pm" } )
261          *
262          * @example $.getIfModified("test.cgi", function(data){
263          *   alert("Data Loaded: " + data);
264          * })
265          *
266          * @example $.getifModified("test.cgi",
267          *   { name: "John", time: "2pm" },
268          *   function(data){
269          *     alert("Data Loaded: " + data);
270          *   }
271          * )
272          *
273          * @name $.getIfModified
274          * @type jQuery
275          * @param String url The URL of the page to load.
276          * @param Hash params A set of key/value pairs that will be sent to the server.
277          * @param Function callback A function to be executed whenever the data is loaded.
278          * @cat AJAX
279          */
280         getIfModified: function( url, data, callback, type ) {
281                 jQuery.get(url, data, callback, type, 1);
282         },
283
284         /**
285          * Loads, and executes, a remote JavaScript file using an HTTP GET request.
286          * All of the arguments to the method (except URL) are optional.
287          *
288          * @example $.getScript("test.js")
289          *
290          * @example $.getScript("test.js", function(){
291          *   alert("Script loaded and executed.");
292          * })
293          *
294          *
295          * @name $.getScript
296          * @type jQuery
297          * @param String url The URL of the page to load.
298          * @param Function callback A function to be executed whenever the data is loaded.
299          * @cat AJAX
300          */
301         getScript: function( url, data, callback ) {
302                 jQuery.get(url, data, callback, "script");
303         },
304         
305         /**
306          * Load a remote JSON object using an HTTP GET request.
307          * All of the arguments to the method (except URL) are optional.
308          *
309          * @example $.getJSON("test.js", function(json){
310          *   alert("JSON Data: " + json.users[3].name);
311          * })
312          *
313          * @example $.getJSON("test.js",
314          *   { name: "John", time: "2pm" },
315          *   function(json){
316          *     alert("JSON Data: " + json.users[3].name);
317          *   }
318          * )
319          *
320          * @name $.getJSON
321          * @type jQuery
322          * @param String url The URL of the page to load.
323          * @param Hash params A set of key/value pairs that will be sent to the server.
324          * @param Function callback A function to be executed whenever the data is loaded.
325          * @cat AJAX
326          */
327         getJSON: function( url, data, callback ) {
328                 jQuery.get(url, data, callback, "json");
329         },
330         
331         /**
332          * Load a remote page using an HTTP POST request. All of the arguments to
333          * the method (except URL) are optional.
334          *
335          * @example $.post("test.cgi")
336          *
337          * @example $.post("test.cgi", { name: "John", time: "2pm" } )
338          *
339          * @example $.post("test.cgi", function(data){
340          *   alert("Data Loaded: " + data);
341          * })
342          *
343          * @example $.post("test.cgi",
344          *   { name: "John", time: "2pm" },
345          *   function(data){
346          *     alert("Data Loaded: " + data);
347          *   }
348          * )
349          *
350          * @name $.post
351          * @type jQuery
352          * @param String url The URL of the page to load.
353          * @param Hash params A set of key/value pairs that will be sent to the server.
354          * @param Function callback A function to be executed whenever the data is loaded.
355          * @cat AJAX
356          */
357         post: function( url, data, callback, type ) {
358                 // Build and start the HTTP Request
359                 jQuery.ajax( "POST", url, jQuery.param(data), function(r, status) {
360                         if ( callback ) callback( jQuery.httpData(r,type), status );
361                 });
362         },
363         
364         // timeout (ms)
365         timeout: 0,
366
367         /**
368          * Set the timeout of all AJAX requests to a specific amount of time.
369          * This will make all future AJAX requests timeout after a specified amount
370          * of time (the default is no timeout).
371          *
372          * @example $.ajaxTimeout( 5000 );
373          * @desc Make all AJAX requests timeout after 5 seconds.
374          *
375          * @name $.ajaxTimeout
376          * @type jQuery
377          * @param Number time How long before an AJAX request times out.
378          * @cat AJAX
379          */
380         ajaxTimeout: function(timeout) {
381                 jQuery.timeout = timeout;
382         },
383
384         // Last-Modified header cache for next request
385         lastModified: {},
386         
387         /**
388          * Load a remote page using an HTTP request. This function is the primary
389          * means of making AJAX requests using jQuery. $.ajax() takes one property,
390          * an object of key/value pairs, that're are used to initalize the request.
391          *
392          * These are all the key/values that can be passed in to 'prop':
393          *
394          * (String) type - The type of request to make (e.g. "POST" or "GET").
395          *
396          * (String) url - The URL of the page to request.
397          * 
398          * (String) data - A string of data to be sent to the server (POST only).
399          *
400          * (String) dataType - The type of data that you're expecting back from
401          * the server (e.g. "xml", "html", "script", or "json").
402          *
403          * (Function) error - A function to be called if the request fails. The
404          * function gets passed two arguments: The XMLHttpRequest object and a
405          * string describing the type of error that occurred.
406          *
407          * (Function) success - A function to be called if the request succeeds. The
408          * function gets passed one argument: The data returned from the server,
409          * formatted according to the 'dataType' parameter.
410          *
411          * (Function) complete - A function to be called when the request finishes. The
412          * function gets passed two arguments: The XMLHttpRequest object and a
413          * string describing the type the success of the request.
414          *
415          * @example $.ajax({
416          *   type: "GET",
417          *   url: "test.js",
418          *   dataType: "script"
419          * })
420          * @desc Load and execute a JavaScript file.
421          *
422          * @example $.ajax({
423          *   type: "POST",
424          *   url: "some.php",
425          *   data: "name=John&location=Boston",
426          *   success: function(msg){
427          *     alert( "Data Saved: " + msg );
428          *   }
429          * });
430          * @desc Save some data to the server and notify the user once its complete.
431          *
432          * @name $.ajax
433          * @type jQuery
434          * @param Hash prop A set of properties to initialize the request with.
435          * @cat AJAX
436          */
437         ajax: function( type, url, data, ret, ifModified ) {
438                 // If only a single argument was passed in,
439                 // assume that it is a object of key/value pairs
440                 if ( !url ) {
441                         ret = type.complete;
442                         var success = type.success;
443                         var error = type.error;
444                         var dataType = type.dataType;
445                         data = type.data;
446                         url = type.url;
447                         type = type.type;
448                 }
449                 
450                 // Watch for a new set of requests
451                 if ( ! jQuery.active++ )
452                         jQuery.event.trigger( "ajaxStart" );
453
454                 var requestDone = false;
455         
456                 // Create the request object
457                 var xml = new XMLHttpRequest();
458         
459                 // Open the socket
460                 xml.open(type || "GET", url, true);
461                 
462                 // Set the correct header, if data is being sent
463                 if ( data )
464                         xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
465                 
466                 // Set the If-Modified-Since header, if ifModified mode.
467                 if ( ifModified )
468                         xml.setRequestHeader("If-Modified-Since",
469                                 jQuery.lastModified[url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
470                 
471                 // Set header so calling script knows that it's an XMLHttpRequest
472                 xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
473         
474                 // Make sure the browser sends the right content length
475                 if ( xml.overrideMimeType )
476                         xml.setRequestHeader("Connection", "close");
477                 
478                 // Wait for a response to come back
479                 var onreadystatechange = function(istimeout){
480                         // The transfer is complete and the data is available, or the request timed out
481                         if ( xml && (xml.readyState == 4 || istimeout == "timeout") ) {
482                                 requestDone = true;
483
484                                 var status = jQuery.httpSuccess( xml ) && istimeout != "timeout" ?
485                                         ifModified && jQuery.httpNotModified( xml, url ) ? "notmodified" : "success" : "error";
486                                 
487                                 // Make sure that the request was successful or notmodified
488                                 if ( status != "error" ) {
489                                         // Cache Last-Modified header, if ifModified mode.
490                                         var modRes = xml.getResponseHeader("Last-Modified");
491                                         if ( ifModified && modRes ) jQuery.lastModified[url] = modRes;
492                                         
493                                         // If a local callback was specified, fire it
494                                         if ( success )
495                                                 success( jQuery.httpData( xml, dataType ), status );
496                                         
497                                         // Fire the global callback
498                                         jQuery.event.trigger( "ajaxSuccess" );
499                                 
500                                 // Otherwise, the request was not successful
501                                 } else {
502                                         // If a local callback was specified, fire it
503                                         if ( error ) error( xml, status );
504                                         
505                                         // Fire the global callback
506                                         jQuery.event.trigger( "ajaxError" );
507                                 }
508                                 
509                                 // The request was completed
510                                 jQuery.event.trigger( "ajaxComplete" );
511                                 
512                                 // Handle the global AJAX counter
513                                 if ( ! --jQuery.active )
514                                         jQuery.event.trigger( "ajaxStop" );
515         
516                                 // Process result
517                                 if ( ret ) ret(xml, status);
518                                 
519                                 // Stop memory leaks
520                                 xml.onreadystatechange = function(){};
521                                 xml = null;
522                                 
523                         }
524                 };
525                 xml.onreadystatechange = onreadystatechange;
526                 
527                 // Timeout checker
528                 if(jQuery.timeout > 0)
529                         setTimeout(function(){
530                                 // Check to see if the request is still happening
531                                 if (xml) {
532                                         // Cancel the request
533                                         xml.abort();
534
535                                         if ( !requestDone ) onreadystatechange( "timeout" );
536
537                                         // Clear from memory
538                                         xml = null;
539                                 }
540                         }, jQuery.timeout);
541                 
542                 // Send the data
543                 xml.send(data);
544         },
545         
546         // Counter for holding the number of active queries
547         active: 0,
548         
549         // Determines if an XMLHttpRequest was successful or not
550         httpSuccess: function(r) {
551                 try {
552                         return !r.status && location.protocol == "file:" ||
553                                 ( r.status >= 200 && r.status < 300 ) || r.status == 304 ||
554                                 jQuery.browser.safari && r.status == undefined;
555                 } catch(e){}
556
557                 return false;
558         },
559
560         // Determines if an XMLHttpRequest returns NotModified
561         httpNotModified: function(xml, url) {
562                 try {
563                         var xmlRes = xml.getResponseHeader("Last-Modified");
564
565                         // Firefox always returns 200. check Last-Modified date
566                         return xml.status == 304 || xmlRes == jQuery.lastModified[url] ||
567                                 jQuery.browser.safari && xml.status == undefined;
568                 } catch(e){}
569
570                 return false;
571         },
572         
573         /* Get the data out of an XMLHttpRequest.
574          * Return parsed XML if content-type header is "xml" and type is "xml" or omitted,
575          * otherwise return plain text.
576          * (String) data - The type of data that you're expecting back,
577          * (e.g. "xml", "html", "script")
578          */
579         httpData: function(r,type) {
580                 var ct = r.getResponseHeader("content-type");
581                 var data = !type && ct && ct.indexOf("xml") >= 0;
582                 data = type == "xml" || data ? r.responseXML : r.responseText;
583
584                 // If the type is "script", eval it
585                 if ( type == "script" ) eval.call( window, data );
586
587                 // Get the JavaScript object, if JSON is used.
588                 if ( type == "json" ) eval( "data = " + data );
589
590                 return data;
591         },
592         
593         // Serialize an array of form elements or a set of
594         // key/values into a query string
595         param: function(a) {
596                 var s = [];
597                 
598                 // If an array was passed in, assume that it is an array
599                 // of form elements
600                 if ( a.constructor == Array || a.jquery ) {
601                         // Serialize the form elements
602                         for ( var i = 0; i < a.length; i++ )
603                                 s.push( a[i].name + "=" + encodeURIComponent( a[i].value ) );
604                         
605                 // Otherwise, assume that it's an object of key/value pairs
606                 } else {
607                         // Serialize the key/values
608                         for ( var j in a )
609                                 s.push( j + "=" + encodeURIComponent( a[j] ) );
610                 }
611                 
612                 // Return the resulting serialization
613                 return s.join("&");
614         }
615
616 });