Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

upgraded jquery.form to 3.15

  • Loading branch information...
commit f3b449023f867cd32593523678091cdcb363ba26 1 parent f4cb113
Kristian Mandrup authored
1,551 vendor/assets/javascripts/jquery.form.js
... ... @@ -1,488 +1,691 @@
1 1 /*!
2 2 * jQuery Form Plugin
3   - * version: 2.52 (07-DEC-2010)
  3 + * version: 3.15 (09-SEP-2012)
4 4 * @requires jQuery v1.3.2 or later
5 5 *
6 6 * Examples and documentation at: http://malsup.com/jquery/form/
  7 + * Project repository: https://github.com/malsup/form
7 8 * Dual licensed under the MIT and GPL licenses:
8   - * http://www.opensource.org/licenses/mit-license.php
9   - * http://www.gnu.org/licenses/gpl.html
  9 + * http://malsup.github.com/mit-license.txt
  10 + * http://malsup.github.com/gpl-license-v2.txt
10 11 */
  12 +/*global ActiveXObject alert */
11 13 ;(function($) {
  14 +"use strict";
12 15
13 16 /*
14   - Usage Note:
15   - -----------
16   - Do not use both ajaxSubmit and ajaxForm on the same form. These
17   - functions are intended to be exclusive. Use ajaxSubmit if you want
18   - to bind your own submit handler to the form. For example,
19   -
20   - $(document).ready(function() {
21   - $('#myForm').bind('submit', function(e) {
22   - e.preventDefault(); // <-- important
23   - $(this).ajaxSubmit({
24   - target: '#output'
25   - });
  17 + Usage Note:
  18 + -----------
  19 + Do not use both ajaxSubmit and ajaxForm on the same form. These
  20 + functions are mutually exclusive. Use ajaxSubmit if you want
  21 + to bind your own submit handler to the form. For example,
  22 +
  23 + $(document).ready(function() {
  24 + $('#myForm').on('submit', function(e) {
  25 + e.preventDefault(); // <-- important
  26 + $(this).ajaxSubmit({
  27 + target: '#output'
  28 + });
  29 + });
26 30 });
27   - });
28 31
29   - Use ajaxForm when you want the plugin to manage all the event binding
30   - for you. For example,
  32 + Use ajaxForm when you want the plugin to manage all the event binding
  33 + for you. For example,
31 34
32   - $(document).ready(function() {
33   - $('#myForm').ajaxForm({
34   - target: '#output'
  35 + $(document).ready(function() {
  36 + $('#myForm').ajaxForm({
  37 + target: '#output'
  38 + });
35 39 });
36   - });
  40 +
  41 + You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
  42 + form does not have to exist when you invoke ajaxForm:
37 43
38   - When using ajaxForm, the ajaxSubmit function will be invoked for you
39   - at the appropriate time.
  44 + $('#myForm').ajaxForm({
  45 + delegation: true,
  46 + target: '#output'
  47 + });
  48 +
  49 + When using ajaxForm, the ajaxSubmit function will be invoked for you
  50 + at the appropriate time.
40 51 */
41 52
42 53 /**
  54 + * Feature detection
  55 + */
  56 +var feature = {};
  57 +feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
  58 +feature.formdata = window.FormData !== undefined;
  59 +
  60 +/**
43 61 * ajaxSubmit() provides a mechanism for immediately submitting
44 62 * an HTML form using AJAX.
45 63 */
46 64 $.fn.ajaxSubmit = function(options) {
47   - // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
48   - if (!this.length) {
49   - log('ajaxSubmit: skipping submit process - no element selected');
50   - return this;
51   - }
52   -
53   - if (typeof options == 'function') {
54   - options = { success: options };
55   - }
56   -
57   - var action = this.attr('action');
58   - var url = (typeof action === 'string') ? $.trim(action) : '';
59   - if (url) {
60   - // clean url (don't include hash vaue)
61   - url = (url.match(/^([^#]+)/)||[])[1];
62   - }
63   - url = url || window.location.href || '';
64   -
65   - options = $.extend(true, {
66   - url: url,
67   - type: this.attr('method') || 'GET',
68   - iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
69   - }, options);
70   -
71   - // hook for manipulating the form data before it is extracted;
72   - // convenient for use with rich editors like tinyMCE or FCKEditor
73   - var veto = {};
74   - this.trigger('form-pre-serialize', [this, options, veto]);
75   - if (veto.veto) {
76   - log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
77   - return this;
78   - }
  65 + /*jshint scripturl:true */
79 66
80   - // provide opportunity to alter form data before it is serialized
81   - if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
82   - log('ajaxSubmit: submit aborted via beforeSerialize callback');
83   - return this;
84   - }
85   -
86   - var n,v,a = this.formToArray(options.semantic);
87   - if (options.data) {
88   - options.extraData = options.data;
89   - for (n in options.data) {
90   - if(options.data[n] instanceof Array) {
91   - for (var k in options.data[n]) {
92   - a.push( { name: n, value: options.data[n][k] } );
93   - }
94   - }
95   - else {
96   - v = options.data[n];
97   - v = $.isFunction(v) ? v() : v; // if value is fn, invoke it
98   - a.push( { name: n, value: v } );
99   - }
100   - }
101   - }
102   -
103   - // give pre-submit callback an opportunity to abort the submit
104   - if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
105   - log('ajaxSubmit: submit aborted via beforeSubmit callback');
106   - return this;
107   - }
  67 + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
  68 + if (!this.length) {
  69 + log('ajaxSubmit: skipping submit process - no element selected');
  70 + return this;
  71 + }
  72 +
  73 + var method, action, url, $form = this;
108 74
109   - // fire vetoable 'validate' event
110   - this.trigger('form-submit-validate', [a, this, options, veto]);
111   - if (veto.veto) {
112   - log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
113   - return this;
114   - }
115   -
116   - var q = $.param(a);
117   -
118   - if (options.type.toUpperCase() == 'GET') {
119   - options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
120   - options.data = null; // data is null for 'get'
121   - }
122   - else {
123   - options.data = q; // data is the query string for 'post'
124   - }
125   -
126   - var $form = this, callbacks = [];
127   - if (options.resetForm) {
128   - callbacks.push(function() { $form.resetForm(); });
129   - }
130   - if (options.clearForm) {
131   - callbacks.push(function() { $form.clearForm(); });
132   - }
133   -
134   - // perform a load on the target only if dataType is not provided
135   - if (!options.dataType && options.target) {
136   - var oldSuccess = options.success || function(){};
137   - callbacks.push(function(data) {
138   - var fn = options.replaceTarget ? 'replaceWith' : 'html';
139   - $(options.target)[fn](data).each(oldSuccess, arguments);
140   - });
141   - }
142   - else if (options.success) {
143   - callbacks.push(options.success);
144   - }
145   -
146   - options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
147   - var context = options.context || options; // jQuery 1.4+ supports scope context
148   - for (var i=0, max=callbacks.length; i < max; i++) {
149   - callbacks[i].apply(context, [data, status, xhr || $form, $form]);
150   - }
151   - };
152   -
153   - // are there files to upload?
154   - var fileInputs = $('input:file', this).length > 0;
155   - var mp = 'multipart/form-data';
156   - var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
157   - var fileAPI = !!(fileInputs && $('input:file', this).get(0).files && window.FormData);
158   - log("fileAPI :" + fileAPI);
159   - var shouldUseFrame = (fileInputs || multipart) && !fileAPI;
  75 + if (typeof options == 'function') {
  76 + options = { success: options };
  77 + }
160 78
161   - // options.iframe allows user to force iframe mode
162   - // 06-NOV-09: now defaulting to iframe mode if file input is detected
163   - if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
164   - // hack to fix Safari hang (thanks to Tim Molendijk for this)
165   - // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
166   - if (options.closeKeepAlive) {
167   - $.get(options.closeKeepAlive, fileUploadIframe);
168   - }
169   - else {
170   - fileUploadIframe();
171   - }
172   - }
173   - else if ((fileInputs || multipart) && fileAPI) {
174   - options.progress = options.progress || $.noop();
175   - fileUploadXhr();
  79 + method = this.attr('method');
  80 + action = this.attr('action');
  81 + url = (typeof action === 'string') ? $.trim(action) : '';
  82 + url = url || window.location.href || '';
  83 + if (url) {
  84 + // clean url (don't include hash vaue)
  85 + url = (url.match(/^([^#]+)/)||[])[1];
  86 + }
  87 +
  88 + options = $.extend(true, {
  89 + url: url,
  90 + success: $.ajaxSettings.success,
  91 + type: method || 'GET',
  92 + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
  93 + }, options);
  94 +
  95 + // hook for manipulating the form data before it is extracted;
  96 + // convenient for use with rich editors like tinyMCE or FCKEditor
  97 + var veto = {};
  98 + this.trigger('form-pre-serialize', [this, options, veto]);
  99 + if (veto.veto) {
  100 + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
  101 + return this;
  102 + }
  103 +
  104 + // provide opportunity to alter form data before it is serialized
  105 + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
  106 + log('ajaxSubmit: submit aborted via beforeSerialize callback');
  107 + return this;
  108 + }
  109 +
  110 + var traditional = options.traditional;
  111 + if ( traditional === undefined ) {
  112 + traditional = $.ajaxSettings.traditional;
  113 + }
  114 +
  115 + var elements = [];
  116 + var qx, a = this.formToArray(options.semantic, elements);
  117 + if (options.data) {
  118 + options.extraData = options.data;
  119 + qx = $.param(options.data, traditional);
  120 + }
  121 +
  122 + // give pre-submit callback an opportunity to abort the submit
  123 + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
  124 + log('ajaxSubmit: submit aborted via beforeSubmit callback');
  125 + return this;
  126 + }
  127 +
  128 + // fire vetoable 'validate' event
  129 + this.trigger('form-submit-validate', [a, this, options, veto]);
  130 + if (veto.veto) {
  131 + log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
  132 + return this;
  133 + }
  134 +
  135 + var q = $.param(a, traditional);
  136 + if (qx) {
  137 + q = ( q ? (q + '&' + qx) : qx );
  138 + }
  139 + if (options.type.toUpperCase() == 'GET') {
  140 + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
  141 + options.data = null; // data is null for 'get'
176 142 }
177 143 else {
178   - $.ajax(options);
179   - }
180   -
181   - // fire 'notify' event
182   - this.trigger('form-submit-notify', [this, options]);
183   - return this;
184   -
185   -
186   - // private function for handling file uploads in iframe (hat tip to YAHOO!)
187   - function fileUploadIframe() {
188   - var form = $form[0];
189   -
190   - if ($(':input[name=submit],:input[id=submit]', form).length) {
191   - // if there is an input with a name or id of 'submit' then we won't be
192   - // able to invoke the submit fn on the form (at least not x-browser)
193   - alert('Error: Form elements must not have name or id of "submit".');
194   - return;
195   - }
196   -
197   - var s = $.extend(true, {}, $.ajaxSettings, options);
198   - s.context = s.context || s;
199   - var id = 'jqFormIO' + (new Date().getTime()), fn = '_'+id;
200   - window[fn] = function() {
201   - var f = $io.data('form-plugin-onload');
202   - if (f) {
203   - f();
204   - window[fn] = undefined;
205   - try { delete window[fn]; } catch(e){}
206   - }
207   - }
208   - var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ s.iframeSrc +'" onload="window[\'_\'+this.id]()" />');
209   - var io = $io[0];
210   -
211   - $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
212   -
213   - var xhr = { // mock object
214   - aborted: 0,
215   - responseText: null,
216   - responseXML: null,
217   - status: 0,
218   - statusText: 'n/a',
219   - getAllResponseHeaders: function() {},
220   - getResponseHeader: function() {},
221   - setRequestHeader: function() {},
222   - abort: function() {
223   - this.aborted = 1;
224   - $io.attr('src', s.iframeSrc); // abort op in progress
225   - }
226   - };
  144 + options.data = q; // data is the query string for 'post'
  145 + }
  146 +
  147 + var callbacks = [];
  148 + if (options.resetForm) {
  149 + callbacks.push(function() { $form.resetForm(); });
  150 + }
  151 + if (options.clearForm) {
  152 + callbacks.push(function() { $form.clearForm(options.includeHidden); });
  153 + }
227 154
228   - var g = s.global;
229   - // trigger ajax global events so that activity/block indicators work like normal
230   - if (g && ! $.active++) {
231   - $.event.trigger("ajaxStart");
232   - }
233   - if (g) {
234   - $.event.trigger("ajaxSend", [xhr, s]);
235   - }
236   -
237   - if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
238   - if (s.global) {
239   - $.active--;
240   - }
241   - return;
242   - }
243   - if (xhr.aborted) {
244   - return;
245   - }
246   -
247   - var cbInvoked = false;
248   - var timedOut = 0;
249   -
250   - // add submitting element to data if we know it
251   - var sub = form.clk;
252   - if (sub) {
253   - var n = sub.name;
254   - if (n && !sub.disabled) {
255   - s.extraData = s.extraData || {};
256   - s.extraData[n] = sub.value;
257   - if (sub.type == "image") {
258   - s.extraData[n+'.x'] = form.clk_x;
259   - s.extraData[n+'.y'] = form.clk_y;
260   - }
261   - }
262   - }
263   -
264   - // take a breath so that pending repaints get some cpu time before the upload starts
265   - function doSubmit() {
266   - // make sure form attrs are set
267   - var t = $form.attr('target'), a = $form.attr('action');
268   -
269   - // update form attrs in IE friendly way
270   - form.setAttribute('target',id);
271   - if (form.getAttribute('method') != 'POST') {
272   - form.setAttribute('method', 'POST');
273   - }
274   - if (form.getAttribute('action') != s.url) {
275   - form.setAttribute('action', s.url);
276   - }
277   -
278   - // ie borks in some cases when setting encoding
279   - if (! s.skipEncodingOverride) {
280   - $form.attr({
281   - encoding: 'multipart/form-data',
282   - enctype: 'multipart/form-data'
  155 + // perform a load on the target only if dataType is not provided
  156 + if (!options.dataType && options.target) {
  157 + var oldSuccess = options.success || function(){};
  158 + callbacks.push(function(data) {
  159 + var fn = options.replaceTarget ? 'replaceWith' : 'html';
  160 + $(options.target)[fn](data).each(oldSuccess, arguments);
283 161 });
284   - }
285   -
286   - // support timout
287   - if (s.timeout) {
288   - setTimeout(function() { timedOut = true; cb(); }, s.timeout);
289   - }
290   -
291   - // add "extra" data to form if provided in options
292   - var extraInputs = [];
293   - try {
294   - if (s.extraData) {
295   - for (var n in s.extraData) {
296   - extraInputs.push(
297   - $('<input type="hidden" name="'+n+'" value="'+s.extraData[n]+'" />')
298   - .appendTo(form)[0]);
299   - }
  162 + }
  163 + else if (options.success) {
  164 + callbacks.push(options.success);
  165 + }
  166 +
  167 + options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
  168 + var context = options.context || this ; // jQuery 1.4+ supports scope context
  169 + for (var i=0, max=callbacks.length; i < max; i++) {
  170 + callbacks[i].apply(context, [data, status, xhr || $form, $form]);
300 171 }
  172 + };
301 173
302   - // add iframe to doc and submit the form
303   - $io.appendTo('body');
304   - $io.data('form-plugin-onload', cb);
305   - form.submit();
306   - }
307   - finally {
308   - // reset attrs and remove "extra" input elements
309   - form.setAttribute('action',a);
310   - if(t) {
311   - form.setAttribute('target', t);
312   - } else {
313   - $form.removeAttr('target');
  174 + // are there files to upload?
  175 + var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
  176 + var hasFileInputs = fileInputs.length > 0;
  177 + var mp = 'multipart/form-data';
  178 + var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
  179 +
  180 + var fileAPI = feature.fileapi && feature.formdata;
  181 + log("fileAPI :" + fileAPI);
  182 + var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
  183 +
  184 + // options.iframe allows user to force iframe mode
  185 + // 06-NOV-09: now defaulting to iframe mode if file input is detected
  186 + if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
  187 + // hack to fix Safari hang (thanks to Tim Molendijk for this)
  188 + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
  189 + if (options.closeKeepAlive) {
  190 + $.get(options.closeKeepAlive, function() {
  191 + fileUploadIframe(a);
  192 + });
314 193 }
315   - $(extraInputs).remove();
316   - }
  194 + else {
  195 + fileUploadIframe(a);
  196 + }
317 197 }
318   -
319   - if (s.forceSync) {
320   - doSubmit();
  198 + else if ((hasFileInputs || multipart) && fileAPI) {
  199 + fileUploadXhr(a);
321 200 }
322 201 else {
323   - setTimeout(doSubmit, 10); // this lets dom updates render
  202 + $.ajax(options);
324 203 }
325 204
326   - var data, doc, domCheckCount = 50;
  205 + // clear element array
  206 + for (var k=0; k < elements.length; k++)
  207 + elements[k] = null;
327 208
328   - function cb() {
329   - if (cbInvoked) {
330   - return;
331   - }
332   -
333   - $io.removeData('form-plugin-onload');
334   -
335   - var ok = true;
336   - try {
337   - if (timedOut) {
338   - throw 'timeout';
339   - }
340   - // extract the server response from the iframe
341   - doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
342   -
343   - var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
344   - log('isXml='+isXml);
345   - if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
346   - if (--domCheckCount) {
347   - // in some browsers (Opera) the iframe DOM is not always traversable when
348   - // the onload callback fires, so we loop a bit to accommodate
349   - log('requeing onLoad callback, DOM not available');
350   - setTimeout(cb, 250);
351   - return;
352   - }
353   - // let this fall through because server response could be an empty document
354   - //log('Could not access iframe DOM after mutiple tries.');
355   - //throw 'DOMException: not available';
356   - }
357   -
358   - //log('response detected');
359   - cbInvoked = true;
360   - xhr.responseText = doc.documentElement ? doc.documentElement.innerHTML : null;
361   - xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
362   - xhr.getResponseHeader = function(header){
363   - var headers = {'content-type': s.dataType};
364   - return headers[header];
  209 + // fire 'notify' event
  210 + this.trigger('form-submit-notify', [this, options]);
  211 + return this;
  212 +
  213 + // utility fn for deep serialization
  214 + function deepSerialize(extraData){
  215 + var serialized = $.param(extraData).split('&');
  216 + var len = serialized.length;
  217 + var result = {};
  218 + var i, part;
  219 + for (i=0; i < len; i++) {
  220 + part = serialized[i].split('=');
  221 + result[decodeURIComponent(part[0])] = decodeURIComponent(part[1]);
  222 + }
  223 + return result;
  224 + }
  225 +
  226 + // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
  227 + function fileUploadXhr(a) {
  228 + var formdata = new FormData();
  229 +
  230 + for (var i=0; i < a.length; i++) {
  231 + formdata.append(a[i].name, a[i].value);
  232 + }
  233 +
  234 + if (options.extraData) {
  235 + var serializedData = deepSerialize(options.extraData);
  236 + for (var p in serializedData)
  237 + if (serializedData.hasOwnProperty(p))
  238 + formdata.append(p, serializedData[p]);
  239 + }
  240 +
  241 + options.data = null;
  242 +
  243 + var s = $.extend(true, {}, $.ajaxSettings, options, {
  244 + contentType: false,
  245 + processData: false,
  246 + cache: false,
  247 + type: 'POST'
  248 + });
  249 +
  250 + if (options.uploadProgress) {
  251 + // workaround because jqXHR does not expose upload property
  252 + s.xhr = function() {
  253 + var xhr = jQuery.ajaxSettings.xhr();
  254 + if (xhr.upload) {
  255 + xhr.upload.onprogress = function(event) {
  256 + var percent = 0;
  257 + var position = event.loaded || event.position; /*event.position is deprecated*/
  258 + var total = event.total;
  259 + if (event.lengthComputable) {
  260 + percent = Math.ceil(position / total * 100);
  261 + }
  262 + options.uploadProgress(event, position, total, percent);
  263 + };
  264 + }
  265 + return xhr;
  266 + };
  267 + }
  268 +
  269 + s.data = null;
  270 + var beforeSend = s.beforeSend;
  271 + s.beforeSend = function(xhr, o) {
  272 + o.data = formdata;
  273 + if(beforeSend)
  274 + beforeSend.call(this, xhr, o);
365 275 };
  276 + $.ajax(s);
  277 + }
366 278
367   - var scr = /(json|script)/.test(s.dataType);
368   - if (scr || s.textarea) {
369   - // see if user embedded response in textarea
370   - var ta = doc.getElementsByTagName('textarea')[0];
371   - if (ta) {
372   - xhr.responseText = ta.value;
373   - }
374   - else if (scr) {
375   - // account for browsers injecting pre around json response
376   - var pre = doc.getElementsByTagName('pre')[0];
377   - var b = doc.getElementsByTagName('body')[0];
378   - if (pre) {
379   - xhr.responseText = pre.textContent;
  279 + // private function for handling file uploads (hat tip to YAHOO!)
  280 + function fileUploadIframe(a) {
  281 + var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
  282 + var useProp = !!$.fn.prop;
  283 +
  284 + if ($(':input[name=submit],:input[id=submit]', form).length) {
  285 + // if there is an input with a name or id of 'submit' then we won't be
  286 + // able to invoke the submit fn on the form (at least not x-browser)
  287 + alert('Error: Form elements must not have name or id of "submit".');
  288 + return;
  289 + }
  290 +
  291 + if (a) {
  292 + // ensure that every serialized input is still enabled
  293 + for (i=0; i < elements.length; i++) {
  294 + el = $(elements[i]);
  295 + if ( useProp )
  296 + el.prop('disabled', false);
  297 + else
  298 + el.removeAttr('disabled');
380 299 }
381   - else if (b) {
382   - xhr.responseText = b.innerHTML;
  300 + }
  301 +
  302 + s = $.extend(true, {}, $.ajaxSettings, options);
  303 + s.context = s.context || s;
  304 + id = 'jqFormIO' + (new Date().getTime());
  305 + if (s.iframeTarget) {
  306 + $io = $(s.iframeTarget);
  307 + n = $io.attr('name');
  308 + if (!n)
  309 + $io.attr('name', id);
  310 + else
  311 + id = n;
  312 + }
  313 + else {
  314 + $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
  315 + $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
  316 + }
  317 + io = $io[0];
  318 +
  319 +
  320 + xhr = { // mock object
  321 + aborted: 0,
  322 + responseText: null,
  323 + responseXML: null,
  324 + status: 0,
  325 + statusText: 'n/a',
  326 + getAllResponseHeaders: function() {},
  327 + getResponseHeader: function() {},
  328 + setRequestHeader: function() {},
  329 + abort: function(status) {
  330 + var e = (status === 'timeout' ? 'timeout' : 'aborted');
  331 + log('aborting upload... ' + e);
  332 + this.aborted = 1;
  333 + // #214
  334 + if (io.contentWindow.document.execCommand) {
  335 + try { // #214
  336 + io.contentWindow.document.execCommand('Stop');
  337 + } catch(ignore) {}
  338 + }
  339 + $io.attr('src', s.iframeSrc); // abort op in progress
  340 + xhr.error = e;
  341 + if (s.error)
  342 + s.error.call(s.context, xhr, e, status);
  343 + if (g)
  344 + $.event.trigger("ajaxError", [xhr, s, e]);
  345 + if (s.complete)
  346 + s.complete.call(s.context, xhr, e);
383 347 }
384   - }
  348 + };
  349 +
  350 + g = s.global;
  351 + // trigger ajax global events so that activity/block indicators work like normal
  352 + if (g && 0 === $.active++) {
  353 + $.event.trigger("ajaxStart");
385 354 }
386   - else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
387   - xhr.responseXML = toXml(xhr.responseText);
388   - }
389   - data = $.httpData(xhr, s.dataType);
390   - }
391   - catch(e){
392   - log('error caught:',e);
393   - ok = false;
394   - xhr.error = e;
395   - $.handleError(s, xhr, 'error', e);
396   - }
397   -
398   - if (xhr.aborted) {
399   - log('upload aborted');
400   - ok = false;
401   - }
402   -
403   - // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
404   - if (ok) {
405   - s.success.call(s.context, data, 'success', xhr);
406 355 if (g) {
407   - $.event.trigger("ajaxSuccess", [xhr, s]);
408   - }
409   - }
410   - if (g) {
411   - $.event.trigger("ajaxComplete", [xhr, s]);
412   - }
413   - if (g && ! --$.active) {
414   - $.event.trigger("ajaxStop");
415   - }
416   - if (s.complete) {
417   - s.complete.call(s.context, xhr, ok ? 'success' : 'error');
418   - }
419   -
420   - // clean up
421   - setTimeout(function() {
422   - $io.removeData('form-plugin-onload');
423   - $io.remove();
424   - xhr.responseXML = null;
425   - }, 100);
426   - }
427   -
428   - function toXml(s, doc) {
429   - if (window.ActiveXObject) {
430   - doc = new ActiveXObject('Microsoft.XMLDOM');
431   - doc.async = 'false';
432   - doc.loadXML(s);
433   - }
434   - else {
435   - doc = (new DOMParser()).parseFromString(s, 'text/xml');
436   - }
437   - return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
438   - }
439   - }
440   -
441   - // private function for handling file uploads with xmlhttprequest (hat type to jquery-sexypost)
442   - function fileUploadXhr() {
443   - // this function will POST the contents of the selected form via XmlHttpRequest.
444   -
445   - var data = new FormData();
446   - $("input:text, input:hidden, input:password, textarea", $form).each(function(){
447   - data.append($(this).attr("name"), $(this).val());
448   - });
  356 + $.event.trigger("ajaxSend", [xhr, s]);
  357 + }
449 358
450   - $("input:file", $form).each(function(){
451   - var files = this.files;
452   - for (i=0; i<files.length; i++) data.append($(this).attr("name"), files[i]);
453   - });
  359 + if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
  360 + if (s.global) {
  361 + $.active--;
  362 + }
  363 + return;
  364 + }
  365 + if (xhr.aborted) {
  366 + return;
  367 + }
454 368
455   - $("select option:selected", $form).each(function(){
456   - data.append($(this).parent().attr("name"), $(this).val());
457   - });
  369 + // add submitting element to data if we know it
  370 + sub = form.clk;
  371 + if (sub) {
  372 + n = sub.name;
  373 + if (n && !sub.disabled) {
  374 + s.extraData = s.extraData || {};
  375 + s.extraData[n] = sub.value;
  376 + if (sub.type == "image") {
  377 + s.extraData[n+'.x'] = form.clk_x;
  378 + s.extraData[n+'.y'] = form.clk_y;
  379 + }
  380 + }
  381 + }
  382 +
  383 + var CLIENT_TIMEOUT_ABORT = 1;
  384 + var SERVER_ABORT = 2;
458 385
459   - $("input:checked", $form).each(function(){
460   - data.append($(this).attr("name"), $(this).val());
461   - });
462   - options.data = null;
463   - var originalBeforeSend = options.beforeSend;
464   - _options = options;
465   - options.beforeSend = function(xhr, options) { // et toc !
466   - options.data = data;
467   - xhr.upload.onprogress = function(event) {
468   - _options.progress(event.position, event.total);
469   - }
470   - /**
471   - * You can use https://github.com/francois2metz/html5-formdata for a fake FormData object
472   - * Only work with Firefox 3.6
473   - */
474   - if (data.fake) {
475   - xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary="+ data.boundary);
476   - // with fake FormData object, we must use sendAsBinary
477   - xhr.send = function(data) {
478   - xhr.sendAsBinary(data.toString());
  386 + function getDoc(frame) {
  387 + var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
  388 + return doc;
  389 + }
  390 +
  391 + // Rails CSRF hack (thanks to Yvan Barthelemy)
  392 + var csrf_token = $('meta[name=csrf-token]').attr('content');
  393 + var csrf_param = $('meta[name=csrf-param]').attr('content');
  394 + if (csrf_param && csrf_token) {
  395 + s.extraData = s.extraData || {};
  396 + s.extraData[csrf_param] = csrf_token;
  397 + }
  398 +
  399 + // take a breath so that pending repaints get some cpu time before the upload starts
  400 + function doSubmit() {
  401 + // make sure form attrs are set
  402 + var t = $form.attr('target'), a = $form.attr('action');
  403 +
  404 + // update form attrs in IE friendly way
  405 + form.setAttribute('target',id);
  406 + if (!method) {
  407 + form.setAttribute('method', 'POST');
  408 + }
  409 + if (a != s.url) {
  410 + form.setAttribute('action', s.url);
  411 + }
  412 +
  413 + // ie borks in some cases when setting encoding
  414 + if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
  415 + $form.attr({
  416 + encoding: 'multipart/form-data',
  417 + enctype: 'multipart/form-data'
  418 + });
  419 + }
  420 +
  421 + // support timout
  422 + if (s.timeout) {
  423 + timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
  424 + }
  425 +
  426 + // look for server aborts
  427 + function checkState() {
  428 + try {
  429 + var state = getDoc(io).readyState;
  430 + log('state = ' + state);
  431 + if (state && state.toLowerCase() == 'uninitialized')
  432 + setTimeout(checkState,50);
  433 + }
  434 + catch(e) {
  435 + log('Server abort: ' , e, ' (', e.name, ')');
  436 + cb(SERVER_ABORT);
  437 + if (timeoutHandle)
  438 + clearTimeout(timeoutHandle);
  439 + timeoutHandle = undefined;
  440 + }
  441 + }
  442 +
  443 + // add "extra" data to form if provided in options
  444 + var extraInputs = [];
  445 + try {
  446 + if (s.extraData) {
  447 + for (var n in s.extraData) {
  448 + if (s.extraData.hasOwnProperty(n)) {
  449 + // if using the $.param format that allows for multiple values with the same name
  450 + if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
  451 + extraInputs.push(
  452 + $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value)
  453 + .appendTo(form)[0]);
  454 + } else {
  455 + extraInputs.push(
  456 + $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
  457 + .appendTo(form)[0]);
  458 + }
  459 + }
  460 + }
  461 + }
  462 +
  463 + if (!s.iframeTarget) {
  464 + // add iframe to doc and submit the form
  465 + $io.appendTo('body');
  466 + if (io.attachEvent)
  467 + io.attachEvent('onload', cb);
  468 + else
  469 + io.addEventListener('load', cb, false);
479 470 }
  471 + setTimeout(checkState,15);
  472 + form.submit();
  473 + }
  474 + finally {
  475 + // reset attrs and remove "extra" input elements
  476 + form.setAttribute('action',a);
  477 + if(t) {
  478 + form.setAttribute('target', t);
  479 + } else {
  480 + $form.removeAttr('target');
  481 + }
  482 + $(extraInputs).remove();
480 483 }
481   - if (originalBeforeSend) originalBeforeSend(xhr, options);
482 484 }
483   - $.ajax(options);
484   - }
485 485
  486 + if (s.forceSync) {
  487 + doSubmit();
  488 + }
  489 + else {
  490 + setTimeout(doSubmit, 10); // this lets dom updates render
  491 + }
  492 +
  493 + var data, doc, domCheckCount = 50, callbackProcessed;
  494 +
  495 + function cb(e) {
  496 + if (xhr.aborted || callbackProcessed) {
  497 + return;
  498 + }
  499 + try {
  500 + doc = getDoc(io);
  501 + }
  502 + catch(ex) {
  503 + log('cannot access response document: ', ex);
  504 + e = SERVER_ABORT;
  505 + }
  506 + if (e === CLIENT_TIMEOUT_ABORT && xhr) {
  507 + xhr.abort('timeout');
  508 + return;
  509 + }
  510 + else if (e == SERVER_ABORT && xhr) {
  511 + xhr.abort('server abort');
  512 + return;
  513 + }
  514 +
  515 + if (!doc || doc.location.href == s.iframeSrc) {
  516 + // response not received yet
  517 + if (!timedOut)
  518 + return;
  519 + }
  520 + if (io.detachEvent)
  521 + io.detachEvent('onload', cb);
  522 + else
  523 + io.removeEventListener('load', cb, false);
  524 +
  525 + var status = 'success', errMsg;
  526 + try {
  527 + if (timedOut) {
  528 + throw 'timeout';
  529 + }
  530 +
  531 + var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
  532 + log('isXml='+isXml);
  533 + if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
  534 + if (--domCheckCount) {
  535 + // in some browsers (Opera) the iframe DOM is not always traversable when
  536 + // the onload callback fires, so we loop a bit to accommodate
  537 + log('requeing onLoad callback, DOM not available');
  538 + setTimeout(cb, 250);
  539 + return;
  540 + }
  541 + // let this fall through because server response could be an empty document
  542 + //log('Could not access iframe DOM after mutiple tries.');
  543 + //throw 'DOMException: not available';
  544 + }
  545 +
  546 + //log('response detected');
  547 + var docRoot = doc.body ? doc.body : doc.documentElement;
  548 + xhr.responseText = docRoot ? docRoot.innerHTML : null;
  549 + xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
  550 + if (isXml)
  551 + s.dataType = 'xml';
  552 + xhr.getResponseHeader = function(header){
  553 + var headers = {'content-type': s.dataType};
  554 + return headers[header];
  555 + };
  556 + // support for XHR 'status' & 'statusText' emulation :
  557 + if (docRoot) {
  558 + xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
  559 + xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
  560 + }
  561 +
  562 + var dt = (s.dataType || '').toLowerCase();
  563 + var scr = /(json|script|text)/.test(dt);
  564 + if (scr || s.textarea) {
  565 + // see if user embedded response in textarea
  566 + var ta = doc.getElementsByTagName('textarea')[0];
  567 + if (ta) {
  568 + xhr.responseText = ta.value;
  569 + // support for XHR 'status' & 'statusText' emulation :
  570 + xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
  571 + xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
  572 + }
  573 + else if (scr) {
  574 + // account for browsers injecting pre around json response
  575 + var pre = doc.getElementsByTagName('pre')[0];
  576 + var b = doc.getElementsByTagName('body')[0];
  577 + if (pre) {
  578 + xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
  579 + }
  580 + else if (b) {
  581 + xhr.responseText = b.textContent ? b.textContent : b.innerText;
  582 + }
  583 + }
  584 + }
  585 + else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
  586 + xhr.responseXML = toXml(xhr.responseText);
  587 + }
  588 +
  589 + try {
  590 + data = httpData(xhr, dt, s);
  591 + }
  592 + catch (e) {
  593 + status = 'parsererror';
  594 + xhr.error = errMsg = (e || status);
  595 + }
  596 + }
  597 + catch (e) {
  598 + log('error caught: ',e);
  599 + status = 'error';
  600 + xhr.error = errMsg = (e || status);
  601 + }
  602 +
  603 + if (xhr.aborted) {
  604 + log('upload aborted');
  605 + status = null;
  606 + }
  607 +
  608 + if (xhr.status) { // we've set xhr.status
  609 + status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
  610 + }
  611 +
  612 + // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
  613 + if (status === 'success') {
  614 + if (s.success)
  615 + s.success.call(s.context, data, 'success', xhr);
  616 + if (g)
  617 + $.event.trigger("ajaxSuccess", [xhr, s]);
  618 + }
  619 + else if (status) {
  620 + if (errMsg === undefined)
  621 + errMsg = xhr.statusText;
  622 + if (s.error)
  623 + s.error.call(s.context, xhr, status, errMsg);
  624 + if (g)
  625 + $.event.trigger("ajaxError", [xhr, s, errMsg]);
  626 + }
  627 +
  628 + if (g)
  629 + $.event.trigger("ajaxComplete", [xhr, s]);
  630 +
  631 + if (g && ! --$.active) {
  632 + $.event.trigger("ajaxStop");
  633 + }
  634 +
  635 + if (s.complete)
  636 + s.complete.call(s.context, xhr, status);
  637 +
  638 + callbackProcessed = true;
  639 + if (s.timeout)
  640 + clearTimeout(timeoutHandle);
  641 +
  642 + // clean up
  643 + setTimeout(function() {
  644 + if (!s.iframeTarget)
  645 + $io.remove();
  646 + xhr.responseXML = null;
  647 + }, 100);
  648 + }
  649 +
  650 + var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
  651 + if (window.ActiveXObject) {
  652 + doc = new ActiveXObject('Microsoft.XMLDOM');
  653 + doc.async = 'false';
  654 + doc.loadXML(s);
  655 + }
  656 + else {
  657 + doc = (new DOMParser()).parseFromString(s, 'text/xml');
  658 + }
  659 + return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
  660 + };
  661 + var parseJSON = $.parseJSON || function(s) {
  662 + /*jslint evil:true */
  663 + return window['eval']('(' + s + ')');
  664 + };
  665 +
  666 + var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
  667 +
  668 + var ct = xhr.getResponseHeader('content-type') || '',
  669 + xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
  670 + data = xml ? xhr.responseXML : xhr.responseText;
  671 +
  672 + if (xml && data.documentElement.nodeName === 'parsererror') {
  673 + if ($.error)
  674 + $.error('parsererror');
  675 + }
  676 + if (s && s.dataFilter) {
  677 + data = s.dataFilter(data, type);
  678 + }
  679 + if (typeof data === 'string') {
  680 + if (type === 'json' || !type && ct.indexOf('json') >= 0) {
  681 + data = parseJSON(data);