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