Added support for:
authorJohn Resig <jeresig@gmail.com>
Mon, 3 Sep 2007 23:45:14 +0000 (23:45 +0000)
committerJohn Resig <jeresig@gmail.com>
Mon, 3 Sep 2007 23:45:14 +0000 (23:45 +0000)
- Cross Domain getScript
  $.getScript("http://foo.com/script.js");
- JSONP
  $.ajax({ url: "script.js", type: "jsonp" });
  $.getJSON("script.js?callback=?");
- Cross Domain JSONP/getJSON
  $.getJSON("http://foo.com/script.js?callback=?");
- No-cache Ajax Requests
  $.ajax({ url: "test.html", cache: false });

build/test/data/jsonp.php [new file with mode: 0644]
build/test/index.html
src/ajax/ajax.js
src/ajax/ajaxTest.js

diff --git a/build/test/data/jsonp.php b/build/test/data/jsonp.php
new file mode 100644 (file)
index 0000000..7500025
--- /dev/null
@@ -0,0 +1,10 @@
+<?php\r
+error_reporting(0);\r
+$callback = $_REQUEST['callback'];\r
+$json = $_REQUEST['json'];\r
+if($json) {\r
+       echo $callback . '([ {"name": "John", "age": 21}, {"name": "Peter", "age": 25 } ])';\r
+} else {\r
+       echo $callback . '({ "data": {"lang": "en", "length": 25} })';\r
+}\r
+?>\r
index 6956cb9..3b59b0e 100644 (file)
                <form id="form" action="formaction">
                        <input type="text" name="action" value="Test" id="text1"/>
                        <input type="text" name="text2" value="Test" id="text2" disabled="disabled"/>
-                       <input type="radio" name="radio1" id="radio1"/>
+                       <input type="radio" name="radio1" id="radio1" value="on"/>
 
                        <input type="radio" name="radio2" id="radio2" checked="checked"/>
                        <input type="checkbox" name="check" id="check1" checked="checked"/>
-                       <input type="checkbox" id="check2"/>
+                       <input type="checkbox" id="check2" value="on"/>
 
                        <input type="hidden" name="hidden" id="hidden1"/>
                        <input type="text" style="display:none;" name="foo[bar]" id="hidden2"/>
index ccf4f14..0b965e4 100644 (file)
@@ -244,6 +244,8 @@ jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".sp
        };
 });
 
