jquery.mockjax.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. /*!
  2. * MockJax - jQuery Plugin to Mock Ajax requests
  3. *
  4. * Version: 1.4.0
  5. * Released: 2011-02-04
  6. * Source: http://github.com/appendto/jquery-mockjax
  7. * Docs: http://enterprisejquery.com/2010/07/mock-your-ajax-requests-with-mockjax-for-rapid-development
  8. * Plugin: mockjax
  9. * Author: Jonathan Sharp (http://jdsharp.com)
  10. * License: MIT,GPL
  11. *
  12. * Copyright (c) 2010 appendTo LLC.
  13. * Dual licensed under the MIT or GPL licenses.
  14. * http://appendto.com/open-source-licenses
  15. */
  16. (function($) {
  17. var _ajax = $.ajax,
  18. mockHandlers = [];
  19. function parseXML(xml) {
  20. if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
  21. DOMParser = function() { };
  22. DOMParser.prototype.parseFromString = function( xmlString ) {
  23. var doc = new ActiveXObject('Microsoft.XMLDOM');
  24. doc.async = 'false';
  25. doc.loadXML( xmlString );
  26. return doc;
  27. };
  28. }
  29. try {
  30. var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
  31. if ( $.isXMLDoc( xmlDoc ) ) {
  32. var err = $('parsererror', xmlDoc);
  33. if ( err.length == 1 ) {
  34. throw('Error: ' + $(xmlDoc).text() );
  35. }
  36. } else {
  37. throw('Unable to parse XML');
  38. }
  39. } catch( e ) {
  40. var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
  41. $(document).trigger('xmlParseError', [ msg ]);
  42. return undefined;
  43. }
  44. return xmlDoc;
  45. }
  46. $.extend({
  47. ajax: function(origSettings) {
  48. var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings),
  49. mock = false;
  50. // Iterate over our mock handlers (in registration order) until we find
  51. // one that is willing to intercept the request
  52. $.each(mockHandlers, function(k, v) {
  53. if ( !mockHandlers[k] ) {
  54. return;
  55. }
  56. var m = null;
  57. // If the mock was registered with a function, let the function decide if we
  58. // want to mock this request
  59. if ( $.isFunction(mockHandlers[k]) ) {
  60. m = mockHandlers[k](s);
  61. } else {
  62. m = mockHandlers[k];
  63. // Inspect the URL of the request and check if the mock handler's url
  64. // matches the url for this ajax request
  65. if ( $.isFunction(m.url.test) ) {
  66. // The user provided a regex for the url, test it
  67. if ( !m.url.test( s.url ) ) {
  68. m = null;
  69. }
  70. } else {
  71. // Look for a simple wildcard '*' or a direct URL match
  72. var star = m.url.indexOf('*');
  73. if ( ( m.url != '*' && m.url != s.url && star == -1 ) ||
  74. ( star > -1 && m.url.substr(0, star) != s.url.substr(0, star) ) ) {
  75. // The url we tested did not match the wildcard *
  76. m = null;
  77. }
  78. }
  79. if ( m ) {
  80. // Inspect the data submitted in the request (either POST body or GET query string)
  81. if ( m.data && s.data ) {
  82. var identical = false;
  83. // Deep inspect the identity of the objects
  84. (function ident(mock, live) {
  85. // Test for situations where the data is a querystring (not an object)
  86. if (typeof live === 'string') {
  87. // Querystring may be a regex
  88. identical = $.isFunction( mock.test ) ? mock.test(live) : mock == live;
  89. return identical;
  90. }
  91. $.each(mock, function(k, v) {
  92. if ( live[k] === undefined ) {
  93. identical = false;
  94. return false;
  95. } else {
  96. identical = true;
  97. if ( typeof live[k] == 'object' ) {
  98. return ident(mock[k], live[k]);
  99. } else {
  100. if ( $.isFunction( mock[k].test ) ) {
  101. identical = mock[k].test(live[k]);
  102. } else {
  103. identical = ( mock[k] == live[k] );
  104. }
  105. return identical;
  106. }
  107. }
  108. });
  109. })(m.data, s.data);
  110. // They're not identical, do not mock this request
  111. if ( identical == false ) {
  112. m = null;
  113. }
  114. }
  115. // Inspect the request type
  116. if ( m && m.type && m.type != s.type ) {
  117. // The request type doesn't match (GET vs. POST)
  118. m = null;
  119. }
  120. }
  121. }
  122. if ( m ) {
  123. mock = true;
  124. // Handle console logging
  125. var c = $.extend({}, $.mockjaxSettings, m);
  126. if ( c.log && $.isFunction(c.log) ) {
  127. c.log('MOCK ' + s.type.toUpperCase() + ': ' + s.url, $.extend({}, s));
  128. }
  129. var jsre = /=\?(&|$)/, jsc = (new Date()).getTime();
  130. // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
  131. // because there isn't an easy hook for the cross domain script tag of jsonp
  132. if ( s.dataType === "jsonp" ) {
  133. if ( s.type.toUpperCase() === "GET" ) {
  134. if ( !jsre.test( s.url ) ) {
  135. s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
  136. }
  137. } else if ( !s.data || !jsre.test(s.data) ) {
  138. s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
  139. }
  140. s.dataType = "json";
  141. }
  142. // Build temporary JSONP function
  143. if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
  144. jsonp = s.jsonpCallback || ("jsonp" + jsc++);
  145. // Replace the =? sequence both in the query string and the data
  146. if ( s.data ) {
  147. s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
  148. }
  149. s.url = s.url.replace(jsre, "=" + jsonp + "$1");
  150. // We need to make sure
  151. // that a JSONP style response is executed properly
  152. s.dataType = "script";
  153. // Handle JSONP-style loading
  154. window[ jsonp ] = window[ jsonp ] || function( tmp ) {
  155. data = tmp;
  156. success();
  157. complete();
  158. // Garbage collect
  159. window[ jsonp ] = undefined;
  160. try {
  161. delete window[ jsonp ];
  162. } catch(e) {}
  163. if ( head ) {
  164. head.removeChild( script );
  165. }
  166. };
  167. }
  168. var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
  169. parts = rurl.exec( s.url ),
  170. remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
  171. // Test if we are going to create a script tag (if so, intercept & mock)
  172. if ( s.dataType === "script" && s.type.toUpperCase() === "GET" && remote ) {
  173. // Synthesize the mock request for adding a script tag
  174. var callbackContext = origSettings && origSettings.context || s;
  175. function success() {
  176. // If a local callback was specified, fire it and pass it the data
  177. if ( s.success ) {
  178. s.success.call( callbackContext, ( m.response ? m.response.toString() : m.responseText || ''), status, {} );
  179. }
  180. // Fire the global callback
  181. if ( s.global ) {
  182. trigger( "ajaxSuccess", [{}, s] );
  183. }
  184. }
  185. function complete() {
  186. // Process result
  187. if ( s.complete ) {
  188. s.complete.call( callbackContext, {} , status );
  189. }
  190. // The request was completed
  191. if ( s.global ) {
  192. trigger( "ajaxComplete", [{}, s] );
  193. }
  194. // Handle the global AJAX counter
  195. if ( s.global && ! --jQuery.active ) {
  196. jQuery.event.trigger( "ajaxStop" );
  197. }
  198. }
  199. function trigger(type, args) {
  200. (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
  201. }
  202. if ( m.response && $.isFunction(m.response) ) {
  203. m.response(origSettings);
  204. } else {
  205. $.globalEval(m.responseText);
  206. }
  207. success();
  208. complete();
  209. return false;
  210. }
  211. mock = _ajax.call($, $.extend(true, {}, origSettings, {
  212. // Mock the XHR object
  213. xhr: function() {
  214. // Extend with our default mockjax settings
  215. m = $.extend({}, $.mockjaxSettings, m);
  216. if ( m.contentType ) {
  217. m.headers['content-type'] = m.contentType;
  218. }
  219. // Return our mock xhr object
  220. return {
  221. status: m.status,
  222. readyState: 1,
  223. open: function() { },
  224. send: function() {
  225. // This is a substitute for < 1.4 which lacks $.proxy
  226. var process = (function(that) {
  227. return function() {
  228. return (function() {
  229. // The request has returned
  230. this.status = m.status;
  231. this.readyState = 4;
  232. // We have an executable function, call it to give
  233. // the mock handler a chance to update it's data
  234. if ( $.isFunction(m.response) ) {
  235. m.response(origSettings);
  236. }
  237. // Copy over our mock to our xhr object before passing control back to
  238. // jQuery's onreadystatechange callback
  239. if ( s.dataType == 'json' && ( typeof m.responseText == 'object' ) ) {
  240. this.responseText = JSON.stringify(m.responseText);
  241. } else if ( s.dataType == 'xml' ) {
  242. if ( typeof m.responseXML == 'string' ) {
  243. this.responseXML = parseXML(m.responseXML);
  244. } else {
  245. this.responseXML = m.responseXML;
  246. }
  247. } else {
  248. this.responseText = m.responseText;
  249. }
  250. // jQuery < 1.4 doesn't have onreadystate change for xhr
  251. if ( $.isFunction(this.onreadystatechange) ) {
  252. this.onreadystatechange( m.isTimeout ? 'timeout' : undefined );
  253. }
  254. }).apply(that);
  255. };
  256. })(this);
  257. if ( m.proxy ) {
  258. // We're proxying this request and loading in an external file instead
  259. _ajax({
  260. global: false,
  261. url: m.proxy,
  262. type: m.proxyType,
  263. data: m.data,
  264. dataType: s.dataType,
  265. complete: function(xhr, txt) {
  266. m.responseXML = xhr.responseXML;
  267. m.responseText = xhr.responseText;
  268. this.responseTimer = setTimeout(process, m.responseTime || 0);
  269. }
  270. });
  271. } else {
  272. // type == 'POST' || 'GET' || 'DELETE'
  273. if ( s.async === false ) {
  274. // TODO: Blocking delay
  275. process();
  276. } else {
  277. this.responseTimer = setTimeout(process, m.responseTime || 50);
  278. }
  279. }
  280. },
  281. abort: function() {
  282. clearTimeout(this.responseTimer);
  283. },
  284. setRequestHeader: function() { },
  285. getResponseHeader: function(header) {
  286. // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
  287. if ( m.headers && m.headers[header] ) {
  288. // Return arbitrary headers
  289. return m.headers[header];
  290. } else if ( header.toLowerCase() == 'last-modified' ) {
  291. return m.lastModified || (new Date()).toString();
  292. } else if ( header.toLowerCase() == 'etag' ) {
  293. return m.etag || '';
  294. } else if ( header.toLowerCase() == 'content-type' ) {
  295. return m.contentType || 'text/plain';
  296. }
  297. },
  298. getAllResponseHeaders: function() {
  299. var headers = '';
  300. $.each(m.headers, function(k, v) {
  301. headers += k + ': ' + v + "\n";
  302. });
  303. return headers;
  304. }
  305. };
  306. }
  307. }));
  308. return false;
  309. }
  310. });
  311. // We don't have a mock request, trigger a normal request
  312. if ( !mock ) {
  313. return _ajax.apply($, arguments);
  314. } else {
  315. return mock;
  316. }
  317. }
  318. });
  319. $.mockjaxSettings = {
  320. //url: null,
  321. //type: 'GET',
  322. log: function(msg) {
  323. window['console'] && window.console.log && window.console.log(msg);
  324. },
  325. status: 200,
  326. responseTime: 500,
  327. isTimeout: false,
  328. contentType: 'text/plain',
  329. response: '',
  330. responseText: '',
  331. responseXML: '',
  332. proxy: '',
  333. proxyType: 'GET',
  334. lastModified: null,
  335. etag: '',
  336. headers: {
  337. etag: 'IJF@H#@923uf8023hFO@I#H#',
  338. 'content-type' : 'text/plain'
  339. }
  340. };
  341. $.mockjax = function(settings) {
  342. var i = mockHandlers.length;
  343. mockHandlers[i] = settings;
  344. return i;
  345. };
  346. $.mockjaxClear = function(i) {
  347. if ( arguments.length == 1 ) {
  348. mockHandlers[i] = null;
  349. } else {
  350. mockHandlers = [];
  351. }
  352. };
  353. })(jQuery);