iqjax 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2011 innoQ Deutschland GmbH
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,41 @@
1
+ iQjax
2
+ =====
3
+
4
+ This is an unobtrusive JavaScript component for doing Ajax in conformity
5
+ with the [ROCA-Style](http://roca-style.org). It enables your web application
6
+ to do Ajax without bigger impacts to your server side.
7
+
8
+ An iQjax enabled DOM node loads the target of annotated links it contains into a
9
+ specific container DOM node. This is done asynchronously via Ajax. If the
10
+ content contains a form, the form handling (especially error handling) will be
11
+ automatically done in the container too. After a form was successfully submitted,
12
+ iQjax provides methods to update the iQjax container without reloading the whole
13
+ page.
14
+
15
+ E.g. given a iQjax enabled list of person names with links pointing to a
16
+ form to edit the respective person name. Clicking on such a link will
17
+ load the form to a given container DOM node. After submitting the form
18
+ with a changed name, the user would expect the list of names to be updated.
19
+ iQjax can do this by searching the new list entry in the response of
20
+ the form POST. Then it replaces the old node with the new one.
21
+
22
+ Documentation
23
+ -------------
24
+
25
+ We distinguish six API parts of ROCA-Style JavaScript components:
26
+
27
+ # The socket HTML markup the component initializes upon,
28
+ # the constructor method together with it's parameters,
29
+ # methods callable upon the component,
30
+ # events triggered by the component,
31
+ # HTML markup the component generates and
32
+ # Ajax-Requests the component fires.
33
+
34
+ ### Socket markup
35
+
36
+ iQjax must be initialized on a DOM node with a `data-iqjax` attribute set.
37
+ The attribute must contain a selector for the container the Ajax-response
38
+ should be put in.
39
+
40
+
41
+ ... TO BE DONE ...
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'rake/testtask'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.test_files = FileList['test/**/*.rb']
8
+ end
9
+
10
+ task :default => [:test]
@@ -0,0 +1,21 @@
1
+ require File.expand_path('../lib/iqjax/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "iqjax"
5
+ s.version = Iqjax::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ["FND", "Till Schulte-Coerne"]
8
+ s.email = ["", "till.schulte-coerne@innoq.com"]
9
+ s.homepage = "http://github.com/innoq/iqjax"
10
+ s.summary = "iQjax - a JS library for real unobtrusive Ajax"
11
+ s.description = s.summary
12
+ s.extra_rdoc_files = ['README.md', 'LICENSE']
13
+
14
+ s.add_dependency "bundler"
15
+ s.add_dependency "railties", ">= 3.2.0", "< 5.0"
16
+
17
+ s.files = %w(LICENSE README.md Rakefile iqjax.gemspec iqjax.js) + Dir.glob("{lib,vendor,test}/**/*")
18
+ s.test_files = Dir.glob("{test}/**/*")
19
+
20
+ s.require_path = 'lib'
21
+ end
@@ -0,0 +1,200 @@
1
+ /*jslint vars: true, unparam: true, browser: true, white: true */
2
+ /*global jQuery */
3
+
4
+ /* Copyright 2012 innoQ Deutschland GmbH
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+ */
18
+
19
+ // dynamic loading of content
20
+ // inspired by https://github.com/defunkt/jquery-iqjax - though minus the
21
+ // History API and based on Rails's data-remote functionality and error handling
22
+ (function($) {
23
+
24
+ "use strict";
25
+
26
+ var iqjax_uri, requestMethod;
27
+
28
+ var IQjax = function(context, target) {
29
+ // NB: context and target must not be descendants of each other
30
+ this.context = context;
31
+ this.target = target;
32
+ this.indicator = $(".indicator", target);
33
+ if(!this.indicator.length) {
34
+ this.indicator = $('<div class="indicator hidden"></div>');
35
+ }
36
+
37
+ // NB: redefining `this` in event handlers (via `proxy`) is dangerously
38
+ // misleading, but avoids non-standard function signatures for event
39
+ // handlers - plus for instance methods, it's actually more intuitive
40
+
41
+ // cancel buttons
42
+ var uri = document.location.toString().split("#")[0]; // XXX: use as selector makes for brittle heuristic?
43
+ this.target.on("click", 'a[href="' + uri + '"]', $.proxy(this.onCancel, this));
44
+
45
+ var selector = "a[data-remote], form[data-remote]";
46
+ var handlers = {
47
+ "ajax:beforeSend": $.proxy(this.beforeSend, this),
48
+ "ajax:success": $.proxy(this.onSuccess, this),
49
+ "ajax:error": $.proxy(this.onError, this)
50
+ };
51
+ this.context.add(this.target).on(handlers, selector);
52
+
53
+ // dirty state: protect against accidental dismissal
54
+ var self = this;
55
+ this.target.on("change", "input, textarea, select", function(ev) {
56
+ self.dirty = true;
57
+ });
58
+ };
59
+ $.extend(IQjax.prototype, {
60
+ onCancel: function(ev) {
61
+ if(!this.checkDirty(ev)) {
62
+ this.reset();
63
+ }
64
+ ev.preventDefault();
65
+ },
66
+ beforeSend: function(ev, xhr, settings) {
67
+ if(this.checkDirty(ev)) {
68
+ return false;
69
+ }
70
+
71
+ var contextAction = $.contains(this.context[0], ev.currentTarget); // TODO: rename -- XXX: hacky?
72
+ if(contextAction) {
73
+ this.reset();
74
+ }
75
+ $(ev.currentTarget).addClass("active");
76
+ this.target.prepend(this.indicator);
77
+ this.indicator.show();
78
+ settings.url = iqjax_uri(settings.url);
79
+ this.target.children().not(this.indicator).css("opacity", 0.5);
80
+ },
81
+ onSuccess: function(ev, data, status, xhr) {
82
+ if(!ev.currentTarget.parentNode) {
83
+ // FIXME: it's not clear under what circumstances this occurs;
84
+ // apparently, for reasons yet unknown, this event is erroneously
85
+ // triggered twice (and not all such duplicate events are
86
+ // intercepted by this hack, e.g. DELETE operations)
87
+ return;
88
+ }
89
+
90
+ var targetAction = $.contains(this.target[0], ev.currentTarget), // TODO: rename -- XXX: hacky?
91
+ el = $(ev.currentTarget),
92
+ reqMethod = "GET",
93
+ origin;
94
+
95
+ if(el.is("form")) {
96
+ if(targetAction) {
97
+ this.onUpdate.call(this, data, status, xhr); // TODO: should trigger event
98
+ return;
99
+ } else {
100
+ reqMethod = requestMethod(el);
101
+ origin = el.closest(".iqjax-entity"); // TODO: document
102
+ }
103
+ }
104
+
105
+ if(origin && reqMethod === "DELETE") {
106
+ this.indicator.hide();
107
+ origin.slideUp($.proxy(origin.remove, origin));
108
+ } else {
109
+ this.display(data);
110
+ }
111
+ },
112
+ onError: function(ev, xhr, error, exc) {
113
+ var cType = xhr.getResponseHeader("Content-Type"),
114
+ isHTML = cType ? cType.match(/\btext\/html\b/) : false;
115
+ this.display(xhr.responseText || error, !isHTML);
116
+ },
117
+ onUpdate: function(data, status, xhr) {
118
+ var src = xhr.getResponseHeader("X-IQJAX"), // TODO: document
119
+ item = $("<div />").html(data).find("#" + src),
120
+ origin = $(".active", this.context),
121
+ container = origin.closest("[data-iqjax-collection]"), // TODO: document
122
+ form = $("form.active", this.target),
123
+ reqMethod = requestMethod(form);
124
+
125
+ this.target.empty();
126
+
127
+ container = container.jquery ? container : $(container);
128
+ // account for nested update targets -- XXX: hacky!?
129
+ if(reqMethod === "PUT" && origin.closest(".iqjax-entity")[0] === container[0]) {
130
+ container = container.parent().closest("[data-iqjax-collection]");
131
+ }
132
+ container = $(container.data("iqjax-collection"));
133
+
134
+ if(reqMethod === "GET") { // multi-step forms
135
+ this.display(data);
136
+ } else {
137
+ this.reset();
138
+ }
139
+
140
+ var el = $("#" + src, self.context);
141
+ if(el.length) {
142
+ el.replaceWith(item);
143
+ } else { // new
144
+ container.append(item);
145
+ }
146
+
147
+ item.addClass("success").removeClass("success", "glacial");
148
+ this.context.trigger("iqjax:update", { item: item, doc: data });
149
+ },
150
+ reset: function() {
151
+ this.target.empty();
152
+ this.dirty = false;
153
+ $(".active", this.context).removeClass("active");
154
+ },
155
+ display: function(txt, plain) {
156
+ this.indicator.hide();
157
+ this.target[plain ? "text" : "html"](txt).prepend(this.indicator);
158
+ // intercept interactions to prevent full page reload
159
+ $("form", this.target).attr("data-remote", true);
160
+ this.context.trigger("iqjax:content", { iqjax: this });
161
+ },
162
+ // dirty state: protect against accidental dismissal
163
+ dirtyMsg: "Es gibt ungespeicherte Änderungen - fortfahren (Änderungen werden verworfen)?", // TODO: rephrase
164
+ checkDirty: function(ev) {
165
+ var isSubmit = $(ev.currentTarget).is("form");
166
+ if(this.dirty && !isSubmit) {
167
+ if(confirm(this.dirtyMsg)) {
168
+ this.dirty = false;
169
+ } else {
170
+ return true;
171
+ }
172
+ }
173
+ }
174
+ });
175
+
176
+ // hack to prevent cache confusion (browser caches should distinguish between
177
+ // iQjax and non-iQjax requests) - it'd be more elegant to modify the jqXHR's
178
+ // `data` property here, but it appears that's being overridden by jQuery later
179
+ iqjax_uri = function(uri) {
180
+ return uri + (uri.indexOf("?") === -1 ? "?" : "&") + "_iqjax=1";
181
+ };
182
+
183
+ requestMethod = function(form) {
184
+ var m = $("input[name=_method]", form).val() || form.attr("method") || "GET";
185
+ return m.toUpperCase();
186
+ };
187
+
188
+ // uses dynamic in-page loading for child elements matching `a[data-remote]`
189
+ // options.target is the DOM element within which contents are to be displayed
190
+ // (defaults to selector contained in context element's `data-iqjax` attribute)
191
+ $.fn.iqjax = function(options) {
192
+ options = options || {};
193
+ return this.each(function(i, node) {
194
+ var context = $(this),
195
+ target = $(options.target || context.data("iqjax")); // TODO: document
196
+ new IQjax(context, target);
197
+ });
198
+ };
199
+
200
+ }(jQuery));
@@ -0,0 +1,2 @@
1
+ require 'iqjax/version'
2
+ require 'iqjax/engine'
@@ -0,0 +1,5 @@
1
+ module Iqjax
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
5
+
@@ -0,0 +1,4 @@
1
+ module Iqjax
2
+ VERSION = "0.1.0"
3
+ end
4
+
@@ -0,0 +1,55 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>iQjax QUnit Test Suite</title>
7
+ <link rel="stylesheet" href="lib/qunit.css">
8
+ </head>
9
+
10
+ <body>
11
+ <h1 id="qunit-header">QUnit Test Suite</h1>
12
+ <h2 id="qunit-banner"></h2>
13
+ <div id="qunit-testrunner-toolbar"></div>
14
+ <h2 id="qunit-userAgent"></h2>
15
+ <ol id="qunit-tests"></ol>
16
+ <div id="qunit-fixture">
17
+
18
+ <div id="basic" data-iqjax-collection="#other-item-list">
19
+
20
+ <div data-iqjax-collection="#item-list">
21
+ <p><a href="/items/new" id="new" data-remote="true">New</a></p>
22
+ <ul id="item-list">
23
+ <li id="item1">
24
+ <a href="/items/1/edit" data-remote="true">Edit</a>
25
+ </li>
26
+ <li id="item2">
27
+ <a href="/items/2/edit" data-remote="true">Edit</a>
28
+ </li>
29
+ <li id="dummy-item-pointing-to-item3">
30
+ <a href="/items/3/edit" data-remote="true">Edit</a>
31
+ </li>
32
+ </ul>
33
+ </div>
34
+
35
+ <ul id="other-item-list">
36
+ <li id="item3">Change me!</li>
37
+ </ul>
38
+
39
+ </div>
40
+
41
+ <div id="my-container1"></div>
42
+
43
+ </div>
44
+
45
+ <script src="lib/qunit.js"></script>
46
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
47
+ <script src="lib/jquery.mockjax.js"></script>
48
+
49
+ <script src="../vendor/jquery_ujs.js"></script>
50
+
51
+ <script src="../iqjax.js"></script>
52
+ <script src="tests.js"></script>
53
+ </body>
54
+
55
+ </html>
@@ -0,0 +1,382 @@
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
+
20
+ function parseXML(xml) {
21
+ if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
22
+ DOMParser = function() { };
23
+ DOMParser.prototype.parseFromString = function( xmlString ) {
24
+ var doc = new ActiveXObject('Microsoft.XMLDOM');
25
+ doc.async = 'false';
26
+ doc.loadXML( xmlString );
27
+ return doc;
28
+ };
29
+ }
30
+
31
+ try {
32
+ var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
33
+ if ( $.isXMLDoc( xmlDoc ) ) {
34
+ var err = $('parsererror', xmlDoc);
35
+ if ( err.length == 1 ) {
36
+ throw('Error: ' + $(xmlDoc).text() );
37
+ }
38
+ } else {
39
+ throw('Unable to parse XML');
40
+ }
41
+ } catch( e ) {
42
+ var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
43
+ $(document).trigger('xmlParseError', [ msg ]);
44
+ return undefined;
45
+ }
46
+ return xmlDoc;
47
+ }
48
+
49
+ $.extend({
50
+ ajax: function(origSettings) {
51
+ var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings),
52
+ mock = false;
53
+ // Iterate over our mock handlers (in registration order) until we find
54
+ // one that is willing to intercept the request
55
+ $.each(mockHandlers, function(k, v) {
56
+ if ( !mockHandlers[k] ) {
57
+ return;
58
+ }
59
+ var m = null;
60
+ // If the mock was registered with a function, let the function decide if we
61
+ // want to mock this request
62
+ if ( $.isFunction(mockHandlers[k]) ) {
63
+ m = mockHandlers[k](s);
64
+ } else {
65
+ m = mockHandlers[k];
66
+ // Inspect the URL of the request and check if the mock handler's url
67
+ // matches the url for this ajax request
68
+ if ( $.isFunction(m.url.test) ) {
69
+ // The user provided a regex for the url, test it
70
+ if ( !m.url.test( s.url ) ) {
71
+ m = null;
72
+ }
73
+ } else {
74
+ // Look for a simple wildcard '*' or a direct URL match
75
+ var star = m.url.indexOf('*');
76
+ if ( ( m.url != '*' && m.url != s.url && star == -1 ) ||
77
+ ( star > -1 && m.url.substr(0, star) != s.url.substr(0, star) ) ) {
78
+ // The url we tested did not match the wildcard *
79
+ m = null;
80
+ }
81
+ }
82
+ if ( m ) {
83
+ // Inspect the data submitted in the request (either POST body or GET query string)
84
+ if ( m.data && s.data ) {
85
+ var identical = false;
86
+ // Deep inspect the identity of the objects
87
+ (function ident(mock, live) {
88
+ // Test for situations where the data is a querystring (not an object)
89
+ if (typeof live === 'string') {
90
+ // Querystring may be a regex
91
+ identical = $.isFunction( mock.test ) ? mock.test(live) : mock == live;
92
+ return identical;
93
+ }
94
+ $.each(mock, function(k, v) {
95
+ if ( live[k] === undefined ) {
96
+ identical = false;
97
+ return false;
98
+ } else {
99
+ identical = true;
100
+ if ( typeof live[k] == 'object' ) {
101
+ return ident(mock[k], live[k]);
102
+ } else {
103
+ if ( $.isFunction( mock[k].test ) ) {
104
+ identical = mock[k].test(live[k]);
105
+ } else {
106
+ identical = ( mock[k] == live[k] );
107
+ }
108
+ return identical;
109
+ }
110
+ }
111
+ });
112
+ })(m.data, s.data);
113
+ // They're not identical, do not mock this request
114
+ if ( identical == false ) {
115
+ m = null;
116
+ }
117
+ }
118
+ // Inspect the request type
119
+ if ( m && m.type && m.type != s.type ) {
120
+ // The request type doesn't match (GET vs. POST)
121
+ m = null;
122
+ }
123
+ }
124
+ }
125
+ if ( m ) {
126
+ mock = true;
127
+
128
+ // Handle console logging
129
+ var c = $.extend({}, $.mockjaxSettings, m);
130
+ if ( c.log && $.isFunction(c.log) ) {
131
+ c.log('MOCK ' + s.type.toUpperCase() + ': ' + s.url, $.extend({}, s));
132
+ }
133
+
134
+ var jsre = /=\?(&|$)/, jsc = (new Date()).getTime();
135
+
136
+ // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
137
+ // because there isn't an easy hook for the cross domain script tag of jsonp
138
+ if ( s.dataType === "jsonp" ) {
139
+ if ( s.type.toUpperCase() === "GET" ) {
140
+ if ( !jsre.test( s.url ) ) {
141
+ s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
142
+ }
143
+ } else if ( !s.data || !jsre.test(s.data) ) {
144
+ s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
145
+ }
146
+ s.dataType = "json";
147
+ }
148
+
149
+ // Build temporary JSONP function
150
+ if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
151
+ jsonp = s.jsonpCallback || ("jsonp" + jsc++);
152
+
153
+ // Replace the =? sequence both in the query string and the data
154
+ if ( s.data ) {
155
+ s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
156
+ }
157
+
158
+ s.url = s.url.replace(jsre, "=" + jsonp + "$1");
159
+
160
+ // We need to make sure
161
+ // that a JSONP style response is executed properly
162
+ s.dataType = "script";
163
+
164
+ // Handle JSONP-style loading
165
+ window[ jsonp ] = window[ jsonp ] || function( tmp ) {
166
+ data = tmp;
167
+ success();
168
+ complete();
169
+ // Garbage collect
170
+ window[ jsonp ] = undefined;
171
+
172
+ try {
173
+ delete window[ jsonp ];
174
+ } catch(e) {}
175
+
176
+ if ( head ) {
177
+ head.removeChild( script );
178
+ }
179
+ };
180
+ }
181
+
182
+ var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
183
+ parts = rurl.exec( s.url ),
184
+ remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
185
+
186
+ // Test if we are going to create a script tag (if so, intercept & mock)
187
+ if ( s.dataType === "script" && s.type.toUpperCase() === "GET" && remote ) {
188
+ // Synthesize the mock request for adding a script tag
189
+ var callbackContext = origSettings && origSettings.context || s;
190
+
191
+ function success() {
192
+ // If a local callback was specified, fire it and pass it the data
193
+ if ( s.success ) {
194
+ s.success.call( callbackContext, ( m.response ? m.response.toString() : m.responseText || ''), status, {} );
195
+ }
196
+
197
+ // Fire the global callback
198
+ if ( s.global ) {
199
+ trigger( "ajaxSuccess", [{}, s] );
200
+ }
201
+ }
202
+
203
+ function complete() {
204
+ // Process result
205
+ if ( s.complete ) {
206
+ s.complete.call( callbackContext, {} , status );
207
+ }
208
+
209
+ // The request was completed
210
+ if ( s.global ) {
211
+ trigger( "ajaxComplete", [{}, s] );
212
+ }
213
+
214
+ // Handle the global AJAX counter
215
+ if ( s.global && ! --jQuery.active ) {
216
+ jQuery.event.trigger( "ajaxStop" );
217
+ }
218
+ }
219
+
220
+ function trigger(type, args) {
221
+ (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
222
+ }
223
+
224
+ if ( m.response && $.isFunction(m.response) ) {
225
+ m.response(origSettings);
226
+ } else {
227
+ $.globalEval(m.responseText);
228
+ }
229
+ success();
230
+ complete();
231
+ return false;
232
+ }
233
+ mock = _ajax.call($, $.extend(true, {}, origSettings, {
234
+ // Mock the XHR object
235
+ xhr: function() {
236
+ // Extend with our default mockjax settings
237
+ m = $.extend({}, $.mockjaxSettings, m);
238
+
239
+ if ( m.contentType ) {
240
+ m.headers['content-type'] = m.contentType;
241
+ }
242
+
243
+ // Return our mock xhr object
244
+ return {
245
+ status: m.status,
246
+ readyState: 1,
247
+ open: function() { },
248
+ send: function() {
249
+ // This is a substitute for < 1.4 which lacks $.proxy
250
+ var process = (function(that) {
251
+ return function() {
252
+ return (function() {
253
+ // The request has returned
254
+ this.status = m.status;
255
+ this.readyState = 4;
256
+
257
+ // We have an executable function, call it to give
258
+ // the mock handler a chance to update it's data
259
+ if ( $.isFunction(m.response) ) {
260
+ m.response(origSettings);
261
+ }
262
+ // Copy over our mock to our xhr object before passing control back to
263
+ // jQuery's onreadystatechange callback
264
+ if ( s.dataType == 'json' && ( typeof m.responseText == 'object' ) ) {
265
+ this.responseText = JSON.stringify(m.responseText);
266
+ } else if ( s.dataType == 'xml' ) {
267
+ if ( typeof m.responseXML == 'string' ) {
268
+ this.responseXML = parseXML(m.responseXML);
269
+ } else {
270
+ this.responseXML = m.responseXML;
271
+ }
272
+ } else {
273
+ this.responseText = m.responseText;
274
+ }
275
+ // jQuery < 1.4 doesn't have onreadystate change for xhr
276
+ if ( $.isFunction(this.onreadystatechange) ) {
277
+ this.onreadystatechange( m.isTimeout ? 'timeout' : undefined );
278
+ }
279
+ }).apply(that);
280
+ };
281
+ })(this);
282
+
283
+ if ( m.proxy ) {
284
+ // We're proxying this request and loading in an external file instead
285
+ _ajax({
286
+ global: false,
287
+ url: m.proxy,
288
+ type: m.proxyType,
289
+ data: m.data,
290
+ dataType: s.dataType,
291
+ complete: function(xhr, txt) {
292
+ m.responseXML = xhr.responseXML;
293
+ m.responseText = xhr.responseText;
294
+ this.responseTimer = setTimeout(process, m.responseTime || 0);
295
+ }
296
+ });
297
+ } else {
298
+ // type == 'POST' || 'GET' || 'DELETE'
299
+ if ( s.async === false ) {
300
+ // TODO: Blocking delay
301
+ process();
302
+ } else {
303
+ this.responseTimer = setTimeout(process, m.responseTime || 50);
304
+ }
305
+ }
306
+ },
307
+ abort: function() {
308
+ clearTimeout(this.responseTimer);
309
+ },
310
+ setRequestHeader: function() { },
311
+ getResponseHeader: function(header) {
312
+ // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
313
+ if ( m.headers && m.headers[header] ) {
314
+ // Return arbitrary headers
315
+ return m.headers[header];
316
+ } else if ( header.toLowerCase() == 'last-modified' ) {
317
+ return m.lastModified || (new Date()).toString();
318
+ } else if ( header.toLowerCase() == 'etag' ) {
319
+ return m.etag || '';
320
+ } else if ( header.toLowerCase() == 'content-type' ) {
321
+ return m.contentType || 'text/plain';
322
+ }
323
+ },
324
+ getAllResponseHeaders: function() {
325
+ var headers = '';
326
+ $.each(m.headers, function(k, v) {
327
+ headers += k + ': ' + v + "\n";
328
+ });
329
+ return headers;
330
+ }
331
+ };
332
+ }
333
+ }));
334
+ return false;
335
+ }
336
+ });
337
+ // We don't have a mock request, trigger a normal request
338
+ if ( !mock ) {
339
+ return _ajax.apply($, arguments);
340
+ } else {
341
+ return mock;
342
+ }
343
+ }
344
+ });
345
+
346
+ $.mockjaxSettings = {
347
+ //url: null,
348
+ //type: 'GET',
349
+ log: function(msg) {
350
+ window['console'] && window.console.log && window.console.log(msg);
351
+ },
352
+ status: 200,
353
+ responseTime: 500,
354
+ isTimeout: false,
355
+ contentType: 'text/plain',
356
+ response: '',
357
+ responseText: '',
358
+ responseXML: '',
359
+ proxy: '',
360
+ proxyType: 'GET',
361
+
362
+ lastModified: null,
363
+ etag: '',
364
+ headers: {
365
+ etag: 'IJF@H#@923uf8023hFO@I#H#',
366
+ 'content-type' : 'text/plain'
367
+ }
368
+ };
369
+
370
+ $.mockjax = function(settings) {
371
+ var i = mockHandlers.length;
372
+ mockHandlers[i] = settings;
373
+ return i;
374
+ };
375
+ $.mockjaxClear = function(i) {
376
+ if ( arguments.length == 1 ) {
377
+ mockHandlers[i] = null;
378
+ } else {
379
+ mockHandlers = [];
380
+ }
381
+ };
382
+ })(jQuery);