+var jsc = (new Date).getTime();
+
 jQuery.extend({
 
        /**
@@ -599,30 +601,95 @@ jQuery.extend({
         * @see ajaxSetup(Map)
         */
        ajax: function( s ) {
+               var jsonp, jsre = /=(\?|%3F)/g, status, data;
+
                // Extend the settings, but re-extend 's' so that it can be
                // checked again later (in the test suite, specifically)
                s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
 
-               // if data available
-               if ( s.data ) {
-                       // convert data if not already a string
-                       if ( s.processData && typeof s.data != "string" )
-                               s.data = jQuery.param(s.data);
+               // convert data if not already a string
+               if ( s.data && s.processData && typeof s.data != "string" )
+                       s.data = jQuery.param(s.data);
 
-                       // append data to url for get requests
-                       if ( s.type.toLowerCase() == "get" ) {
-                               // "?" + data or "&" + data (in case there are already params)
-                               s.url += (s.url.indexOf("?") > -1 ? "&" : "?") + s.data;
+               // Break the data into one single string
+               var q = s.url.indexOf("?");
+               if ( q > -1 ) {
+                       s.data = (s.data ? s.data + "&" : "") + s.url.slice(q + 1);
+                       s.url = s.url.slice(0, q);
+               }
 
-                               // IE likes to send both get and post data, prevent this
-                               s.data = null;
-                       }
+               // Handle JSONP Parameter Callbacks
+               if ( s.dataType == "jsonp" ) {
+                       if ( !s.data || !s.data.match(jsre) )
+                               s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+                       s.dataType = "json";
+               }
+
+               // Build temporary JSONP function
+               if ( s.dataType == "json" && s.data && s.data.match(jsre) ) {
+                       jsonp = "jsonp" + jsc++;
+                       s.data = s.data.replace(jsre, "=" + jsonp);
+
+                       // We need to make sure
+                       // that a JSONP style response is executed properly
+                       s.dataType = "script";
+
+                       // Handle JSONP-style loading
+                       window[ jsonp ] = function(tmp){
+                               data = tmp;
+                               success();
+                               // Garbage collect
+                               window[ jsonp ] = undefined;
+                               try{ delete window[ jsonp ]; } catch(e){}
+                       };
+               }
+
+               if ( s.dataType == "script" && s.cache == null )
+                       s.cache = false;
+
+               if ( s.cache === false && s.type.toLowerCase() == "get" )
+                       s.data = (s.data ? s.data + "&" : "") + "_=" + (new Date()).getTime();
+
+               // If data is available, append data to url for get requests
+               if ( s.data && s.type.toLowerCase() == "get" ) {
+                       s.url += "?" + s.data;
+
+                       // IE likes to send both get and post data, prevent this
+                       s.data = null;
                }
 
                // Watch for a new set of requests
                if ( s.global && ! jQuery.active++ )
                        jQuery.event.trigger( "ajaxStart" );
 
+               // If we're requesting a remote document
+               // and trying to load JSON or Script
+               if ( !s.url.indexOf("http") && s.dataType == "script" ) {
+                       var script = document.createElement("script");
+                       script.src = s.url;
+
+                       // Handle Script loading
+                       if ( !jsonp && (s.success || s.complete) ) {
+                               var done = false;
+
+                               // Attach handlers for all browsers
+                               script.onload = script.onreadystatechange = function(){
+                                       if ( !done && (!this.readyState || 
+                                                       this.readyState == "loaded" || this.readyState == "complete") ) {
+                                               done = true;
+                                               success();
+                                               complete();
+                                               document.body.removeChild( script );
+                                       }
+                               };
+                       }
+
+                       document.body.appendChild(script);
+
+                       // We handle everything using the script element injection
+                       return;
+               }
+
                var requestDone = false;
 
                // Create the request object; Microsoft failed to properly
@@ -645,7 +712,7 @@ jQuery.extend({
                xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
 
                // Allow custom headers/mimetypes
-               if( s.beforeSend )
+               if ( s.beforeSend )
                        s.beforeSend(xml);
                        
                if ( s.global )
@@ -663,7 +730,7 @@ jQuery.extend({
                                        ival = null;
                                }
                                
-                               var status = isTimeout == "timeout" && "timeout" ||
+                               status = isTimeout == "timeout" && "timeout" ||
                                        !jQuery.httpSuccess( xml ) && "error" ||
                                        s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" ||
                                        "success";
@@ -672,7 +739,7 @@ jQuery.extend({
                                        // Watch for, and catch, XML document parse errors
                                        try {
                                                // process the data (runs the xml through httpData regardless of callback)
-                                               var data = jQuery.httpData( xml, s.dataType );
+                                               data = jQuery.httpData( xml, s.dataType );
                                        } catch(e) {
                                                status = "parsererror";
                                        }
@@ -688,31 +755,18 @@ jQuery.extend({
        
                                        if ( s.ifModified && modRes )
                                                jQuery.lastModified[s.url] = modRes;
-       
-                                       // If a local callback was specified, fire it and pass it the data
-                                       if ( s.success )
-                                               s.success( data, status );
-       
-                                       // Fire the global callback
-                                       if ( s.global )
-                                               jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+
+                                       // JSONP handles its own success callback
+                                       if ( !jsonp )
+                                               success();      
                                } else
                                        jQuery.handleError(s, xml, status);
 
-                               // The request was completed
-                               if( s.global )
-                                       jQuery.event.trigger( "ajaxComplete", [xml, s] );
-
-                               // Handle the global AJAX counter
-                               if ( s.global && ! --jQuery.active )
-                                       jQuery.event.trigger( "ajaxStop" );
-
-                               // Process result
-                               if ( s.complete )
-                                       s.complete(xml, status);
+                               // Fire the complete handlers
+                               complete();
 
                                // Stop memory leaks
-                               if(s.async)
+                               if ( s.async )
                                        xml = null;
                        }
                };
@@ -748,6 +802,30 @@ jQuery.extend({
                
                // return XMLHttpRequest to allow aborting the request etc.
                return xml;
+
+               function success(){
+                       // If a local callback was specified, fire it and pass it the data
+                       if ( s.success )
+                               s.success( data, status );
+
+                       // Fire the global callback
+                       if ( s.global )
+                               jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+               }
+
+               function complete(){
+                       // Process result
+                       if ( s.complete )
+                               s.complete(xml, status);
+
+                       // The request was completed
+                       if ( s.global )
+                               jQuery.event.trigger( "ajaxComplete", [xml, s] );
+
+                       // Handle the global AJAX counter
+                       if ( s.global && ! --jQuery.active )
+                               jQuery.event.trigger( "ajaxStop" );
+               }
        },
 
        handleError: function( s, xml, status, e ) {
@@ -793,7 +871,7 @@ jQuery.extend({
        httpData: function( r, type ) {
                var ct = r.getResponseHeader("content-type");
                var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0;
-               data = xml ? r.responseXML : r.responseText;
+               var data = xml ? r.responseXML : r.responseText;
 
                if ( xml && data.documentElement.tagName == "parsererror" )
                        throw "parsererror";
index 0fd4888..24afb5e 100644 (file)
@@ -3,7 +3,164 @@ module("ajax");
 // Safari 3 randomly crashes when running these tests,
 // but only in the full suite - you can run just the Ajax
 // tests and they'll pass
-if ( !jQuery.browser.safari ) {
+//if ( !jQuery.browser.safari ) {
+
+test("$.ajax() - success callbacks", function() {
+       expect( 8 );
+       
+       $.ajaxSetup({ timeout: 0 });
+       
+       stop();
+       
+       setTimeout(function(){  
+        $('#foo').ajaxStart(function(){
+            ok( true, "ajaxStart" );
+        }).ajaxStop(function(){
+            ok( true, "ajaxStop" );
+            start();
+        }).ajaxSend(function(){
+            ok( true, "ajaxSend" );
+        }).ajaxComplete(function(){
+            ok( true, "ajaxComplete" );
+        }).ajaxError(function(){
+            ok( false, "ajaxError" );
+        }).ajaxSuccess(function(){
+            ok( true, "ajaxSuccess" );
+        });
+        
+        $.ajax({
+            url: url("data/name.html"),
+            beforeSend: function(){ ok(true, "beforeSend"); },
+            success: function(){ ok(true, "success"); },
+            error: function(){ ok(false, "error"); },
+            complete: function(){ ok(true, "complete"); }
+        });
+    }, 13);
+});
+
+if ( !isLocal ) {
+       test("$.ajax() - error callbacks", function() {
+               expect( 8 );
+               stop();
+               
+               $('#foo').ajaxStart(function(){
+                       ok( true, "ajaxStart" );
+               }).ajaxStop(function(){
+                       ok( true, "ajaxStop" );
+                       start();
+               }).ajaxSend(function(){
+                       ok( true, "ajaxSend" );
+               }).ajaxComplete(function(){
+                       ok( true, "ajaxComplete" );
+               }).ajaxError(function(){
+                       ok( true, "ajaxError" );
+               }).ajaxSuccess(function(){
+                       ok( false, "ajaxSuccess" );
+               });
+               
+               $.ajaxSetup({ timeout: 500 });
+               
+               $.ajax({
+                       url: url("data/name.php?wait=5"),
+                       beforeSend: function(){ ok(true, "beforeSend"); },
+                       success: function(){ ok(false, "success"); },
+                       error: function(){ ok(true, "error"); },
+                       complete: function(){ ok(true, "complete"); }
+               });
+       });
+}
+
+test("$.ajax() - disabled globals", function() {
+       expect( 3 );
+       stop();
+       
+       $('#foo').ajaxStart(function(){
+               ok( false, "ajaxStart" );
+       }).ajaxStop(function(){
+               ok( false, "ajaxStop" );
+       }).ajaxSend(function(){
+               ok( false, "ajaxSend" );
+       }).ajaxComplete(function(){
+               ok( false, "ajaxComplete" );
+       }).ajaxError(function(){
+               ok( false, "ajaxError" );
+       }).ajaxSuccess(function(){
+               ok( false, "ajaxSuccess" );
+       });
+       
+       $.ajax({
+               global: false,
+               url: url("data/name.html"),
+               beforeSend: function(){ ok(true, "beforeSend"); },
+               success: function(){ ok(true, "success"); },
+               error: function(){ ok(false, "error"); },
+               complete: function(){
+                 ok(true, "complete");
+                 setTimeout(function(){ start(); }, 13);
+        }
+       });
+});
+
+test("$.ajax - xml: non-namespace elements inside namespaced elements", function() {
+       expect(3);
+       stop();
+       $.ajax({
+         url: url("data/with_fries.xml"),
+         dataType: "xml",
+         success: function(resp) {
+           equals( $("properties", resp).length, 1, 'properties in responseXML' );
+           equals( $("jsconf", resp).length, 1, 'jsconf in responseXML' );
+           equals( $("thing", resp).length, 2, 'things in responseXML' );
+           start();
+         }
+       });
+});
+
+test("$.ajax - beforeSend", function() {
+       expect(1);
+       stop();
+       
+       var check = false;
+       
+       $.ajaxSetup({ timeout: 0 });
+       
+       $.ajax({
+               url: url("data/name.html"), 
+               beforeSend: function(xml) {
+                       check = true;
+               },
+               success: function(data) {
+                       ok( check, "check beforeSend was executed" );
+                       start();
+               }
+       });
+});
+
+var foobar;
+
+test("$.ajax - dataType html", function() {
+       expect(5);
+       stop();
+       
+       foobar = null;
+       testFoo = undefined;
+       
+       var verifyEvaluation = function() {
+         ok( testFoo == "foo", 'Check if script was evaluated for datatype html' );
+         ok( foobar == "bar", 'Check if script src was evaluated for datatype html' );
+         start();
+       };
+
+       $.ajax({
+         dataType: "html",
+         url: url("data/test.html"),
+         success: function(data) {
+               $("#ap").html(data);
+           ok( data.match(/^html text/), 'Check content for datatype html' );
+           setTimeout(verifyEvaluation, 600);
+         }
+       });
+});
 
 test("serialize()", function() {
        expect(1);
@@ -66,23 +223,23 @@ test("global ajaxSettings", function() {
        expect(3);
 
        var tmp = jQuery.extend({}, jQuery.ajaxSettings);
-        var orig = { url: "data/with_fries.xml", data: null };
+    var orig = { url: "data/with_fries.xml", data: null };
        var t;
 
        $.ajaxSetup({ data: {foo: 'bar', bar: 'BAR'} });
 
-        t = jQuery.extend({}, orig);
-        $.ajax(t);
+    t = jQuery.extend({}, orig);
+    $.ajax(t);
        ok( t.url.indexOf('foo') > -1 && t.url.indexOf('bar') > -1, "Check extending null" );
 
-        t = jQuery.extend({}, orig);
+    t = jQuery.extend({}, orig);
        t.data = {};
-        $.ajax(t);
+    $.ajax(t);
        ok( t.url.indexOf('foo') > -1 && t.url.indexOf('bar') > -1, "Check extending {}" );
 
-        t = jQuery.extend({}, orig);
+    t = jQuery.extend({}, orig);
        t.data = { zoo: 'a', ping: 'b' };
-        $.ajax(t);
+    $.ajax(t);
        ok( t.url.indexOf('ping') > -1 && t.url.indexOf('zoo') > -1 && t.url.indexOf('foo') > -1 && t.url.indexOf('bar') > -1, "Check extending { zoo: 'a', ping: 'b' }" );
        
        jQuery.ajaxSettings = tmp;
@@ -179,139 +336,153 @@ test("$.getScript(String, Function) - no callback", function() {
        $.getScript(url("data/test.js"), start);
 });
 
-test("$.ajax - xml: non-namespace elements inside namespaced elements", function() {
-       expect(3);
+if ( !isLocal ) {
+
+test("$.ajax() - JSONP, Local", function() {
+       expect(7);
+
+       var count = 0;
+       function plus(){ if ( ++count == 7 ) start(); }
+
        stop();
+
        $.ajax({
-         url: url("data/with_fries.xml"),
-         dataType: "xml",
-         success: function(resp) {
-           equals( $("properties", resp).length, 1, 'properties in responseXML' );
-           equals( $("jsconf", resp).length, 1, 'jsconf in responseXML' );
-           equals( $("thing", resp).length, 2, 'things in responseXML' );
-           start();
-         }
+               url: "data/jsonp.php",
+               dataType: "jsonp",
+               success: function(data){
+                       ok( data.data, "JSON results returned (GET, no callback)" );
+                       plus();
+               }
        });
-});
 
-test("test global handlers - success", function() {
-       expect( isLocal ? 4 : 8 );
-       stop();
-       
-       var counter = { complete: 0, success: 0, error: 0, send: 0 },
-               success = function() { counter.success++ },
-               error = function() { counter.error++ },
-               complete = function() { counter.complete++ },
-               send = function() { counter.send++ };
+       $.ajax({
+               url: "data/jsonp.php?callback=?",
+               dataType: "jsonp",
+               success: function(data){
+                       ok( data.data, "JSON results returned (GET, url callback)" );
+                       plus();
+               }
+       });
 
-       $('#foo').ajaxStart(complete).ajaxStop(complete).ajaxSend(send).ajaxComplete(complete).ajaxError(error).ajaxSuccess(success);
-       
-       // start with successful test
-       $.ajax({url: url("data/name.html"), beforeSend: send, success: success, error: error, complete: function() {
-         equals( counter.error, 0, 'Check succesful request, error callback' );
-         equals( counter.success, 2, 'Check succesful request, success callback' );
-         equals( counter.complete, 3, 'Check succesful request, complete callback' );
-         equals( counter.send, 2, 'Check succesful request, send callback' );
-         
-         if ( !isLocal ) {
-                 counter.error = counter.success = counter.complete = counter.send = 0;
-                 $.ajaxTimeout(500);
-                 
-                 $.ajax({url: url("data/name.php?wait=5"), beforeSend: send, success: success, error: error, complete: function() {
-                       equals( counter.error, 2, 'Check failed request, error callback' );
-                       equals( counter.success, 0, 'Check failed request, success callback' );
-                       equals( counter.complete, 3, 'Check failed request, failed callback' );
-                       equals( counter.send, 2, 'Check failed request, send callback' );
-                       start();
-                 }});
-         } else
-                 start();
-       }});
-});
+       $.ajax({
+               url: "data/jsonp.php",
+               dataType: "jsonp",
+               data: "callback=?",
+               success: function(data){
+                       ok( data.data, "JSON results returned (GET, data callback)" );
+                       plus();
+               }
+       });
 
-test("test global handlers - failure", function() {
-       expect( isLocal ? 4 : 8 );
-       stop();
-       
-       var counter = { complete: 0, success: 0, error: 0, send: 0 },
-               success = function() { counter.success++ },
-               error = function() { counter.error++ },
-               complete = function() { counter.complete++ },
-               send = function() { counter.send++ };
-               
-       $.ajaxTimeout(0);
-       
-       $('#foo').ajaxStart(complete).ajaxStop(complete).ajaxSend(send).ajaxComplete(complete).ajaxError(error).ajaxSuccess(success);
-       
-       $.ajax({url: url("data/name.php"), global: false, beforeSend: send, success: success, error: error, complete: function() {
-         ok( counter.error == 0, 'Check sucesful request without globals' );
-         ok( counter.success == 1, 'Check sucesful request without globals' );
-         ok( counter.complete == 0, 'Check sucesful request without globals' );
-         ok( counter.send == 1, 'Check sucesful request without globals' );
-         
-         if ( !isLocal ) {
-                 counter.error = counter.success = counter.complete = counter.send = 0;
-                 $.ajaxTimeout(500);
-                 
-                 $.ajax({url: url("data/name.php?wait=5"), global: false, beforeSend: send, success: success, error: error, complete: function() {
-                        var x = counter;
-                        ok( counter.error == 1, 'Check failed request without globals' );
-                        ok( counter.success == 0, 'Check failed request without globals' );
-                        ok( counter.complete == 0, 'Check failed request without globals' );
-                        ok( counter.send == 1, 'Check failed request without globals' );
-                        start();
-                 }});
-         } else
-                 start();
-       }});
+       $.ajax({
+               url: "data/jsonp.php",
+               dataType: "jsonp",
+               data: { callback: "?" },
+               success: function(data){
+                       ok( data.data, "JSON results returned (GET, data obj callback)" );
+                       plus();
+               }
+       });
+
+       $.ajax({
+               type: "POST",
+               url: "data/jsonp.php",
+               dataType: "jsonp",
+               success: function(data){
+                       ok( data.data, "JSON results returned (POST, no callback)" );
+                       plus();
+               }
+       });
+
+       $.ajax({
+               type: "POST",
+               url: "data/jsonp.php",
+               data: "callback=?",
+               dataType: "jsonp",
+               success: function(data){
+                       ok( data.data, "JSON results returned (POST, data callback)" );
+                       plus();
+               }
+       });
+
+       $.ajax({
+               type: "POST",
+               url: "data/jsonp.php",
+               data: { callback: "?" },
+               dataType: "jsonp",
+               success: function(data){
+                       ok( data.data, "JSON results returned (POST, data obj callback)" );
+                       plus();
+               }
+       });
 });
 
-test("$.ajax - beforeSend", function() {
-       expect(1);
+test("$.ajax() - JSONP, Remote", function() {
+       expect(4);
+
+       var count = 0;
+       function plus(){ if ( ++count == 4 ) start(); }
+
+       var base = window.location.href.replace(/\?.*$/, "");
+
        stop();
-       
-       var check = false;
-       
-       $.ajaxSetup({ timeout: 0 });
-       
+
        $.ajax({
-               url: url("data/name.html"), 
-               beforeSend: function(xml) {
-                       check = true;
-               },
-               success: function(data) {
-                       ok( check, "check beforeSend was executed" );
-                       start();
+               url: base + "data/jsonp.php",
+               dataType: "jsonp",
+               success: function(data){
+                       ok( data.data, "JSON results returned (GET, no callback)" );
+                       plus();
+               }
+       });
+
+       $.ajax({
+               url: base + "data/jsonp.php?callback=?",
+               dataType: "jsonp",
+               success: function(data){
+                       ok( data.data, "JSON results returned (GET, url callback)" );
+                       plus();
+               }
+       });
+
+       $.ajax({
+               url: base + "data/jsonp.php",
+               dataType: "jsonp",
+               data: "callback=?",
+               success: function(data){
+                       ok( data.data, "JSON results returned (GET, data callback)" );
+                       plus();
+               }
+       });
+
+       $.ajax({
+               url: base + "data/jsonp.php",
+               dataType: "jsonp",
+               data: { callback: "?" },
+               success: function(data){
+                       ok( data.data, "JSON results returned (GET, data obj callback)" );
+                       plus();
                }
        });
 });
 
-test("$.ajax - dataType html", function() {
-       expect(5);
+test("$.ajax() - script, Remote", function() {
+       expect(2);
+
+       var base = window.location.href.replace(/\?.*$/, "");
+
        stop();
-       
-       foobar = null;
-       testFoo = undefined;
-       
-       var verifyEvaluation = function() {
-         ok( testFoo == "foo", 'Check if script was evaluated for datatype html' );
-         ok( foobar == "bar", 'Check if script src was evaluated for datatype html' );
-         start();
-       };
-       
+
        $.ajax({
-         dataType: "html",
-         url: url("data/test.html"),
-         success: function(data) {
-               $("#ap").html(data);
-           ok( data.match(/^html text/), 'Check content for datatype html' );
-           setTimeout(verifyEvaluation, 600);
-         }
+               url: base + "data/test.js",
+               dataType: "script",
+               success: function(data){
+                       ok( foobar, "Script results returned (GET, no callback)" );
+                       start();
+               }
        });
 });
 
-if ( !isLocal ) {
-
 test("$.getJSON(String, Hash, Function) - JSON array", function() {
        expect(4);
        stop();
@@ -454,4 +625,4 @@ test("custom timeout does not set error message when timeout occurs, see #970",
 
 }
 
-}
+//}