d89f0ec8 |
1 | // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt |
2 | |
1c1dd91e |
3 | /*! |
4 | * jQuery Form Plugin |
5 | * version: 3.25.0-2013.01.18 |
6 | * @requires jQuery v1.5 or later |
7 | * |
8 | * Examples and documentation at: http://malsup.com/jquery/form/ |
9 | * Project repository: https://github.com/malsup/form |
10 | * Dual licensed under the MIT and GPL licenses: |
11 | * http://malsup.github.com/mit-license.txt |
12 | * http://malsup.github.com/gpl-license-v2.txt |
13 | */ |
14 | /*global ActiveXObject alert */ |
15 | ;(function($) { |
16 | "use strict"; |
17 | |
18 | /* |
19 | Usage Note: |
20 | ----------- |
21 | Do not use both ajaxSubmit and ajaxForm on the same form. These |
22 | functions are mutually exclusive. Use ajaxSubmit if you want |
23 | to bind your own submit handler to the form. For example, |
24 | |
25 | $(document).ready(function() { |
26 | $('#myForm').on('submit', function(e) { |
27 | e.preventDefault(); // <-- important |
28 | $(this).ajaxSubmit({ |
29 | target: '#output' |
30 | }); |
31 | }); |
32 | }); |
33 | |
34 | Use ajaxForm when you want the plugin to manage all the event binding |
35 | for you. For example, |
36 | |
37 | $(document).ready(function() { |
38 | $('#myForm').ajaxForm({ |
39 | target: '#output' |
40 | }); |
41 | }); |
42 | |
43 | You can also use ajaxForm with delegation (requires jQuery v1.7+), so the |
44 | form does not have to exist when you invoke ajaxForm: |
45 | |
46 | $('#myForm').ajaxForm({ |
47 | delegation: true, |
48 | target: '#output' |
49 | }); |
50 | |
51 | When using ajaxForm, the ajaxSubmit function will be invoked for you |
52 | at the appropriate time. |
53 | */ |
54 | |
55 | /** |
56 | * Feature detection |
57 | */ |
58 | var feature = {}; |
59 | feature.fileapi = $("<input type='file'/>").get(0).files !== undefined; |
60 | feature.formdata = window.FormData !== undefined; |
61 | |
62 | /** |
63 | * ajaxSubmit() provides a mechanism for immediately submitting |
64 | * an HTML form using AJAX. |
65 | */ |
66 | $.fn.ajaxSubmit = function(options) { |
67 | /*jshint scripturl:true */ |
68 | |
69 | // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) |
70 | if (!this.length) { |
71 | log('ajaxSubmit: skipping submit process - no element selected'); |
72 | return this; |
73 | } |
74 | |
75 | var method, action, url, $form = this; |
76 | |
77 | if (typeof options == 'function') { |
78 | options = { success: options }; |
79 | } |
80 | |
81 | method = this.attr('method'); |
82 | action = this.attr('action'); |
83 | url = (typeof action === 'string') ? $.trim(action) : ''; |
84 | url = url || window.location.href || ''; |
85 | if (url) { |
86 | // clean url (don't include hash vaue) |
87 | url = (url.match(/^([^#]+)/)||[])[1]; |
88 | } |
89 | |
90 | options = $.extend(true, { |
91 | url: url, |
92 | success: $.ajaxSettings.success, |
93 | type: method || 'GET', |
94 | iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' |
95 | }, options); |
96 | |
97 | // hook for manipulating the form data before it is extracted; |
98 | // convenient for use with rich editors like tinyMCE or FCKEditor |
99 | var veto = {}; |
100 | this.trigger('form-pre-serialize', [this, options, veto]); |
101 | if (veto.veto) { |
102 | log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); |
103 | return this; |
104 | } |
105 | |
106 | // provide opportunity to alter form data before it is serialized |
107 | if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { |
108 | log('ajaxSubmit: submit aborted via beforeSerialize callback'); |
109 | return this; |
110 | } |
111 | |
112 | var traditional = options.traditional; |
113 | if ( traditional === undefined ) { |
114 | traditional = $.ajaxSettings.traditional; |
115 | } |
116 | |
117 | var elements = []; |
118 | var qx, a = this.formToArray(options.semantic, elements); |
119 | if (options.data) { |
120 | options.extraData = options.data; |
121 | qx = $.param(options.data, traditional); |
122 | } |
123 | |
124 | // give pre-submit callback an opportunity to abort the submit |
125 | if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { |
126 | log('ajaxSubmit: submit aborted via beforeSubmit callback'); |
127 | return this; |
128 | } |
129 | |
130 | // fire vetoable 'validate' event |
131 | this.trigger('form-submit-validate', [a, this, options, veto]); |
132 | if (veto.veto) { |
133 | log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); |
134 | return this; |
135 | } |
136 | |
137 | var q = $.param(a, traditional); |
138 | if (qx) { |
139 | q = ( q ? (q + '&' + qx) : qx ); |
140 | } |
141 | if (options.type.toUpperCase() == 'GET') { |
142 | options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; |
143 | options.data = null; // data is null for 'get' |
144 | } |
145 | else { |
146 | options.data = q; // data is the query string for 'post' |
147 | } |
148 | |
149 | var callbacks = []; |
150 | if (options.resetForm) { |
151 | callbacks.push(function() { $form.resetForm(); }); |
152 | } |
153 | if (options.clearForm) { |
154 | callbacks.push(function() { $form.clearForm(options.includeHidden); }); |
155 | } |
156 | |
157 | // perform a load on the target only if dataType is not provided |
158 | if (!options.dataType && options.target) { |
159 | var oldSuccess = options.success || function(){}; |
160 | callbacks.push(function(data) { |
161 | var fn = options.replaceTarget ? 'replaceWith' : 'html'; |
162 | $(options.target)[fn](data).each(oldSuccess, arguments); |
163 | }); |
164 | } |
165 | else if (options.success) { |
166 | callbacks.push(options.success); |
167 | } |
168 | |
169 | options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg |
170 | var context = options.context || this ; // jQuery 1.4+ supports scope context |
171 | for (var i=0, max=callbacks.length; i < max; i++) { |
172 | callbacks[i].apply(context, [data, status, xhr || $form, $form]); |
173 | } |
174 | }; |
175 | |
176 | // are there files to upload? |
177 | |
178 | // [value] (issue #113), also see comment: |
179 | // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219 |
180 | var fileInputs = $('input[type=file]:enabled[value!=""]', this); |
181 | |
182 | var hasFileInputs = fileInputs.length > 0; |
183 | var mp = 'multipart/form-data'; |
184 | var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); |
185 | |
186 | var fileAPI = feature.fileapi && feature.formdata; |
187 | log("fileAPI :" + fileAPI); |
188 | var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI; |
189 | |
190 | var jqxhr; |
191 | |
192 | // options.iframe allows user to force iframe mode |
193 | // 06-NOV-09: now defaulting to iframe mode if file input is detected |
194 | if (options.iframe !== false && (options.iframe || shouldUseFrame)) { |
195 | // hack to fix Safari hang (thanks to Tim Molendijk for this) |
196 | // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d |
197 | if (options.closeKeepAlive) { |
198 | $.get(options.closeKeepAlive, function() { |
199 | jqxhr = fileUploadIframe(a); |
200 | }); |
201 | } |
202 | else { |
203 | jqxhr = fileUploadIframe(a); |
204 | } |
205 | } |
206 | else if ((hasFileInputs || multipart) && fileAPI) { |
207 | jqxhr = fileUploadXhr(a); |
208 | } |
209 | else { |
210 | jqxhr = $.ajax(options); |
211 | } |
212 | |
213 | $form.removeData('jqxhr').data('jqxhr', jqxhr); |
214 | |
215 | // clear element array |
216 | for (var k=0; k < elements.length; k++) |
217 | elements[k] = null; |
218 | |
219 | // fire 'notify' event |
220 | this.trigger('form-submit-notify', [this, options]); |
221 | return this; |
222 | |
223 | // utility fn for deep serialization |
224 | function deepSerialize(extraData){ |
225 | var serialized = $.param(extraData).split('&'); |
226 | var len = serialized.length; |
227 | var result = {}; |
228 | var i, part; |
229 | for (i=0; i < len; i++) { |
230 | // #252; undo param space replacement |
231 | serialized[i] = serialized[i].replace(/\+/g,' '); |
232 | part = serialized[i].split('='); |
233 | result[decodeURIComponent(part[0])] = decodeURIComponent(part[1]); |
234 | } |
235 | return result; |
236 | } |
237 | |
238 | // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz) |
239 | function fileUploadXhr(a) { |
240 | var formdata = new FormData(); |
241 | |
242 | for (var i=0; i < a.length; i++) { |
243 | formdata.append(a[i].name, a[i].value); |
244 | } |
245 | |
246 | if (options.extraData) { |
247 | var serializedData = deepSerialize(options.extraData); |
248 | for (var p in serializedData) |
249 | if (serializedData.hasOwnProperty(p)) |
250 | formdata.append(p, serializedData[p]); |
251 | } |
252 | |
253 | options.data = null; |
254 | |
255 | var s = $.extend(true, {}, $.ajaxSettings, options, { |
256 | contentType: false, |
257 | processData: false, |
258 | cache: false, |
259 | type: method || 'POST' |
260 | }); |
261 | |
262 | if (options.uploadProgress) { |
263 | // workaround because jqXHR does not expose upload property |
264 | s.xhr = function() { |
265 | var xhr = jQuery.ajaxSettings.xhr(); |
266 | if (xhr.upload) { |
267 | xhr.upload.onprogress = function(event) { |
268 | var percent = 0; |
269 | var position = event.loaded || event.position; /*event.position is deprecated*/ |
270 | var total = event.total; |
271 | if (event.lengthComputable) { |
272 | percent = Math.ceil(position / total * 100); |
273 | } |
274 | options.uploadProgress(event, position, total, percent); |
275 | }; |
276 | } |
277 | return xhr; |
278 | }; |
279 | } |
280 | |
281 | s.data = null; |
282 | var beforeSend = s.beforeSend; |
283 | s.beforeSend = function(xhr, o) { |
284 | o.data = formdata; |
285 | if(beforeSend) |
286 | beforeSend.call(this, xhr, o); |
287 | }; |
288 | return $.ajax(s); |
289 | } |
290 | |
291 | // private function for handling file uploads (hat tip to YAHOO!) |
292 | function fileUploadIframe(a) { |
293 | var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle; |
294 | var useProp = !!$.fn.prop; |
295 | var deferred = $.Deferred(); |
296 | |
297 | if ($('[name=submit],[id=submit]', form).length) { |
298 | // if there is an input with a name or id of 'submit' then we won't be |
299 | // able to invoke the submit fn on the form (at least not x-browser) |
300 | alert('Error: Form elements must not have name or id of "submit".'); |
301 | deferred.reject(); |
302 | return deferred; |
303 | } |
304 | |
305 | if (a) { |
306 | // ensure that every serialized input is still enabled |
307 | for (i=0; i < elements.length; i++) { |
308 | el = $(elements[i]); |
309 | if ( useProp ) |
310 | el.prop('disabled', false); |
311 | else |
312 | el.removeAttr('disabled'); |
313 | } |
314 | } |
315 | |
316 | s = $.extend(true, {}, $.ajaxSettings, options); |
317 | s.context = s.context || s; |
318 | id = 'jqFormIO' + (new Date().getTime()); |
319 | if (s.iframeTarget) { |
320 | $io = $(s.iframeTarget); |
321 | n = $io.attr('name'); |
322 | if (!n) |
323 | $io.attr('name', id); |
324 | else |
325 | id = n; |
326 | } |
327 | else { |
328 | $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />'); |
329 | $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' }); |
330 | } |
331 | io = $io[0]; |
332 | |
333 | |
334 | xhr = { // mock object |
335 | aborted: 0, |
336 | responseText: null, |
337 | responseXML: null, |
338 | status: 0, |
339 | statusText: 'n/a', |
340 | getAllResponseHeaders: function() {}, |
341 | getResponseHeader: function() {}, |
342 | setRequestHeader: function() {}, |
343 | abort: function(status) { |
344 | var e = (status === 'timeout' ? 'timeout' : 'aborted'); |
345 | log('aborting upload... ' + e); |
346 | this.aborted = 1; |
347 | |
348 | try { // #214, #257 |
349 | if (io.contentWindow.document.execCommand) { |
350 | io.contentWindow.document.execCommand('Stop'); |
351 | } |
352 | } |
353 | catch(ignore) {} |
354 | |
355 | $io.attr('src', s.iframeSrc); // abort op in progress |
356 | xhr.error = e; |
357 | if (s.error) |
358 | s.error.call(s.context, xhr, e, status); |
359 | if (g) |
360 | $.event.trigger("ajaxError", [xhr, s, e]); |
361 | if (s.complete) |
362 | s.complete.call(s.context, xhr, e); |
363 | } |
364 | }; |
365 | |
366 | g = s.global; |
367 | // trigger ajax global events so that activity/block indicators work like normal |
368 | if (g && 0 === $.active++) { |
369 | $.event.trigger("ajaxStart"); |
370 | } |
371 | if (g) { |
372 | $.event.trigger("ajaxSend", [xhr, s]); |
373 | } |
374 | |
375 | if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) { |
376 | if (s.global) { |
377 | $.active--; |
378 | } |
379 | deferred.reject(); |
380 | return deferred; |
381 | } |
382 | if (xhr.aborted) { |
383 | deferred.reject(); |
384 | return deferred; |
385 | } |
386 | |
387 | // add submitting element to data if we know it |
388 | sub = form.clk; |
389 | if (sub) { |
390 | n = sub.name; |
391 | if (n && !sub.disabled) { |
392 | s.extraData = s.extraData || {}; |
393 | s.extraData[n] = sub.value; |
394 | if (sub.type == "image") { |
395 | s.extraData[n+'.x'] = form.clk_x; |
396 | s.extraData[n+'.y'] = form.clk_y; |
397 | } |
398 | } |
399 | } |
400 | |
401 | var CLIENT_TIMEOUT_ABORT = 1; |
402 | var SERVER_ABORT = 2; |
403 | |
404 | function getDoc(frame) { |
405 | var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document; |
406 | return doc; |
407 | } |
408 | |
409 | // Rails CSRF hack (thanks to Yvan Barthelemy) |
410 | var csrf_token = $('meta[name=csrf-token]').attr('content'); |
411 | var csrf_param = $('meta[name=csrf-param]').attr('content'); |
412 | if (csrf_param && csrf_token) { |
413 | s.extraData = s.extraData || {}; |
414 | s.extraData[csrf_param] = csrf_token; |
415 | } |
416 | |
417 | // take a breath so that pending repaints get some cpu time before the upload starts |
418 | function doSubmit() { |
419 | // make sure form attrs are set |
420 | var t = $form.attr('target'), a = $form.attr('action'); |
421 | |
422 | // update form attrs in IE friendly way |
423 | form.setAttribute('target',id); |
424 | if (!method) { |
425 | form.setAttribute('method', 'POST'); |
426 | } |
427 | if (a != s.url) { |
428 | form.setAttribute('action', s.url); |
429 | } |
430 | |
431 | // ie borks in some cases when setting encoding |
432 | if (! s.skipEncodingOverride && (!method || /post/i.test(method))) { |
433 | $form.attr({ |
434 | encoding: 'multipart/form-data', |
435 | enctype: 'multipart/form-data' |
436 | }); |
437 | } |
438 | |
439 | // support timout |
440 | if (s.timeout) { |
441 | timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout); |
442 | } |
443 | |
444 | // look for server aborts |
445 | function checkState() { |
446 | try { |
447 | var state = getDoc(io).readyState; |
448 | log('state = ' + state); |
449 | if (state && state.toLowerCase() == 'uninitialized') |
450 | setTimeout(checkState,50); |
451 | } |
452 | catch(e) { |
453 | log('Server abort: ' , e, ' (', e.name, ')'); |
454 | cb(SERVER_ABORT); |
455 | if (timeoutHandle) |
456 | clearTimeout(timeoutHandle); |
457 | timeoutHandle = undefined; |
458 | } |
459 | } |
460 | |
461 | // add "extra" data to form if provided in options |
462 | var extraInputs = []; |
463 | try { |
464 | if (s.extraData) { |
465 | for (var n in s.extraData) { |
466 | if (s.extraData.hasOwnProperty(n)) { |
467 | // if using the $.param format that allows for multiple values with the same name |
468 | if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) { |
469 | extraInputs.push( |
470 | $('<input type="hidden" name="'+s.extraData[n].name+'">').val(s.extraData[n].value) |
471 | .appendTo(form)[0]); |
472 | } else { |
473 | extraInputs.push( |
474 | $('<input type="hidden" name="'+n+'">').val(s.extraData[n]) |
475 | .appendTo(form)[0]); |
476 | } |
477 | } |
478 | } |
479 | } |
480 | |
481 | if (!s.iframeTarget) { |
482 | // add iframe to doc and submit the form |
483 | $io.appendTo('body'); |
484 | if (io.attachEvent) |
485 | io.attachEvent('onload', cb); |
486 | else |
487 | io.addEventListener('load', cb, false); |
488 | } |
489 | setTimeout(checkState,15); |
490 | form.submit(); |
491 | } |
492 | finally { |
493 | // reset attrs and remove "extra" input elements |
494 | form.setAttribute('action',a); |
495 | if(t) { |
496 | form.setAttribute('target', t); |
497 | } else { |
498 | $form.removeAttr('target'); |
499 | } |
500 | $(extraInputs).remove(); |
501 | } |
502 | } |
503 | |
504 | if (s.forceSync) { |
505 | doSubmit(); |
506 | } |
507 | else { |
508 | setTimeout(doSubmit, 10); // this lets dom updates render |
509 | } |
510 | |
511 | var data, doc, domCheckCount = 50, callbackProcessed; |
512 | |
513 | function cb(e) { |
514 | if (xhr.aborted || callbackProcessed) { |
515 | return; |
516 | } |
517 | try { |
518 | doc = getDoc(io); |
519 | } |
520 | catch(ex) { |
521 | log('cannot access response document: ', ex); |
522 | e = SERVER_ABORT; |
523 | } |
524 | if (e === CLIENT_TIMEOUT_ABORT && xhr) { |
525 | xhr.abort('timeout'); |
526 | deferred.reject(xhr, 'timeout'); |
527 | return; |
528 | } |
529 | else if (e == SERVER_ABORT && xhr) { |
530 | xhr.abort('server abort'); |
531 | deferred.reject(xhr, 'error', 'server abort'); |
532 | return; |
533 | } |
534 | |
535 | if (!doc || doc.location.href == s.iframeSrc) { |
536 | // response not received yet |
537 | if (!timedOut) |
538 | return; |
539 | } |
540 | if (io.detachEvent) |
541 | io.detachEvent('onload', cb); |
542 | else |
543 | io.removeEventListener('load', cb, false); |
544 | |
545 | var status = 'success', errMsg; |
546 | try { |
547 | if (timedOut) { |
548 | throw 'timeout'; |
549 | } |
550 | |
551 | var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc); |
552 | log('isXml='+isXml); |
553 | if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) { |
554 | if (--domCheckCount) { |
555 | // in some browsers (Opera) the iframe DOM is not always traversable when |
556 | // the onload callback fires, so we loop a bit to accommodate |
557 | log('requeing onLoad callback, DOM not available'); |
558 | setTimeout(cb, 250); |
559 | return; |
560 | } |
561 | // let this fall through because server response could be an empty document |
562 | //log('Could not access iframe DOM after mutiple tries.'); |
563 | //throw 'DOMException: not available'; |
564 | } |
565 | |
566 | //log('response detected'); |
567 | var docRoot = doc.body ? doc.body : doc.documentElement; |
568 | xhr.responseText = docRoot ? docRoot.innerHTML : null; |
569 | xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc; |
570 | if (isXml) |
571 | s.dataType = 'xml'; |
572 | xhr.getResponseHeader = function(header){ |
573 | var headers = {'content-type': s.dataType}; |
574 | return headers[header]; |
575 | }; |
576 | // support for XHR 'status' & 'statusText' emulation : |
577 | if (docRoot) { |
578 | xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status; |
579 | xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText; |
580 | } |
581 | |
582 | var dt = (s.dataType || '').toLowerCase(); |
583 | var scr = /(json|script|text)/.test(dt); |
584 | if (scr || s.textarea) { |
585 | // see if user embedded response in textarea |
586 | var ta = doc.getElementsByTagName('textarea')[0]; |
587 | if (ta) { |
588 | xhr.responseText = ta.value; |
589 | // support for XHR 'status' & 'statusText' emulation : |
590 | xhr.status = Number( ta.getAttribute('status') ) || xhr.status; |
591 | xhr.statusText = ta.getAttribute('statusText') || xhr.statusText; |
592 | } |
593 | else if (scr) { |
594 | // account for browsers injecting pre around json response |
595 | var pre = doc.getElementsByTagName('pre')[0]; |
596 | var b = doc.getElementsByTagName('body')[0]; |
597 | if (pre) { |
598 | xhr.responseText = pre.innerHTML ? pre.innerHTML : pre.textContent; |
599 | } |
600 | else if (b) { |
601 | xhr.responseText = b.innerHTML ? b.innerHTML : b.textContent; |
602 | } |
603 | } |
604 | } |
605 | else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) { |
606 | xhr.responseXML = toXml(xhr.responseText); |
607 | } |
608 | |
609 | try { |
610 | data = httpData(xhr, dt, s); |
611 | } |
612 | catch (e) { |
613 | status = 'parsererror'; |
614 | xhr.error = errMsg = (e || status); |
615 | } |
616 | } |
617 | catch (e) { |
618 | log('error caught: ',e); |
619 | status = 'error'; |
620 | xhr.error = errMsg = (e || status); |
621 | } |
622 | |
623 | if (xhr.aborted) { |
624 | log('upload aborted'); |
625 | status = null; |
626 | } |
627 | |
628 | if (xhr.status) { // we've set xhr.status |
629 | status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error'; |
630 | } |
631 | |
632 | // ordering of these callbacks/triggers is odd, but that's how $.ajax does it |
633 | if (status === 'success') { |
634 | if (s.success) |
635 | s.success.call(s.context, data, 'success', xhr); |
636 | deferred.resolve(xhr.responseText, 'success', xhr); |
637 | if (g) |
638 | $.event.trigger("ajaxSuccess", [xhr, s]); |
639 | } |
640 | else if (status) { |
641 | if (errMsg === undefined) |
642 | errMsg = xhr.statusText; |
643 | if (s.error) |
644 | s.error.call(s.context, xhr, status, errMsg); |
645 | deferred.reject(xhr, 'error', errMsg); |
646 | if (g) |
647 | $.event.trigger("ajaxError", [xhr, s, errMsg]); |
648 | } |
649 | |
650 | if (g) |
651 | $.event.trigger("ajaxComplete", [xhr, s]); |
652 | |
653 | if (g && ! --$.active) { |
654 | $.event.trigger("ajaxStop"); |
655 | } |
656 | |
657 | if (s.complete) |
658 | s.complete.call(s.context, xhr, status); |
659 | |
660 | callbackProcessed = true; |
661 | if (s.timeout) |
662 | clearTimeout(timeoutHandle); |
663 | |
664 | // clean up |
665 | setTimeout(function() { |
666 | if (!s.iframeTarget) |
667 | $io.remove(); |
668 | xhr.responseXML = null; |
669 | }, 100); |
670 | } |
671 | |
672 | var toXml = $.parseXML; |
673 | |
674 | var parseJSON = $.parseJSON; |
675 | |
676 | var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4 |
677 | |
678 | var ct = xhr.getResponseHeader('content-type') || '', |
679 | xml = type === 'xml' || !type && ct.indexOf('xml') >= 0, |
680 | data = xml ? xhr.responseXML : xhr.responseText; |
681 | |
682 | if (xml && data.documentElement.nodeName === 'parsererror') { |
683 | if ($.error) |
684 | $.error('parsererror'); |
685 | } |
686 | if (s && s.dataFilter) { |
687 | data = s.dataFilter(data, type); |
688 | } |
689 | if (typeof data === 'string') { |
690 | if (type === 'json' || !type && ct.indexOf('json') >= 0) { |
691 | data = parseJSON(data); |
692 | } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) { |
693 | $.globalEval(data); |
694 | } |
695 | } |
696 | return data; |
697 | }; |
698 | |
699 | return deferred; |
700 | } |
701 | }; |
702 | |
703 | /** |
704 | * ajaxForm() provides a mechanism for fully automating form submission. |
705 | * |
706 | * The advantages of using this method instead of ajaxSubmit() are: |
707 | * |
708 | * 1: This method will include coordinates for <input type="image" /> elements (if the element |
709 | * is used to submit the form). |
710 | * 2. This method will include the submit element's name/value data (for the element that was |
711 | * used to submit the form). |
712 | * 3. This method binds the submit() method to the form for you. |
713 | * |
714 | * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely |
715 | * passes the options argument along after properly binding events for submit elements and |
716 | * the form itself. |
717 | */ |
718 | $.fn.ajaxForm = function(options) { |
719 | options = options || {}; |
720 | options.delegation = options.delegation && $.isFunction($.fn.on); |
721 | |
722 | // in jQuery 1.3+ we can fix mistakes with the ready state |
723 | if (!options.delegation && this.length === 0) { |
724 | var o = { s: this.selector, c: this.context }; |
725 | if (!$.isReady && o.s) { |
726 | log('DOM not ready, queuing ajaxForm'); |
727 | $(function() { |
728 | $(o.s,o.c).ajaxForm(options); |
729 | }); |
730 | return this; |
731 | } |
732 | // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready() |
733 | log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)')); |
734 | return this; |
735 | } |
736 | |
737 | if ( options.delegation ) { |
738 | $(document) |
739 | .off('submit.form-plugin', this.selector, doAjaxSubmit) |
740 | .off('click.form-plugin', this.selector, captureSubmittingElement) |
741 | .on('submit.form-plugin', this.selector, options, doAjaxSubmit) |
742 | .on('click.form-plugin', this.selector, options, captureSubmittingElement); |
743 | return this; |
744 | } |
745 | |
746 | return this.ajaxFormUnbind() |
747 | .bind('submit.form-plugin', options, doAjaxSubmit) |
748 | .bind('click.form-plugin', options, captureSubmittingElement); |
749 | }; |
750 | |
751 | // private event handlers |
752 | function doAjaxSubmit(e) { |
753 | /*jshint validthis:true */ |
754 | var options = e.data; |
755 | if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed |
756 | e.preventDefault(); |
757 | $(this).ajaxSubmit(options); |
758 | } |
759 | } |
760 | |
761 | function captureSubmittingElement(e) { |
762 | /*jshint validthis:true */ |
763 | var target = e.target; |
764 | var $el = $(target); |
765 | if (!($el.is("[type=submit],[type=image]"))) { |
766 | // is this a child element of the submit el? (ex: a span within a button) |
767 | var t = $el.closest('[type=submit]'); |
768 | if (t.length === 0) { |
769 | return; |
770 | } |
771 | target = t[0]; |
772 | } |
773 | var form = this; |
774 | form.clk = target; |
775 | if (target.type == 'image') { |
776 | if (e.offsetX !== undefined) { |
777 | form.clk_x = e.offsetX; |
778 | form.clk_y = e.offsetY; |
779 | } else if (typeof $.fn.offset == 'function') { |
780 | var offset = $el.offset(); |
781 | form.clk_x = e.pageX - offset.left; |
782 | form.clk_y = e.pageY - offset.top; |
783 | } else { |
784 | form.clk_x = e.pageX - target.offsetLeft; |
785 | form.clk_y = e.pageY - target.offsetTop; |
786 | } |
787 | } |
788 | // clear form vars |
789 | setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100); |
790 | } |
791 | |
792 | |
793 | // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm |
794 | $.fn.ajaxFormUnbind = function() { |
795 | return this.unbind('submit.form-plugin click.form-plugin'); |
796 | }; |
797 | |
798 | /** |
799 | * formToArray() gathers form element data into an array of objects that can |
800 | * be passed to any of the following ajax functions: $.get, $.post, or load. |
801 | * Each object in the array has both a 'name' and 'value' property. An example of |
802 | * an array for a simple login form might be: |
803 | * |
804 | * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] |
805 | * |
806 | * It is this array that is passed to pre-submit callback functions provided to the |
807 | * ajaxSubmit() and ajaxForm() methods. |
808 | */ |
809 | $.fn.formToArray = function(semantic, elements) { |
810 | var a = []; |
811 | if (this.length === 0) { |
812 | return a; |
813 | } |
814 | |
815 | var form = this[0]; |
816 | var els = semantic ? form.getElementsByTagName('*') : form.elements; |
817 | if (!els) { |
818 | return a; |
819 | } |
820 | |
821 | var i,j,n,v,el,max,jmax; |
822 | for(i=0, max=els.length; i < max; i++) { |
823 | el = els[i]; |
824 | n = el.name; |
825 | if (!n) { |
826 | continue; |
827 | } |
828 | |
829 | if (semantic && form.clk && el.type == "image") { |
830 | // handle image inputs on the fly when semantic == true |
831 | if(!el.disabled && form.clk == el) { |
832 | a.push({name: n, value: $(el).val(), type: el.type }); |
833 | a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); |
834 | } |
835 | continue; |
836 | } |
837 | |
838 | v = $.fieldValue(el, true); |
839 | if (v && v.constructor == Array) { |
840 | if (elements) |
841 | elements.push(el); |
842 | for(j=0, jmax=v.length; j < jmax; j++) { |
843 | a.push({name: n, value: v[j]}); |
844 | } |
845 | } |
846 | else if (feature.fileapi && el.type == 'file' && !el.disabled) { |
847 | if (elements) |
848 | elements.push(el); |
849 | var files = el.files; |
850 | if (files.length) { |
851 | for (j=0; j < files.length; j++) { |
852 | a.push({name: n, value: files[j], type: el.type}); |
853 | } |
854 | } |
855 | else { |
856 | // #180 |
857 | a.push({ name: n, value: '', type: el.type }); |
858 | } |
859 | } |
860 | else if (v !== null && typeof v != 'undefined') { |
861 | if (elements) |
862 | elements.push(el); |
863 | a.push({name: n, value: v, type: el.type, required: el.required}); |
864 | } |
865 | } |
866 | |
867 | if (!semantic && form.clk) { |
868 | // input type=='image' are not found in elements array! handle it here |
869 | var $input = $(form.clk), input = $input[0]; |
870 | n = input.name; |
871 | if (n && !input.disabled && input.type == 'image') { |
872 | a.push({name: n, value: $input.val()}); |
873 | a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); |
874 | } |
875 | } |
876 | return a; |
877 | }; |
878 | |
879 | /** |
880 | * Serializes form data into a 'submittable' string. This method will return a string |
881 | * in the format: name1=value1&name2=value2 |
882 | */ |
883 | $.fn.formSerialize = function(semantic) { |
884 | //hand off to jQuery.param for proper encoding |
885 | return $.param(this.formToArray(semantic)); |
886 | }; |
887 | |
888 | /** |
889 | * Serializes all field elements in the jQuery object into a query string. |
890 | * This method will return a string in the format: name1=value1&name2=value2 |
891 | */ |
892 | $.fn.fieldSerialize = function(successful) { |
893 | var a = []; |
894 | this.each(function() { |
895 | var n = this.name; |
896 | if (!n) { |
897 | return; |
898 | } |
899 | var v = $.fieldValue(this, successful); |
900 | if (v && v.constructor == Array) { |
901 | for (var i=0,max=v.length; i < max; i++) { |
902 | a.push({name: n, value: v[i]}); |
903 | } |
904 | } |
905 | else if (v !== null && typeof v != 'undefined') { |
906 | a.push({name: this.name, value: v}); |
907 | } |
908 | }); |
909 | //hand off to jQuery.param for proper encoding |
910 | return $.param(a); |
911 | }; |
912 | |
913 | /** |
914 | * Returns the value(s) of the element in the matched set. For example, consider the following form: |
915 | * |
916 | * <form><fieldset> |
917 | * <input name="A" type="text" /> |
918 | * <input name="A" type="text" /> |
919 | * <input name="B" type="checkbox" value="B1" /> |
920 | * <input name="B" type="checkbox" value="B2"/> |
921 | * <input name="C" type="radio" value="C1" /> |
922 | * <input name="C" type="radio" value="C2" /> |
923 | * </fieldset></form> |
924 | * |
925 | * var v = $('input[type=text]').fieldValue(); |
926 | * // if no values are entered into the text inputs |
927 | * v == ['',''] |
928 | * // if values entered into the text inputs are 'foo' and 'bar' |
929 | * v == ['foo','bar'] |
930 | * |
931 | * var v = $('input[type=checkbox]').fieldValue(); |
932 | * // if neither checkbox is checked |
933 | * v === undefined |
934 | * // if both checkboxes are checked |
935 | * v == ['B1', 'B2'] |
936 | * |
937 | * var v = $('input[type=radio]').fieldValue(); |
938 | * // if neither radio is checked |
939 | * v === undefined |
940 | * // if first radio is checked |
941 | * v == ['C1'] |
942 | * |
943 | * The successful argument controls whether or not the field element must be 'successful' |
944 | * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls). |
945 | * The default value of the successful argument is true. If this value is false the value(s) |
946 | * for each element is returned. |
947 | * |
948 | * Note: This method *always* returns an array. If no valid value can be determined the |
949 | * array will be empty, otherwise it will contain one or more values. |
950 | */ |
951 | $.fn.fieldValue = function(successful) { |
952 | for (var val=[], i=0, max=this.length; i < max; i++) { |
953 | var el = this[i]; |
954 | var v = $.fieldValue(el, successful); |
955 | if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) { |
956 | continue; |
957 | } |
958 | if (v.constructor == Array) |
959 | $.merge(val, v); |
960 | else |
961 | val.push(v); |
962 | } |
963 | return val; |
964 | }; |
965 | |
966 | /** |
967 | * Returns the value of the field element. |
968 | */ |
969 | $.fieldValue = function(el, successful) { |
970 | var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); |
971 | if (successful === undefined) { |
972 | successful = true; |
973 | } |
974 | |
975 | if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || |
976 | (t == 'checkbox' || t == 'radio') && !el.checked || |
977 | (t == 'submit' || t == 'image') && el.form && el.form.clk != el || |
978 | tag == 'select' && el.selectedIndex == -1)) { |
979 | return null; |
980 | } |
981 | |
982 | if (tag == 'select') { |
983 | var index = el.selectedIndex; |
984 | if (index < 0) { |
985 | return null; |
986 | } |
987 | var a = [], ops = el.options; |
988 | var one = (t == 'select-one'); |
989 | var max = (one ? index+1 : ops.length); |
990 | for(var i=(one ? index : 0); i < max; i++) { |
991 | var op = ops[i]; |
992 | if (op.selected) { |
993 | var v = op.value; |
994 | if (!v) { // extra pain for IE... |
995 | v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value; |
996 | } |
997 | if (one) { |
998 | return v; |
999 | } |
1000 | a.push(v); |
1001 | } |
1002 | } |
1003 | return a; |
1004 | } |
1005 | return $(el).val(); |
1006 | }; |
1007 | |
1008 | /** |
1009 | * Clears the form data. Takes the following actions on the form's input fields: |
1010 | * - input text fields will have their 'value' property set to the empty string |
1011 | * - select elements will have their 'selectedIndex' property set to -1 |
1012 | * - checkbox and radio inputs will have their 'checked' property set to false |
1013 | * - inputs of type submit, button, reset, and hidden will *not* be effected |
1014 | * - button elements will *not* be effected |
1015 | */ |
1016 | $.fn.clearForm = function(includeHidden) { |
1017 | return this.each(function() { |
1018 | $('input,select,textarea', this).clearFields(includeHidden); |
1019 | }); |
1020 | }; |
1021 | |
1022 | /** |
1023 | * Clears the selected form elements. |
1024 | */ |
1025 | $.fn.clearFields = $.fn.clearInputs = function(includeHidden) { |
1026 | var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list |
1027 | return this.each(function() { |
1028 | var t = this.type, tag = this.tagName.toLowerCase(); |
1029 | if (re.test(t) || tag == 'textarea') { |
1030 | this.value = ''; |
1031 | } |
1032 | else if (t == 'checkbox' || t == 'radio') { |
1033 | this.checked = false; |
1034 | } |
1035 | else if (tag == 'select') { |
1036 | this.selectedIndex = -1; |
1037 | } |
1038 | else if (t == "file") { |
1039 | if (/MSIE/.test(navigator.userAgent)) { |
1040 | $(this).replaceWith($(this).clone()); |
1041 | } else { |
1042 | $(this).val(''); |
1043 | } |
1044 | } |
1045 | else if (includeHidden) { |
1046 | // includeHidden can be the value true, or it can be a selector string |
1047 | // indicating a special test; for example: |
1048 | // $('#myForm').clearForm('.special:hidden') |
1049 | // the above would clean hidden inputs that have the class of 'special' |
1050 | if ( (includeHidden === true && /hidden/.test(t)) || |
1051 | (typeof includeHidden == 'string' && $(this).is(includeHidden)) ) |
1052 | this.value = ''; |
1053 | } |
1054 | }); |
1055 | }; |
1056 | |
1057 | /** |
1058 | * Resets the form data. Causes all form elements to be reset to their original value. |
1059 | */ |
1060 | $.fn.resetForm = function() { |
1061 | return this.each(function() { |
1062 | // guard against an input with the name of 'reset' |
1063 | // note that IE reports the reset function as an 'object' |
1064 | if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) { |
1065 | this.reset(); |
1066 | } |
1067 | }); |
1068 | }; |
1069 | |
1070 | /** |
1071 | * Enables or disables any matching elements. |
1072 | */ |
1073 | $.fn.enable = function(b) { |
1074 | if (b === undefined) { |
1075 | b = true; |
1076 | } |
1077 | return this.each(function() { |
1078 | this.disabled = !b; |
1079 | }); |
1080 | }; |
1081 | |
1082 | /** |
1083 | * Checks/unchecks any matching checkboxes or radio buttons and |
1084 | * selects/deselects and matching option elements. |
1085 | */ |
1086 | $.fn.selected = function(select) { |
1087 | if (select === undefined) { |
1088 | select = true; |
1089 | } |
1090 | return this.each(function() { |
1091 | var t = this.type; |
1092 | if (t == 'checkbox' || t == 'radio') { |
1093 | this.checked = select; |
1094 | } |
1095 | else if (this.tagName.toLowerCase() == 'option') { |
1096 | var $sel = $(this).parent('select'); |
1097 | if (select && $sel[0] && $sel[0].type == 'select-one') { |
1098 | // deselect all other options |
1099 | $sel.find('option').selected(false); |
1100 | } |
1101 | this.selected = select; |
1102 | } |
1103 | }); |
1104 | }; |
1105 | |
1106 | // expose debug var |
1107 | $.fn.ajaxSubmit.debug = false; |
1108 | |
1109 | // helper fn for console logging |
1110 | function log() { |
1111 | if (!$.fn.ajaxSubmit.debug) |
1112 | return; |
1113 | var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,''); |
1114 | if (window.console && window.console.log) { |
1115 | window.console.log(msg); |
1116 | } |
1117 | else if (window.opera && window.opera.postError) { |
1118 | window.opera.postError(msg); |
1119 | } |
1120 | } |
1121 | |
1122 | })(jQuery); |
d89f0ec8 |
1123 | |
1124 | // @license-end |