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