3  * version: 2.33 (22-SEP-2009)
 
   4  * @requires jQuery v1.2.6 or later
 
   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
 
  16         Do not use both ajaxSubmit and ajaxForm on the same form.  These
 
  17         functions are intended to be exclusive.  Use ajaxSubmit if you want
 
  18         to bind your own submit handler to the form.  For example,
 
  20         $(document).ready(function() {
 
  21                 $('#myForm').bind('submit', function() {
 
  25                         return false; // <-- important!
 
  29         Use ajaxForm when you want the plugin to manage all the event binding
 
  32         $(document).ready(function() {
 
  33                 $('#myForm').ajaxForm({
 
  38         When using ajaxForm, the ajaxSubmit function will be invoked for you
 
  39         at the appropriate time.
 
  43  * ajaxSubmit() provides a mechanism for immediately submitting
 
  44  * an HTML form using AJAX.
 
  46 $.fn.ajaxSubmit = function(options) {
 
  47         // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
 
  49                 log('ajaxSubmit: skipping submit process - no element selected');
 
  53         if (typeof options == 'function')
 
  54                 options = { success: options };
 
  56         var url = $.trim(this.attr('action'));
 
  58                 // clean url (don't include hash vaue)
 
  59                 url = (url.match(/^([^#]+)/)||[])[1];
 
  61         url = url || window.location.href || '';
 
  65                 type: this.attr('method') || 'GET'
 
  68         // hook for manipulating the form data before it is extracted;
 
  69         // convenient for use with rich editors like tinyMCE or FCKEditor
 
  71         this.trigger('form-pre-serialize', [this, options, veto]);
 
  73                 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
 
  77         // provide opportunity to alter form data before it is serialized
 
  78         if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
 
  79                 log('ajaxSubmit: submit aborted via beforeSerialize callback');
 
  83         var a = this.formToArray(options.semantic);
 
  85                 options.extraData = options.data;
 
  86                 for (var n in options.data) {
 
  87                   if(options.data[n] instanceof Array) {
 
  88                         for (var k in options.data[n])
 
  89                           a.push( { name: n, value: options.data[n][k] } );
 
  92                          a.push( { name: n, value: options.data[n] } );
 
  96         // give pre-submit callback an opportunity to abort the submit
 
  97         if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
 
  98                 log('ajaxSubmit: submit aborted via beforeSubmit callback');
 
 102         // fire vetoable 'validate' event
 
 103         this.trigger('form-submit-validate', [a, this, options, veto]);
 
 105                 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
 
 111         if (options.type.toUpperCase() == 'GET') {
 
 112                 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
 
 113                 options.data = null;  // data is null for 'get'
 
 116                 options.data = q; // data is the query string for 'post'
 
 118         var $form = this, callbacks = [];
 
 119         if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
 
 120         if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
 
 122         // perform a load on the target only if dataType is not provided
 
 123         if (!options.dataType && options.target) {
 
 124                 var oldSuccess = options.success || function(){};
 
 125                 callbacks.push(function(data) {
 
 126                         $(options.target).html(data).each(oldSuccess, arguments);
 
 129         else if (options.success)
 
 130                 callbacks.push(options.success);
 
 132         options.success = function(data, status) {
 
 133                 for (var i=0, max=callbacks.length; i < max; i++)
 
 134                         callbacks[i].apply(options, [data, status, $form]);
 
 137         // are there files to upload?
 
 138         var files = $('input:file', this).fieldValue();
 
 140         for (var j=0; j < files.length; j++)
 
 144         var multipart = false;
 
 145 //      var mp = 'multipart/form-data';
 
 146 //      multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
 
 148         // options.iframe allows user to force iframe mode
 
 149    if (options.iframe || found || multipart) {
 
 150            // hack to fix Safari hang (thanks to Tim Molendijk for this)
 
 151            // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
 
 152            if (options.closeKeepAlive)
 
 153                    $.get(options.closeKeepAlive, fileUpload);
 
 161         // fire 'notify' event
 
 162         this.trigger('form-submit-notify', [this, options]);
 
 166         // private function for handling file uploads (hat tip to YAHOO!)
 
 167         function fileUpload() {
 
 170                 if ($(':input[name=submit]', form).length) {
 
 171                         alert('Error: Form elements must not be named "submit".');
 
 175                 var opts = $.extend({}, $.ajaxSettings, options);
 
 176                 var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
 
 178                 var id = 'jqFormIO' + (new Date().getTime());
 
 179                 var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />');
 
 182                 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
 
 184                 var xhr = { // mock object
 
 190                         getAllResponseHeaders: function() {},
 
 191                         getResponseHeader: function() {},
 
 192                         setRequestHeader: function() {},
 
 195                                 $io.attr('src','about:blank'); // abort op in progress
 
 200                 // trigger ajax global events so that activity/block indicators work like normal
 
 201                 if (g && ! $.active++) $.event.trigger("ajaxStart");
 
 202                 if (g) $.event.trigger("ajaxSend", [xhr, opts]);
 
 204                 if (s.beforeSend && s.beforeSend(xhr, s) === false) {
 
 205                         s.global && $.active--;
 
 214                 // add submitting element to data if we know it
 
 218                         if (n && !sub.disabled) {
 
 219                                 options.extraData = options.extraData || {};
 
 220                                 options.extraData[n] = sub.value;
 
 221                                 if (sub.type == "image") {
 
 222                                         options.extraData[name+'.x'] = form.clk_x;
 
 223                                         options.extraData[name+'.y'] = form.clk_y;
 
 228                 // take a breath so that pending repaints get some cpu time before the upload starts
 
 229                 setTimeout(function() {
 
 230                         // make sure form attrs are set
 
 231                         var t = $form.attr('target'), a = $form.attr('action');
 
 233                         // update form attrs in IE friendly way
 
 234                         form.setAttribute('target',id);
 
 235                         if (form.getAttribute('method') != 'POST')
 
 236                                 form.setAttribute('method', 'POST');
 
 237                         if (form.getAttribute('action') != opts.url)
 
 238                                 form.setAttribute('action', opts.url);
 
 240                         // ie borks in some cases when setting encoding
 
 241                         if (! options.skipEncodingOverride) {
 
 243                                         encoding: 'multipart/form-data',
 
 244                                         enctype:  'multipart/form-data'
 
 250                                 setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
 
 252                         // add "extra" data to form if provided in options
 
 253                         var extraInputs = [];
 
 255                                 if (options.extraData)
 
 256                                         for (var n in options.extraData)
 
 258                                                         $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
 
 261                                 // add iframe to doc and submit the form
 
 262                                 $io.appendTo('body');
 
 263                                 io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
 
 267                                 // reset attrs and remove "extra" input elements
 
 268                                 form.setAttribute('action',a);
 
 269                                 t ? form.setAttribute('target', t) : $form.removeAttr('target');
 
 270                                 $(extraInputs).remove();
 
 274                 var domCheckCount = 50;
 
 277                         if (cbInvoked++) return;
 
 279                         io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
 
 283                                 if (timedOut) throw 'timeout';
 
 284                                 // extract the server response from the iframe
 
 287                                 doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
 
 289                                 var isXml = opts.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
 
 291                                 if (!isXml && (doc.body == null || doc.body.innerHTML == '')) {
 
 292                                         if (--domCheckCount) {
 
 293                                                 // in some browsers (Opera) the iframe DOM is not always traversable when
 
 294                                                 // the onload callback fires, so we loop a bit to accommodate
 
 299                                         log('Could not access iframe DOM after 50 tries.');
 
 303                                 xhr.responseText = doc.body ? doc.body.innerHTML : null;
 
 304                                 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
 
 305                                 xhr.getResponseHeader = function(header){
 
 306                                         var headers = {'content-type': opts.dataType};
 
 307                                         return headers[header];
 
 310                                 if (opts.dataType == 'json' || opts.dataType == 'script') {
 
 311                                         // see if user embedded response in textarea
 
 312                                         var ta = doc.getElementsByTagName('textarea')[0];
 
 314                                                 xhr.responseText = ta.value;
 
 316                                                 // account for browsers injecting pre around json response
 
 317                                                 var pre = doc.getElementsByTagName('pre')[0];
 
 319                                                         xhr.responseText = pre.innerHTML;
 
 322                                 else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
 
 323                                         xhr.responseXML = toXml(xhr.responseText);
 
 325                                 data = $.httpData(xhr, opts.dataType);
 
 329                                 $.handleError(opts, xhr, 'error', e);
 
 332                         // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
 
 334                                 opts.success(data, 'success');
 
 335                                 if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
 
 337                         if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
 
 338                         if (g && ! --$.active) $.event.trigger("ajaxStop");
 
 339                         if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
 
 342                         setTimeout(function() {
 
 344                                 xhr.responseXML = null;
 
 348                 function toXml(s, doc) {
 
 349                         if (window.ActiveXObject) {
 
 350                                 doc = new ActiveXObject('Microsoft.XMLDOM');
 
 355                                 doc = (new DOMParser()).parseFromString(s, 'text/xml');
 
 356                         return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
 
 362  * ajaxForm() provides a mechanism for fully automating form submission.
 
 364  * The advantages of using this method instead of ajaxSubmit() are:
 
 366  * 1: This method will include coordinates for <input type="image" /> elements (if the element
 
 367  *      is used to submit the form).
 
 368  * 2. This method will include the submit element's name/value data (for the element that was
 
 369  *      used to submit the form).
 
 370  * 3. This method binds the submit() method to the form for you.
 
 372  * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 
 373  * passes the options argument along after properly binding events for submit elements and
 
 376 $.fn.ajaxForm = function(options) {
 
 377         return this.ajaxFormUnbind().bind('submit.form-plugin', function() {
 
 378                 $(this).ajaxSubmit(options);
 
 380         }).bind('click.form-plugin', function(e) {
 
 381                 var $el = $(e.target);
 
 382                 if (!($el.is(":submit,input:image"))) {
 
 387                 if (e.target.type == 'image') {
 
 388                         if (e.offsetX != undefined) {
 
 389                                 form.clk_x = e.offsetX;
 
 390                                 form.clk_y = e.offsetY;
 
 391                         } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
 
 392                                 var offset = $el.offset();
 
 393                                 form.clk_x = e.pageX - offset.left;
 
 394                                 form.clk_y = e.pageY - offset.top;
 
 396                                 form.clk_x = e.pageX - e.target.offsetLeft;
 
 397                                 form.clk_y = e.pageY - e.target.offsetTop;
 
 401                 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
 
 405 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
 
 406 $.fn.ajaxFormUnbind = function() {
 
 407         return this.unbind('submit.form-plugin click.form-plugin');
 
 411  * formToArray() gathers form element data into an array of objects that can
 
 412  * be passed to any of the following ajax functions: $.get, $.post, or load.
 
 413  * Each object in the array has both a 'name' and 'value' property.  An example of
 
 414  * an array for a simple login form might be:
 
 416  * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 
 418  * It is this array that is passed to pre-submit callback functions provided to the
 
 419  * ajaxSubmit() and ajaxForm() methods.
 
 421 $.fn.formToArray = function(semantic) {
 
 423         if (this.length == 0) return a;
 
 426         var els = semantic ? form.getElementsByTagName('*') : form.elements;
 
 428         for(var i=0, max=els.length; i < max; i++) {
 
 433                 if (semantic && form.clk && el.type == "image") {
 
 434                         // handle image inputs on the fly when semantic == true
 
 435                         if(!el.disabled && form.clk == el) {
 
 436                                 a.push({name: n, value: $(el).val()});
 
 437                                 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
 
 442                 var v = $.fieldValue(el, true);
 
 443                 if (v && v.constructor == Array) {
 
 444                         for(var j=0, jmax=v.length; j < jmax; j++)
 
 445                                 a.push({name: n, value: v[j]});
 
 447                 else if (v !== null && typeof v != 'undefined')
 
 448                         a.push({name: n, value: v});
 
 451         if (!semantic && form.clk) {
 
 452                 // input type=='image' are not found in elements array! handle it here
 
 453                 var $input = $(form.clk), input = $input[0], n = input.name;
 
 454                 if (n && !input.disabled && input.type == 'image') {
 
 455                         a.push({name: n, value: $input.val()});
 
 456                         a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
 
 463  * Serializes form data into a 'submittable' string. This method will return a string
 
 464  * in the format: name1=value1&name2=value2
 
 466 $.fn.formSerialize = function(semantic) {
 
 467         //hand off to jQuery.param for proper encoding
 
 468         return $.param(this.formToArray(semantic));
 
 472  * Serializes all field elements in the jQuery object into a query string.
 
 473  * This method will return a string in the format: name1=value1&name2=value2
 
 475 $.fn.fieldSerialize = function(successful) {
 
 477         this.each(function() {
 
 480                 var v = $.fieldValue(this, successful);
 
 481                 if (v && v.constructor == Array) {
 
 482                         for (var i=0,max=v.length; i < max; i++)
 
 483                                 a.push({name: n, value: v[i]});
 
 485                 else if (v !== null && typeof v != 'undefined')
 
 486                         a.push({name: this.name, value: v});
 
 488         //hand off to jQuery.param for proper encoding
 
 493  * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 
 496  *        <input name="A" type="text" />
 
 497  *        <input name="A" type="text" />
 
 498  *        <input name="B" type="checkbox" value="B1" />
 
 499  *        <input name="B" type="checkbox" value="B2"/>
 
 500  *        <input name="C" type="radio" value="C1" />
 
 501  *        <input name="C" type="radio" value="C2" />
 
 504  *  var v = $(':text').fieldValue();
 
 505  *  // if no values are entered into the text inputs
 
 507  *  // if values entered into the text inputs are 'foo' and 'bar'
 
 510  *  var v = $(':checkbox').fieldValue();
 
 511  *  // if neither checkbox is checked
 
 513  *  // if both checkboxes are checked
 
 516  *  var v = $(':radio').fieldValue();
 
 517  *  // if neither radio is checked
 
 519  *  // if first radio is checked
 
 522  * The successful argument controls whether or not the field element must be 'successful'
 
 523  * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 
 524  * The default value of the successful argument is true.  If this value is false the value(s)
 
 525  * for each element is returned.
 
 527  * Note: This method *always* returns an array.  If no valid value can be determined the
 
 528  *         array will be empty, otherwise it will contain one or more values.
 
 530 $.fn.fieldValue = function(successful) {
 
 531         for (var val=[], i=0, max=this.length; i < max; i++) {
 
 533                 var v = $.fieldValue(el, successful);
 
 534                 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
 
 536                 v.constructor == Array ? $.merge(val, v) : val.push(v);
 
 542  * Returns the value of the field element.
 
 544 $.fieldValue = function(el, successful) {
 
 545         var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
 
 546         if (typeof successful == 'undefined') successful = true;
 
 548         if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
 
 549                 (t == 'checkbox' || t == 'radio') && !el.checked ||
 
 550                 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
 
 551                 tag == 'select' && el.selectedIndex == -1))
 
 554         if (tag == 'select') {
 
 555                 var index = el.selectedIndex;
 
 556                 if (index < 0) return null;
 
 557                 var a = [], ops = el.options;
 
 558                 var one = (t == 'select-one');
 
 559                 var max = (one ? index+1 : ops.length);
 
 560                 for(var i=(one ? index : 0); i < max; i++) {
 
 564                                 if (!v) // extra pain for IE...
 
 565                                         v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
 
 576  * Clears the form data.  Takes the following actions on the form's input fields:
 
 577  *  - input text fields will have their 'value' property set to the empty string
 
 578  *  - select elements will have their 'selectedIndex' property set to -1
 
 579  *  - checkbox and radio inputs will have their 'checked' property set to false
 
 580  *  - inputs of type submit, button, reset, and hidden will *not* be effected
 
 581  *  - button elements will *not* be effected
 
 583 $.fn.clearForm = function() {
 
 584         return this.each(function() {
 
 585                 $('input,select,textarea', this).clearFields();
 
 590  * Clears the selected form elements.
 
 592 $.fn.clearFields = $.fn.clearInputs = function() {
 
 593         return this.each(function() {
 
 594                 var t = this.type, tag = this.tagName.toLowerCase();
 
 595                 if (t == 'text' || t == 'password' || tag == 'textarea')
 
 597                 else if (t == 'checkbox' || t == 'radio')
 
 598                         this.checked = false;
 
 599                 else if (tag == 'select')
 
 600                         this.selectedIndex = -1;
 
 605  * Resets the form data.  Causes all form elements to be reset to their original value.
 
 607 $.fn.resetForm = function() {
 
 608         return this.each(function() {
 
 609                 // guard against an input with the name of 'reset'
 
 610                 // note that IE reports the reset function as an 'object'
 
 611                 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
 
 617  * Enables or disables any matching elements.
 
 619 $.fn.enable = function(b) {
 
 620         if (b == undefined) b = true;
 
 621         return this.each(function() {
 
 627  * Checks/unchecks any matching checkboxes or radio buttons and
 
 628  * selects/deselects and matching option elements.
 
 630 $.fn.selected = function(select) {
 
 631         if (select == undefined) select = true;
 
 632         return this.each(function() {
 
 634                 if (t == 'checkbox' || t == 'radio')
 
 635                         this.checked = select;
 
 636                 else if (this.tagName.toLowerCase() == 'option') {
 
 637                         var $sel = $(this).parent('select');
 
 638                         if (select && $sel[0] && $sel[0].type == 'select-one') {
 
 639                                 // deselect all other options
 
 640                                 $sel.find('option').selected(false);
 
 642                         this.selected = select;
 
 647 // helper fn for console logging
 
 648 // set $.fn.ajaxSubmit.debug to true to enable debug logging
 
 650         if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
 
 651                 window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));