templates/jquery/jquery.form.js in compass-jquery-plugin-0.3.2.pre.2 vs templates/jquery/jquery.form.js in compass-jquery-plugin-0.3.2.0

- old
+ new

@@ -1,9 +1,9 @@ -/* +/*! * jQuery Form Plugin - * version: 2.36 (07-NOV-2009) - * @requires jQuery v1.2.6 or later + * version: 2.52 (07-DEC-2010) + * @requires jQuery v1.3.2 or later * * Examples and documentation at: http://malsup.com/jquery/form/ * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html @@ -16,15 +16,15 @@ Do not use both ajaxSubmit and ajaxForm on the same form. These functions are intended to be exclusive. Use ajaxSubmit if you want to bind your own submit handler to the form. For example, $(document).ready(function() { - $('#myForm').bind('submit', function() { + $('#myForm').bind('submit', function(e) { + e.preventDefault(); // <-- important $(this).ajaxSubmit({ target: '#output' }); - return false; // <-- important! }); }); Use ajaxForm when you want the plugin to manage all the event binding for you. For example, @@ -48,25 +48,27 @@ if (!this.length) { log('ajaxSubmit: skipping submit process - no element selected'); return this; } - if (typeof options == 'function') + if (typeof options == 'function') { options = { success: options }; + } - var url = $.trim(this.attr('action')); + var action = this.attr('action'); + var url = (typeof action === 'string') ? $.trim(action) : ''; if (url) { // clean url (don't include hash vaue) url = (url.match(/^([^#]+)/)||[])[1]; - } - url = url || window.location.href || ''; + } + url = url || window.location.href || ''; - options = $.extend({ + options = $.extend(true, { url: url, type: this.attr('method') || 'GET', iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' - }, options || {}); + }, options); // hook for manipulating the form data before it is extracted; // convenient for use with rich editors like tinyMCE or FCKEditor var veto = {}; this.trigger('form-pre-serialize', [this, options, veto]); @@ -79,20 +81,24 @@ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { log('ajaxSubmit: submit aborted via beforeSerialize callback'); return this; } - var a = this.formToArray(options.semantic); + var n,v,a = this.formToArray(options.semantic); if (options.data) { options.extraData = options.data; - for (var n in options.data) { - if(options.data[n] instanceof Array) { - for (var k in options.data[n]) - a.push( { name: n, value: options.data[n][k] } ); - } - else - a.push( { name: n, value: options.data[n] } ); + for (n in options.data) { + if(options.data[n] instanceof Array) { + for (var k in options.data[n]) { + a.push( { name: n, value: options.data[n][k] } ); + } + } + else { + v = options.data[n]; + v = $.isFunction(v) ? v() : v; // if value is fn, invoke it + a.push( { name: n, value: v } ); + } } } // give pre-submit callback an opportunity to abort the submit if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { @@ -111,75 +117,90 @@ if (options.type.toUpperCase() == 'GET') { options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; options.data = null; // data is null for 'get' } - else + else { options.data = q; // data is the query string for 'post' + } var $form = this, callbacks = []; - if (options.resetForm) callbacks.push(function() { $form.resetForm(); }); - if (options.clearForm) callbacks.push(function() { $form.clearForm(); }); + if (options.resetForm) { + callbacks.push(function() { $form.resetForm(); }); + } + if (options.clearForm) { + callbacks.push(function() { $form.clearForm(); }); + } // perform a load on the target only if dataType is not provided if (!options.dataType && options.target) { var oldSuccess = options.success || function(){}; callbacks.push(function(data) { - $(options.target).html(data).each(oldSuccess, arguments); + var fn = options.replaceTarget ? 'replaceWith' : 'html'; + $(options.target)[fn](data).each(oldSuccess, arguments); }); } - else if (options.success) + else if (options.success) { callbacks.push(options.success); + } - options.success = function(data, status) { - for (var i=0, max=callbacks.length; i < max; i++) - callbacks[i].apply(options, [data, status, $form]); + options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg + var context = options.context || options; // jQuery 1.4+ supports scope context + for (var i=0, max=callbacks.length; i < max; i++) { + callbacks[i].apply(context, [data, status, xhr || $form, $form]); + } }; // are there files to upload? - var files = $('input:file', this).fieldValue(); - var found = false; - for (var j=0; j < files.length; j++) - if (files[j]) - found = true; + var fileInputs = $('input:file', this).length > 0; + var mp = 'multipart/form-data'; + var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); - var multipart = false; -// var mp = 'multipart/form-data'; -// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); - // options.iframe allows user to force iframe mode // 06-NOV-09: now defaulting to iframe mode if file input is detected - if ((files.length && options.iframe !== false) || options.iframe || found || multipart) { + if (options.iframe !== false && (fileInputs || options.iframe || multipart)) { // hack to fix Safari hang (thanks to Tim Molendijk for this) // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d - if (options.closeKeepAlive) + if (options.closeKeepAlive) { $.get(options.closeKeepAlive, fileUpload); - else + } + else { fileUpload(); - } - else + } + } + else { $.ajax(options); + } // fire 'notify' event this.trigger('form-submit-notify', [this, options]); return this; // private function for handling file uploads (hat tip to YAHOO!) function fileUpload() { var form = $form[0]; - if ($(':input[name=submit]', form).length) { - alert('Error: Form elements must not be named "submit".'); + if ($(':input[name=submit],:input[id=submit]', form).length) { + // if there is an input with a name or id of 'submit' then we won't be + // able to invoke the submit fn on the form (at least not x-browser) + alert('Error: Form elements must not have name or id of "submit".'); return; } - - var opts = $.extend({}, $.ajaxSettings, options); - var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts); - - var id = 'jqFormIO' + (new Date().getTime()); - var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ opts.iframeSrc +'" />'); + + var s = $.extend(true, {}, $.ajaxSettings, options); + s.context = s.context || s; + var id = 'jqFormIO' + (new Date().getTime()), fn = '_'+id; + window[fn] = function() { + var f = $io.data('form-plugin-onload'); + if (f) { + f(); + window[fn] = undefined; + try { delete window[fn]; } catch(e){} + } + } + var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ s.iframeSrc +'" onload="window[\'_\'+this.id]()" />'); var io = $io[0]; $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' }); var xhr = { // mock object @@ -191,174 +212,226 @@ getAllResponseHeaders: function() {}, getResponseHeader: function() {}, setRequestHeader: function() {}, abort: function() { this.aborted = 1; - $io.attr('src', opts.iframeSrc); // abort op in progress + $io.attr('src', s.iframeSrc); // abort op in progress } }; - var g = opts.global; + var g = s.global; // trigger ajax global events so that activity/block indicators work like normal - if (g && ! $.active++) $.event.trigger("ajaxStart"); - if (g) $.event.trigger("ajaxSend", [xhr, opts]); + if (g && ! $.active++) { + $.event.trigger("ajaxStart"); + } + if (g) { + $.event.trigger("ajaxSend", [xhr, s]); + } - if (s.beforeSend && s.beforeSend(xhr, s) === false) { - s.global && $.active--; + if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) { + if (s.global) { + $.active--; + } return; } - if (xhr.aborted) + if (xhr.aborted) { return; + } - var cbInvoked = 0; + var cbInvoked = false; var timedOut = 0; // add submitting element to data if we know it var sub = form.clk; if (sub) { var n = sub.name; if (n && !sub.disabled) { - options.extraData = options.extraData || {}; - options.extraData[n] = sub.value; + s.extraData = s.extraData || {}; + s.extraData[n] = sub.value; if (sub.type == "image") { - options.extraData[name+'.x'] = form.clk_x; - options.extraData[name+'.y'] = form.clk_y; + s.extraData[n+'.x'] = form.clk_x; + s.extraData[n+'.y'] = form.clk_y; } } } // take a breath so that pending repaints get some cpu time before the upload starts - setTimeout(function() { + function doSubmit() { // make sure form attrs are set var t = $form.attr('target'), a = $form.attr('action'); // update form attrs in IE friendly way form.setAttribute('target',id); - if (form.getAttribute('method') != 'POST') + if (form.getAttribute('method') != 'POST') { form.setAttribute('method', 'POST'); - if (form.getAttribute('action') != opts.url) - form.setAttribute('action', opts.url); + } + if (form.getAttribute('action') != s.url) { + form.setAttribute('action', s.url); + } // ie borks in some cases when setting encoding - if (! options.skipEncodingOverride) { + if (! s.skipEncodingOverride) { $form.attr({ encoding: 'multipart/form-data', enctype: 'multipart/form-data' }); } // support timout - if (opts.timeout) - setTimeout(function() { timedOut = true; cb(); }, opts.timeout); + if (s.timeout) { + setTimeout(function() { timedOut = true; cb(); }, s.timeout); + } // add "extra" data to form if provided in options var extraInputs = []; try { - if (options.extraData) - for (var n in options.extraData) + if (s.extraData) { + for (var n in s.extraData) { extraInputs.push( - $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />') + $('<input type="hidden" name="'+n+'" value="'+s.extraData[n]+'" />') .appendTo(form)[0]); + } + } // add iframe to doc and submit the form $io.appendTo('body'); - io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false); + $io.data('form-plugin-onload', cb); form.submit(); } finally { // reset attrs and remove "extra" input elements form.setAttribute('action',a); - t ? form.setAttribute('target', t) : $form.removeAttr('target'); + if(t) { + form.setAttribute('target', t); + } else { + $form.removeAttr('target'); + } $(extraInputs).remove(); } - }, 10); + } - var domCheckCount = 50; + if (s.forceSync) { + doSubmit(); + } + else { + setTimeout(doSubmit, 10); // this lets dom updates render + } + + var data, doc, domCheckCount = 50; function cb() { - if (cbInvoked++) return; + if (cbInvoked) { + return; + } - io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false); - + $io.removeData('form-plugin-onload'); + var ok = true; try { - if (timedOut) throw 'timeout'; + if (timedOut) { + throw 'timeout'; + } // extract the server response from the iframe - var data, doc; - doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document; - var isXml = opts.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc); + var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc); log('isXml='+isXml); - if (!isXml && (doc.body == null || doc.body.innerHTML == '')) { - if (--domCheckCount) { + if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) { + if (--domCheckCount) { // in some browsers (Opera) the iframe DOM is not always traversable when // the onload callback fires, so we loop a bit to accommodate - cbInvoked = 0; - setTimeout(cb, 100); + log('requeing onLoad callback, DOM not available'); + setTimeout(cb, 250); return; } - log('Could not access iframe DOM after 50 tries.'); - return; + // let this fall through because server response could be an empty document + //log('Could not access iframe DOM after mutiple tries.'); + //throw 'DOMException: not available'; } - xhr.responseText = doc.body ? doc.body.innerHTML : null; + //log('response detected'); + cbInvoked = true; + xhr.responseText = doc.documentElement ? doc.documentElement.innerHTML : null; xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc; xhr.getResponseHeader = function(header){ - var headers = {'content-type': opts.dataType}; + var headers = {'content-type': s.dataType}; return headers[header]; }; - if (opts.dataType == 'json' || opts.dataType == 'script') { + var scr = /(json|script)/.test(s.dataType); + if (scr || s.textarea) { // see if user embedded response in textarea var ta = doc.getElementsByTagName('textarea')[0]; - if (ta) + if (ta) { xhr.responseText = ta.value; - else { + } + else if (scr) { // account for browsers injecting pre around json response var pre = doc.getElementsByTagName('pre')[0]; - if (pre) - xhr.responseText = pre.innerHTML; + var b = doc.getElementsByTagName('body')[0]; + if (pre) { + xhr.responseText = pre.textContent; + } + else if (b) { + xhr.responseText = b.innerHTML; + } } } - else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) { + else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) { xhr.responseXML = toXml(xhr.responseText); } - data = $.httpData(xhr, opts.dataType); + data = $.httpData(xhr, s.dataType); } catch(e){ + log('error caught:',e); ok = false; - $.handleError(opts, xhr, 'error', e); + xhr.error = e; + $.handleError(s, xhr, 'error', e); } + + if (xhr.aborted) { + log('upload aborted'); + ok = false; + } // ordering of these callbacks/triggers is odd, but that's how $.ajax does it if (ok) { - opts.success(data, 'success'); - if (g) $.event.trigger("ajaxSuccess", [xhr, opts]); + s.success.call(s.context, data, 'success', xhr); + if (g) { + $.event.trigger("ajaxSuccess", [xhr, s]); + } } - if (g) $.event.trigger("ajaxComplete", [xhr, opts]); - if (g && ! --$.active) $.event.trigger("ajaxStop"); - if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error'); + if (g) { + $.event.trigger("ajaxComplete", [xhr, s]); + } + if (g && ! --$.active) { + $.event.trigger("ajaxStop"); + } + if (s.complete) { + s.complete.call(s.context, xhr, ok ? 'success' : 'error'); + } // clean up setTimeout(function() { + $io.removeData('form-plugin-onload'); $io.remove(); xhr.responseXML = null; }, 100); - }; + } function toXml(s, doc) { if (window.ActiveXObject) { doc = new ActiveXObject('Microsoft.XMLDOM'); doc.async = 'false'; doc.loadXML(s); } - else + else { doc = (new DOMParser()).parseFromString(s, 'text/xml'); + } return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null; - }; - }; + } + } }; /** * ajaxForm() provides a mechanism for fully automating form submission. * @@ -373,21 +446,39 @@ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely * passes the options argument along after properly binding events for submit elements and * the form itself. */ $.fn.ajaxForm = function(options) { - return this.ajaxFormUnbind().bind('submit.form-plugin', function() { - $(this).ajaxSubmit(options); - return false; + // in jQuery 1.3+ we can fix mistakes with the ready state + if (this.length === 0) { + var o = { s: this.selector, c: this.context }; + if (!$.isReady && o.s) { + log('DOM not ready, queuing ajaxForm'); + $(function() { + $(o.s,o.c).ajaxForm(options); + }); + return this; + } + // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready() + log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)')); + return this; + } + + return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) { + if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed + e.preventDefault(); + $(this).ajaxSubmit(options); + } }).bind('click.form-plugin', function(e) { var target = e.target; var $el = $(target); if (!($el.is(":submit,input:image"))) { // is this a child element of the submit el? (ex: a span within a button) var t = $el.closest(':submit'); - if (t.length == 0) + if (t.length == 0) { return; + } target = t[0]; } var form = this; form.clk = target; if (target.type == 'image') { @@ -424,41 +515,52 @@ * It is this array that is passed to pre-submit callback functions provided to the * ajaxSubmit() and ajaxForm() methods. */ $.fn.formToArray = function(semantic) { var a = []; - if (this.length == 0) return a; + if (this.length === 0) { + return a; + } var form = this[0]; var els = semantic ? form.getElementsByTagName('*') : form.elements; - if (!els) return a; - for(var i=0, max=els.length; i < max; i++) { - var el = els[i]; - var n = el.name; - if (!n) continue; + if (!els) { + return a; + } + + var i,j,n,v,el,max,jmax; + for(i=0, max=els.length; i < max; i++) { + el = els[i]; + n = el.name; + if (!n) { + continue; + } if (semantic && form.clk && el.type == "image") { // handle image inputs on the fly when semantic == true if(!el.disabled && form.clk == el) { a.push({name: n, value: $(el).val()}); a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); } continue; } - var v = $.fieldValue(el, true); + v = $.fieldValue(el, true); if (v && v.constructor == Array) { - for(var j=0, jmax=v.length; j < jmax; j++) + for(j=0, jmax=v.length; j < jmax; j++) { a.push({name: n, value: v[j]}); + } } - else if (v !== null && typeof v != 'undefined') + else if (v !== null && typeof v != 'undefined') { a.push({name: n, value: v}); + } } if (!semantic && form.clk) { // input type=='image' are not found in elements array! handle it here - var $input = $(form.clk), input = $input[0], n = input.name; + var $input = $(form.clk), input = $input[0]; + n = input.name; if (n && !input.disabled && input.type == 'image') { a.push({name: n, value: $input.val()}); a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); } } @@ -480,18 +582,22 @@ */ $.fn.fieldSerialize = function(successful) { var a = []; this.each(function() { var n = this.name; - if (!n) return; + if (!n) { + return; + } var v = $.fieldValue(this, successful); if (v && v.constructor == Array) { - for (var i=0,max=v.length; i < max; i++) + for (var i=0,max=v.length; i < max; i++) { a.push({name: n, value: v[i]}); + } } - else if (v !== null && typeof v != 'undefined') + else if (v !== null && typeof v != 'undefined') { a.push({name: this.name, value: v}); + } }); //hand off to jQuery.param for proper encoding return $.param(a); }; @@ -535,49 +641,58 @@ */ $.fn.fieldValue = function(successful) { for (var val=[], i=0, max=this.length; i < max; i++) { var el = this[i]; var v = $.fieldValue(el, successful); - if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) + if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) { continue; + } v.constructor == Array ? $.merge(val, v) : val.push(v); } return val; }; /** * Returns the value of the field element. */ $.fieldValue = function(el, successful) { var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); - if (typeof successful == 'undefined') successful = true; + if (successful === undefined) { + successful = true; + } if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || (t == 'checkbox' || t == 'radio') && !el.checked || (t == 'submit' || t == 'image') && el.form && el.form.clk != el || - tag == 'select' && el.selectedIndex == -1)) + tag == 'select' && el.selectedIndex == -1)) { return null; + } if (tag == 'select') { var index = el.selectedIndex; - if (index < 0) return null; + if (index < 0) { + return null; + } var a = [], ops = el.options; var one = (t == 'select-one'); var max = (one ? index+1 : ops.length); for(var i=(one ? index : 0); i < max; i++) { var op = ops[i]; if (op.selected) { var v = op.value; - if (!v) // extra pain for IE... + if (!v) { // extra pain for IE... v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value; - if (one) return v; + } + if (one) { + return v; + } a.push(v); } } return a; } - return el.value; + return $(el).val(); }; /** * Clears the form data. Takes the following actions on the form's input fields: * - input text fields will have their 'value' property set to the empty string @@ -596,51 +711,60 @@ * Clears the selected form elements. */ $.fn.clearFields = $.fn.clearInputs = function() { return this.each(function() { var t = this.type, tag = this.tagName.toLowerCase(); - if (t == 'text' || t == 'password' || tag == 'textarea') + if (t == 'text' || t == 'password' || tag == 'textarea') { this.value = ''; - else if (t == 'checkbox' || t == 'radio') + } + else if (t == 'checkbox' || t == 'radio') { this.checked = false; - else if (tag == 'select') + } + else if (tag == 'select') { this.selectedIndex = -1; + } }); }; /** * Resets the form data. Causes all form elements to be reset to their original value. */ $.fn.resetForm = function() { return this.each(function() { // guard against an input with the name of 'reset' // note that IE reports the reset function as an 'object' - if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) + if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) { this.reset(); + } }); }; /** * Enables or disables any matching elements. */ $.fn.enable = function(b) { - if (b == undefined) b = true; + if (b === undefined) { + b = true; + } return this.each(function() { this.disabled = !b; }); }; /** * Checks/unchecks any matching checkboxes or radio buttons and * selects/deselects and matching option elements. */ $.fn.selected = function(select) { - if (select == undefined) select = true; + if (select === undefined) { + select = true; + } return this.each(function() { var t = this.type; - if (t == 'checkbox' || t == 'radio') + if (t == 'checkbox' || t == 'radio') { this.checked = select; + } else if (this.tagName.toLowerCase() == 'option') { var $sel = $(this).parent('select'); if (select && $sel[0] && $sel[0].type == 'select-one') { // deselect all other options $sel.find('option').selected(false); @@ -651,10 +775,17 @@ }; // helper fn for console logging // set $.fn.ajaxSubmit.debug to true to enable debug logging function log() { - if ($.fn.ajaxSubmit.debug && window.console && window.console.log) - window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,'')); + if ($.fn.ajaxSubmit.debug) { + var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,''); + if (window.console && window.console.log) { + window.console.log(msg); + } + else if (window.opera && window.opera.postError) { + window.opera.postError(msg); + } + } }; })(jQuery);