| 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); |