iqjax 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +13 -0
- data/README.md +41 -0
- data/Rakefile +10 -0
- data/iqjax.gemspec +21 -0
- data/iqjax.js +200 -0
- data/lib/iqjax.rb +2 -0
- data/lib/iqjax/engine.rb +5 -0
- data/lib/iqjax/version.rb +4 -0
- data/test/index.html +55 -0
- data/test/lib/jquery.mockjax.js +382 -0
- data/test/lib/qunit.css +226 -0
- data/test/lib/qunit.js +1597 -0
- data/test/lib/run-qunit.js +90 -0
- data/test/test.rb +36 -0
- data/test/tests.js +175 -0
- data/vendor/assets/javascripts/iqjax.js +200 -0
- data/vendor/jquery_ujs.js +367 -0
- metadata +128 -0
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.
|
data/README.md
ADDED
@@ -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 ...
|
data/Rakefile
ADDED
data/iqjax.gemspec
ADDED
@@ -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
|
data/iqjax.js
ADDED
@@ -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));
|
data/lib/iqjax.rb
ADDED
data/lib/iqjax/engine.rb
ADDED
data/test/index.html
ADDED
@@ -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);
|