Commit cd27aeadde4ccc81cdf140ce0366e2b4381c03d5

Authored by Antonio Terceiro
1 parent e739076b

Fix media upload after jQuery upgrade

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