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