6 * Examples and documentation at: http://malsup.com/jquery/form/ |
6 * Examples and documentation at: http://malsup.com/jquery/form/ |
7 * Dual licensed under the MIT and GPL licenses: |
7 * Dual licensed under the MIT and GPL licenses: |
8 * http://www.opensource.org/licenses/mit-license.php |
8 * http://www.opensource.org/licenses/mit-license.php |
9 * http://www.gnu.org/licenses/gpl.html |
9 * http://www.gnu.org/licenses/gpl.html |
10 */ |
10 */ |
11 (function(b){b.fn.ajaxSubmit=function(t){if(!this.length){a("ajaxSubmit: skipping submit process - no element selected");return this}if(typeof t=="function"){t={success:t}}var h=this.attr("action");var d=(typeof h==="string")?b.trim(h):"";if(d){d=(d.match(/^([^#]+)/)||[])[1]}d=d||window.location.href||"";t=b.extend(true,{url:d,success:b.ajaxSettings.success,type:this[0].getAttribute("method")||"GET",iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank"},t);var u={};this.trigger("form-pre-serialize",[this,t,u]);if(u.veto){a("ajaxSubmit: submit vetoed via form-pre-serialize trigger");return this}if(t.beforeSerialize&&t.beforeSerialize(this,t)===false){a("ajaxSubmit: submit aborted via beforeSerialize callback");return this}var f,p,m=this.formToArray(t.semantic);if(t.data){t.extraData=t.data;for(f in t.data){if(t.data[f] instanceof Array){for(var i in t.data[f]){m.push({name:f,value:t.data[f][i]})}}else{p=t.data[f];p=b.isFunction(p)?p():p;m.push({name:f,value:p})}}}if(t.beforeSubmit&&t.beforeSubmit(m,this,t)===false){a("ajaxSubmit: submit aborted via beforeSubmit callback");return this}this.trigger("form-submit-validate",[m,this,t,u]);if(u.veto){a("ajaxSubmit: submit vetoed via form-submit-validate trigger");return this}var c=b.param(m);if(t.type.toUpperCase()=="GET"){t.url+=(t.url.indexOf("?")>=0?"&":"?")+c;t.data=null}else{t.data=c}var s=this,l=[];if(t.resetForm){l.push(function(){s.resetForm()})}if(t.clearForm){l.push(function(){s.clearForm()})}if(!t.dataType&&t.target){var r=t.success||function(){};l.push(function(n){var k=t.replaceTarget?"replaceWith":"html";b(t.target)[k](n).each(r,arguments)})}else{if(t.success){l.push(t.success)}}t.success=function(w,n,x){var v=t.context||t;for(var q=0,k=l.length;q<k;q++){l[q].apply(v,[w,n,x||s,s])}};var g=b("input:file",this).length>0;var e="multipart/form-data";var j=(s.attr("enctype")==e||s.attr("encoding")==e);if(t.iframe!==false&&(g||t.iframe||j)){if(t.closeKeepAlive){b.get(t.closeKeepAlive,o)}else{o()}}else{b.ajax(t)}this.trigger("form-submit-notify",[this,t]);return this;function o(){var v=s[0];if(b(":input[name=submit],:input[id=submit]",v).length){alert('Error: Form elements must not have name or id of "submit".');return}var D=b.extend(true,{},b.ajaxSettings,t);D.context=D.context||D;var G="jqFormIO"+(new Date().getTime()),A="_"+G;var x=b('<iframe id="'+G+'" name="'+G+'" src="'+D.iframeSrc+'" />');var B=x[0];x.css({position:"absolute",top:"-1000px",left:"-1000px"});var y={aborted:0,responseText:null,responseXML:null,status:0,statusText:"n/a",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(n){var O=(n==="timeout"?"timeout":"aborted");a("aborting upload... "+O);this.aborted=1;x.attr("src",D.iframeSrc);y.error=O;D.error&&D.error.call(D.context,y,O,O);K&&b.event.trigger("ajaxError",[y,D,O]);D.complete&&D.complete.call(D.context,y,O)}};var K=D.global;if(K&&!b.active++){b.event.trigger("ajaxStart")}if(K){b.event.trigger("ajaxSend",[y,D])}if(D.beforeSend&&D.beforeSend.call(D.context,y,D)===false){if(D.global){b.active--}return}if(y.aborted){return}var J=0,C;var z=v.clk;if(z){var H=z.name;if(H&&!z.disabled){D.extraData=D.extraData||{};D.extraData[H]=z.value;if(z.type=="image"){D.extraData[H+".x"]=v.clk_x;D.extraData[H+".y"]=v.clk_y}}}function I(){var Q=s.attr("target"),O=s.attr("action");v.setAttribute("target",G);if(v.getAttribute("method")!="POST"){v.setAttribute("method","POST")}if(v.getAttribute("action")!=D.url){v.setAttribute("action",D.url)}if(!D.skipEncodingOverride){s.attr({encoding:"multipart/form-data",enctype:"multipart/form-data"})}if(D.timeout){C=setTimeout(function(){J=true;F(true)},D.timeout)}var P=[];try{if(D.extraData){for(var R in D.extraData){P.push(b('<input type="hidden" name="'+R+'" value="'+D.extraData[R]+'" />').appendTo(v)[0])}}x.appendTo("body");B.attachEvent?B.attachEvent("onload",F):B.addEventListener("load",F,false);v.submit()}finally{v.setAttribute("action",O);if(Q){v.setAttribute("target",Q)}else{s.removeAttr("target")}b(P).remove()}}if(D.forceSync){I()}else{setTimeout(I,10)}var M,N,L=50,w;function F(T){if(y.aborted||w){return}if(T===true&&y){y.abort("timeout");return}var S=B.contentWindow?B.contentWindow.document:B.contentDocument?B.contentDocument:B.document;if(!S||S.location.href==D.iframeSrc){if(!J){return}}B.detachEvent?B.detachEvent("onload",F):B.removeEventListener("load",F,false);var P=true;try{if(J){throw"timeout"}var U=D.dataType=="xml"||S.XMLDocument||b.isXMLDoc(S);a("isXml="+U);if(!U&&window.opera&&(S.body==null||S.body.innerHTML=="")){if(--L){a("requeing onLoad callback, DOM not available");setTimeout(F,250);return}}y.responseText=S.body?S.body.innerHTML:S.documentElement?S.documentElement.innerHTML:null;y.responseXML=S.XMLDocument?S.XMLDocument:S;if(U){D.dataType="xml"}y.getResponseHeader=function(W){var V={"content-type":D.dataType};return V[W]};var R=/(json|script|text)/.test(D.dataType);if(R||D.textarea){var O=S.getElementsByTagName("textarea")[0];if(O){y.responseText=O.value}else{if(R){var Q=S.getElementsByTagName("pre")[0];var n=S.getElementsByTagName("body")[0];if(Q){y.responseText=Q.textContent}else{if(n){y.responseText=n.innerHTML}}}}}else{if(D.dataType=="xml"&&!y.responseXML&&y.responseText!=null){y.responseXML=E(y.responseText)}}M=k(y,D.dataType,D)}catch(T){a("error caught:",T);P=false;y.error=T;D.error&&D.error.call(D.context,y,"error",T);K&&b.event.trigger("ajaxError",[y,D,T])}if(y.aborted){a("upload aborted");P=false}if(P){D.success&&D.success.call(D.context,M,"success",y);K&&b.event.trigger("ajaxSuccess",[y,D])}K&&b.event.trigger("ajaxComplete",[y,D]);if(K&&!--b.active){b.event.trigger("ajaxStop")}D.complete&&D.complete.call(D.context,y,P?"success":"error");w=true;if(D.timeout){clearTimeout(C)}setTimeout(function(){x.removeData("form-plugin-onload");x.remove();y.responseXML=null},100)}var E=b.parseXML||function(n,O){if(window.ActiveXObject){O=new ActiveXObject("Microsoft.XMLDOM");O.async="false";O.loadXML(n)}else{O=(new DOMParser()).parseFromString(n,"text/xml")}return(O&&O.documentElement&&O.documentElement.nodeName!="parsererror")?O:null};var q=b.parseJSON||function(n){return window["eval"]("("+n+")")};var k=function(S,Q,P){var O=S.getResponseHeader("content-type")||"",n=Q==="xml"||!Q&&O.indexOf("xml")>=0,R=n?S.responseXML:S.responseText;if(n&&R.documentElement.nodeName==="parsererror"){b.error&&b.error("parsererror")}if(P&&P.dataFilter){R=P.dataFilter(R,Q)}if(typeof R==="string"){if(Q==="json"||!Q&&O.indexOf("json")>=0){R=q(R)}else{if(Q==="script"||!Q&&O.indexOf("javascript")>=0){b.globalEval(R)}}}return R}}};b.fn.ajaxForm=function(c){if(this.length===0){var d={s:this.selector,c:this.context};if(!b.isReady&&d.s){a("DOM not ready, queuing ajaxForm");b(function(){b(d.s,d.c).ajaxForm(c)});return this}a("terminating; zero elements found by selector"+(b.isReady?"":" (DOM not ready)"));return this}return this.ajaxFormUnbind().bind("submit.form-plugin",function(f){if(!f.isDefaultPrevented()){f.preventDefault();b(this).ajaxSubmit(c)}}).bind("click.form-plugin",function(j){var i=j.target;var g=b(i);if(!(g.is(":submit,input:image"))){var f=g.closest(":submit");if(f.length==0){return}i=f[0]}var h=this;h.clk=i;if(i.type=="image"){if(j.offsetX!=undefined){h.clk_x=j.offsetX;h.clk_y=j.offsetY}else{if(typeof b.fn.offset=="function"){var k=g.offset();h.clk_x=j.pageX-k.left;h.clk_y=j.pageY-k.top}else{h.clk_x=j.pageX-i.offsetLeft;h.clk_y=j.pageY-i.offsetTop}}}setTimeout(function(){h.clk=h.clk_x=h.clk_y=null},100)})};b.fn.ajaxFormUnbind=function(){return this.unbind("submit.form-plugin click.form-plugin")};b.fn.formToArray=function(q){var p=[];if(this.length===0){return p}var d=this[0];var g=q?d.getElementsByTagName("*"):d.elements;if(!g){return p}var k,h,f,r,e,m,c;for(k=0,m=g.length;k<m;k++){e=g[k];f=e.name;if(!f){continue}if(q&&d.clk&&e.type=="image"){if(!e.disabled&&d.clk==e){p.push({name:f,value:b(e).val()});p.push({name:f+".x",value:d.clk_x},{name:f+".y",value:d.clk_y})}continue}r=b.fieldValue(e,true);if(r&&r.constructor==Array){for(h=0,c=r.length;h<c;h++){p.push({name:f,value:r[h]})}}else{if(r!==null&&typeof r!="undefined"){p.push({name:f,value:r})}}}if(!q&&d.clk){var l=b(d.clk),o=l[0];f=o.name;if(f&&!o.disabled&&o.type=="image"){p.push({name:f,value:l.val()});p.push({name:f+".x",value:d.clk_x},{name:f+".y",value:d.clk_y})}}return p};b.fn.formSerialize=function(c){return b.param(this.formToArray(c))};b.fn.fieldSerialize=function(d){var c=[];this.each(function(){var h=this.name;if(!h){return}var f=b.fieldValue(this,d);if(f&&f.constructor==Array){for(var g=0,e=f.length;g<e;g++){c.push({name:h,value:f[g]})}}else{if(f!==null&&typeof f!="undefined"){c.push({name:this.name,value:f})}}});return b.param(c)};b.fn.fieldValue=function(h){for(var g=[],e=0,c=this.length;e<c;e++){var f=this[e];var d=b.fieldValue(f,h);if(d===null||typeof d=="undefined"||(d.constructor==Array&&!d.length)){continue}d.constructor==Array?b.merge(g,d):g.push(d)}return g};b.fieldValue=function(c,j){var e=c.name,p=c.type,q=c.tagName.toLowerCase();if(j===undefined){j=true}if(j&&(!e||c.disabled||p=="reset"||p=="button"||(p=="checkbox"||p=="radio")&&!c.checked||(p=="submit"||p=="image")&&c.form&&c.form.clk!=c||q=="select"&&c.selectedIndex==-1)){return null}if(q=="select"){var k=c.selectedIndex;if(k<0){return null}var m=[],d=c.options;var g=(p=="select-one");var l=(g?k+1:d.length);for(var f=(g?k:0);f<l;f++){var h=d[f];if(h.selected){var o=h.value;if(!o){o=(h.attributes&&h.attributes.value&&!(h.attributes.value.specified))?h.text:h.value}if(g){return o}m.push(o)}}return m}return b(c).val()};b.fn.clearForm=function(){return this.each(function(){b("input,select,textarea",this).clearFields()})};b.fn.clearFields=b.fn.clearInputs=function(){return this.each(function(){var d=this.type,c=this.tagName.toLowerCase();if(d=="text"||d=="password"||c=="textarea"){this.value=""}else{if(d=="checkbox"||d=="radio"){this.checked=false}else{if(c=="select"){this.selectedIndex=-1}}}})};b.fn.resetForm=function(){return this.each(function(){if(typeof this.reset=="function"||(typeof this.reset=="object"&&!this.reset.nodeType)){this.reset()}})};b.fn.enable=function(c){if(c===undefined){c=true}return this.each(function(){this.disabled=!c})};b.fn.selected=function(c){if(c===undefined){c=true}return this.each(function(){var d=this.type;if(d=="checkbox"||d=="radio"){this.checked=c}else{if(this.tagName.toLowerCase()=="option"){var e=b(this).parent("select");if(c&&e[0]&&e[0].type=="select-one"){e.find("option").selected(false)}this.selected=c}}})};function a(){if(b.fn.ajaxSubmit.debug){var c="[jquery.form] "+Array.prototype.join.call(arguments,"");if(window.console&&window.console.log){window.console.log(c)}else{if(window.opera&&window.opera.postError){window.opera.postError(c)}}}}})(jQuery); |
11 ;(function($) { |
|
12 |
|
13 /* |
|
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(e) { |
|
22 e.preventDefault(); // <-- important |
|
23 $(this).ajaxSubmit({ |
|
24 target: '#output' |
|
25 }); |
|
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. |
|
40 */ |
|
41 |
|
42 /** |
|
43 * ajaxSubmit() provides a mechanism for immediately submitting |
|
44 * an HTML form using AJAX. |
|
45 */ |
|
46 $.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 |
|
57 var action = this.attr('action'); |
|
58 var url = (typeof action === 'string') ? $.trim(action) : ''; |
|
59 if (url) { |
|
60 // clean url (don't include hash vaue) |
|
61 url = (url.match(/^([^#]+)/)||[])[1]; |
|
62 } |
|
63 url = url || window.location.href || ''; |
|
64 |
|
65 options = $.extend(true, { |
|
66 url: url, |
|
67 success: $.ajaxSettings.success, |
|
68 type: this[0].getAttribute('method') || 'GET', // IE7 massage (see issue 57) |
|
69 iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' |
|
70 }, options); |
|
71 |
|
72 // hook for manipulating the form data before it is extracted; |
|
73 // convenient for use with rich editors like tinyMCE or FCKEditor |
|
74 var veto = {}; |
|
75 this.trigger('form-pre-serialize', [this, options, veto]); |
|
76 if (veto.veto) { |
|
77 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); |
|
78 return this; |
|
79 } |
|
80 |
|
81 // provide opportunity to alter form data before it is serialized |
|
82 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { |
|
83 log('ajaxSubmit: submit aborted via beforeSerialize callback'); |
|
84 return this; |
|
85 } |
|
86 |
|
87 var n,v,a = this.formToArray(options.semantic); |
|
88 if (options.data) { |
|
89 options.extraData = options.data; |
|
90 for (n in options.data) { |
|
91 if(options.data[n] instanceof Array) { |
|
92 for (var k in options.data[n]) { |
|
93 a.push( { name: n, value: options.data[n][k] } ); |
|
94 } |
|
95 } |
|
96 else { |
|
97 v = options.data[n]; |
|
98 v = $.isFunction(v) ? v() : v; // if value is fn, invoke it |
|
99 a.push( { name: n, value: v } ); |
|
100 } |
|
101 } |
|
102 } |
|
103 |
|
104 // give pre-submit callback an opportunity to abort the submit |
|
105 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { |
|
106 log('ajaxSubmit: submit aborted via beforeSubmit callback'); |
|
107 return this; |
|
108 } |
|
109 |
|
110 // fire vetoable 'validate' event |
|
111 this.trigger('form-submit-validate', [a, this, options, veto]); |
|
112 if (veto.veto) { |
|
113 log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); |
|
114 return this; |
|
115 } |
|
116 |
|
117 var q = $.param(a); |
|
118 |
|
119 if (options.type.toUpperCase() == 'GET') { |
|
120 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; |
|
121 options.data = null; // data is null for 'get' |
|
122 } |
|
123 else { |
|
124 options.data = q; // data is the query string for 'post' |
|
125 } |
|
126 |
|
127 var $form = this, callbacks = []; |
|
128 if (options.resetForm) { |
|
129 callbacks.push(function() { $form.resetForm(); }); |
|
130 } |
|
131 if (options.clearForm) { |
|
132 callbacks.push(function() { $form.clearForm(); }); |
|
133 } |
|
134 |
|
135 // perform a load on the target only if dataType is not provided |
|
136 if (!options.dataType && options.target) { |
|
137 var oldSuccess = options.success || function(){}; |
|
138 callbacks.push(function(data) { |
|
139 var fn = options.replaceTarget ? 'replaceWith' : 'html'; |
|
140 $(options.target)[fn](data).each(oldSuccess, arguments); |
|
141 }); |
|
142 } |
|
143 else if (options.success) { |
|
144 callbacks.push(options.success); |
|
145 } |
|
146 |
|
147 options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg |
|
148 var context = options.context || options; // jQuery 1.4+ supports scope context |
|
149 for (var i=0, max=callbacks.length; i < max; i++) { |
|
150 callbacks[i].apply(context, [data, status, xhr || $form, $form]); |
|
151 } |
|
152 }; |
|
153 |
|
154 // are there files to upload? |
|
155 var fileInputs = $('input:file', this).length > 0; |
|
156 var mp = 'multipart/form-data'; |
|
157 var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); |
|
158 |
|
159 // options.iframe allows user to force iframe mode |
|
160 // 06-NOV-09: now defaulting to iframe mode if file input is detected |
|
161 if (options.iframe !== false && (fileInputs || options.iframe || multipart)) { |
|
162 // hack to fix Safari hang (thanks to Tim Molendijk for this) |
|
163 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d |
|
164 if (options.closeKeepAlive) { |
|
165 $.get(options.closeKeepAlive, fileUpload); |
|
166 } |
|
167 else { |
|
168 fileUpload(); |
|
169 } |
|
170 } |
|
171 else { |
|
172 $.ajax(options); |
|
173 } |
|
174 |
|
175 // fire 'notify' event |
|
176 this.trigger('form-submit-notify', [this, options]); |
|
177 return this; |
|
178 |
|
179 |
|
180 // private function for handling file uploads (hat tip to YAHOO!) |
|
181 function fileUpload() { |
|
182 var form = $form[0]; |
|
183 |
|
184 if ($(':input[name=submit],:input[id=submit]', form).length) { |
|
185 // if there is an input with a name or id of 'submit' then we won't be |
|
186 // able to invoke the submit fn on the form (at least not x-browser) |
|
187 alert('Error: Form elements must not have name or id of "submit".'); |
|
188 return; |
|
189 } |
|
190 |
|
191 var s = $.extend(true, {}, $.ajaxSettings, options); |
|
192 s.context = s.context || s; |
|
193 var id = 'jqFormIO' + (new Date().getTime()), fn = '_'+id; |
|
194 var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ s.iframeSrc +'" />'); |
|
195 var io = $io[0]; |
|
196 |
|
197 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' }); |
|
198 |
|
199 var xhr = { // mock object |
|
200 aborted: 0, |
|
201 responseText: null, |
|
202 responseXML: null, |
|
203 status: 0, |
|
204 statusText: 'n/a', |
|
205 getAllResponseHeaders: function() {}, |
|
206 getResponseHeader: function() {}, |
|
207 setRequestHeader: function() {}, |
|
208 abort: function(status) { |
|
209 var e = (status === 'timeout' ? 'timeout' : 'aborted'); |
|
210 log('aborting upload... ' + e); |
|
211 this.aborted = 1; |
|
212 $io.attr('src', s.iframeSrc); // abort op in progress |
|
213 xhr.error = e; |
|
214 s.error && s.error.call(s.context, xhr, e, e); |
|
215 g && $.event.trigger("ajaxError", [xhr, s, e]); |
|
216 s.complete && s.complete.call(s.context, xhr, e); |
|
217 } |
|
218 }; |
|
219 |
|
220 var g = s.global; |
|
221 // trigger ajax global events so that activity/block indicators work like normal |
|
222 if (g && ! $.active++) { |
|
223 $.event.trigger("ajaxStart"); |
|
224 } |
|
225 if (g) { |
|
226 $.event.trigger("ajaxSend", [xhr, s]); |
|
227 } |
|
228 |
|
229 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) { |
|
230 if (s.global) { |
|
231 $.active--; |
|
232 } |
|
233 return; |
|
234 } |
|
235 if (xhr.aborted) { |
|
236 return; |
|
237 } |
|
238 |
|
239 var timedOut = 0, timeoutHandle; |
|
240 |
|
241 // add submitting element to data if we know it |
|
242 var sub = form.clk; |
|
243 if (sub) { |
|
244 var n = sub.name; |
|
245 if (n && !sub.disabled) { |
|
246 s.extraData = s.extraData || {}; |
|
247 s.extraData[n] = sub.value; |
|
248 if (sub.type == "image") { |
|
249 s.extraData[n+'.x'] = form.clk_x; |
|
250 s.extraData[n+'.y'] = form.clk_y; |
|
251 } |
|
252 } |
|
253 } |
|
254 |
|
255 // take a breath so that pending repaints get some cpu time before the upload starts |
|
256 function doSubmit() { |
|
257 // make sure form attrs are set |
|
258 var t = $form.attr('target'), a = $form.attr('action'); |
|
259 |
|
260 // update form attrs in IE friendly way |
|
261 form.setAttribute('target',id); |
|
262 if (form.getAttribute('method') != 'POST') { |
|
263 form.setAttribute('method', 'POST'); |
|
264 } |
|
265 if (form.getAttribute('action') != s.url) { |
|
266 form.setAttribute('action', s.url); |
|
267 } |
|
268 |
|
269 // ie borks in some cases when setting encoding |
|
270 if (! s.skipEncodingOverride) { |
|
271 $form.attr({ |
|
272 encoding: 'multipart/form-data', |
|
273 enctype: 'multipart/form-data' |
|
274 }); |
|
275 } |
|
276 |
|
277 // support timout |
|
278 if (s.timeout) { |
|
279 timeoutHandle = setTimeout(function() { timedOut = true; cb(true); }, s.timeout); |
|
280 } |
|
281 |
|
282 // add "extra" data to form if provided in options |
|
283 var extraInputs = []; |
|
284 try { |
|
285 if (s.extraData) { |
|
286 for (var n in s.extraData) { |
|
287 extraInputs.push( |
|
288 $('<input type="hidden" name="'+n+'" value="'+s.extraData[n]+'" />') |
|
289 .appendTo(form)[0]); |
|
290 } |
|
291 } |
|
292 |
|
293 // add iframe to doc and submit the form |
|
294 $io.appendTo('body'); |
|
295 io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false); |
|
296 form.submit(); |
|
297 } |
|
298 finally { |
|
299 // reset attrs and remove "extra" input elements |
|
300 form.setAttribute('action',a); |
|
301 if(t) { |
|
302 form.setAttribute('target', t); |
|
303 } else { |
|
304 $form.removeAttr('target'); |
|
305 } |
|
306 $(extraInputs).remove(); |
|
307 } |
|
308 } |
|
309 |
|
310 if (s.forceSync) { |
|
311 doSubmit(); |
|
312 } |
|
313 else { |
|
314 setTimeout(doSubmit, 10); // this lets dom updates render |
|
315 } |
|
316 |
|
317 var data, doc, domCheckCount = 50, callbackProcessed; |
|
318 |
|
319 function cb(e) { |
|
320 if (xhr.aborted || callbackProcessed) { |
|
321 return; |
|
322 } |
|
323 if (e === true && xhr) { |
|
324 xhr.abort('timeout'); |
|
325 return; |
|
326 } |
|
327 |
|
328 var doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document; |
|
329 if (!doc || doc.location.href == s.iframeSrc) { |
|
330 // response not received yet |
|
331 if (!timedOut) |
|
332 return; |
|
333 } |
|
334 io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false); |
|
335 |
|
336 var ok = true; |
|
337 try { |
|
338 if (timedOut) { |
|
339 throw 'timeout'; |
|
340 } |
|
341 |
|
342 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc); |
|
343 log('isXml='+isXml); |
|
344 if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) { |
|
345 if (--domCheckCount) { |
|
346 // in some browsers (Opera) the iframe DOM is not always traversable when |
|
347 // the onload callback fires, so we loop a bit to accommodate |
|
348 log('requeing onLoad callback, DOM not available'); |
|
349 setTimeout(cb, 250); |
|
350 return; |
|
351 } |
|
352 // let this fall through because server response could be an empty document |
|
353 //log('Could not access iframe DOM after mutiple tries.'); |
|
354 //throw 'DOMException: not available'; |
|
355 } |
|
356 |
|
357 //log('response detected'); |
|
358 xhr.responseText = doc.body ? doc.body.innerHTML : doc.documentElement ? doc.documentElement.innerHTML : null; |
|
359 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc; |
|
360 if (isXml) |
|
361 s.dataType = 'xml'; |
|
362 xhr.getResponseHeader = function(header){ |
|
363 var headers = {'content-type': s.dataType}; |
|
364 return headers[header]; |
|
365 }; |
|
366 |
|
367 var scr = /(json|script|text)/.test(s.dataType); |
|
368 if (scr || s.textarea) { |
|
369 // see if user embedded response in textarea |
|
370 var ta = doc.getElementsByTagName('textarea')[0]; |
|
371 if (ta) { |
|
372 xhr.responseText = ta.value; |
|
373 } |
|
374 else if (scr) { |
|
375 // account for browsers injecting pre around json response |
|
376 var pre = doc.getElementsByTagName('pre')[0]; |
|
377 var b = doc.getElementsByTagName('body')[0]; |
|
378 if (pre) { |
|
379 xhr.responseText = pre.textContent; |
|
380 } |
|
381 else if (b) { |
|
382 xhr.responseText = b.innerHTML; |
|
383 } |
|
384 } |
|
385 } |
|
386 else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) { |
|
387 xhr.responseXML = toXml(xhr.responseText); |
|
388 } |
|
389 |
|
390 data = httpData(xhr, s.dataType, s); |
|
391 } |
|
392 catch(e){ |
|
393 log('error caught:',e); |
|
394 ok = false; |
|
395 xhr.error = e; |
|
396 s.error && s.error.call(s.context, xhr, 'error', e); |
|
397 g && $.event.trigger("ajaxError", [xhr, s, e]); |
|
398 } |
|
399 |
|
400 if (xhr.aborted) { |
|
401 log('upload aborted'); |
|
402 ok = false; |
|
403 } |
|
404 |
|
405 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it |
|
406 if (ok) { |
|
407 s.success && s.success.call(s.context, data, 'success', xhr); |
|
408 g && $.event.trigger("ajaxSuccess", [xhr, s]); |
|
409 } |
|
410 |
|
411 g && $.event.trigger("ajaxComplete", [xhr, s]); |
|
412 |
|
413 if (g && ! --$.active) { |
|
414 $.event.trigger("ajaxStop"); |
|
415 } |
|
416 |
|
417 s.complete && s.complete.call(s.context, xhr, ok ? 'success' : 'error'); |
|
418 |
|
419 callbackProcessed = true; |
|
420 if (s.timeout) |
|
421 clearTimeout(timeoutHandle); |
|
422 |
|
423 // clean up |
|
424 setTimeout(function() { |
|
425 $io.removeData('form-plugin-onload'); |
|
426 $io.remove(); |
|
427 xhr.responseXML = null; |
|
428 }, 100); |
|
429 } |
|
430 |
|
431 var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+) |
|
432 if (window.ActiveXObject) { |
|
433 doc = new ActiveXObject('Microsoft.XMLDOM'); |
|
434 doc.async = 'false'; |
|
435 doc.loadXML(s); |
|
436 } |
|
437 else { |
|
438 doc = (new DOMParser()).parseFromString(s, 'text/xml'); |
|
439 } |
|
440 return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null; |
|
441 }; |
|
442 var parseJSON = $.parseJSON || function(s) { |
|
443 return window['eval']('(' + s + ')'); |
|
444 }; |
|
445 |
|
446 var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4 |
|
447 var ct = xhr.getResponseHeader('content-type') || '', |
|
448 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0, |
|
449 data = xml ? xhr.responseXML : xhr.responseText; |
|
450 |
|
451 if (xml && data.documentElement.nodeName === 'parsererror') { |
|
452 $.error && $.error('parsererror'); |
|
453 } |
|
454 if (s && s.dataFilter) { |
|
455 data = s.dataFilter(data, type); |
|
456 } |
|
457 if (typeof data === 'string') { |
|
458 if (type === 'json' || !type && ct.indexOf('json') >= 0) { |
|
459 data = parseJSON(data); |
|
460 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) { |
|
461 $.globalEval(data); |
|
462 } |
|
463 } |
|
464 return data; |
|
465 }; |
|
466 } |
|
467 }; |
|
468 |
|
469 /** |
|
470 * ajaxForm() provides a mechanism for fully automating form submission. |
|
471 * |
|
472 * The advantages of using this method instead of ajaxSubmit() are: |
|
473 * |
|
474 * 1: This method will include coordinates for <input type="image" /> elements (if the element |
|
475 * is used to submit the form). |
|
476 * 2. This method will include the submit element's name/value data (for the element that was |
|
477 * used to submit the form). |
|
478 * 3. This method binds the submit() method to the form for you. |
|
479 * |
|
480 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely |
|
481 * passes the options argument along after properly binding events for submit elements and |
|
482 * the form itself. |
|
483 */ |
|
484 $.fn.ajaxForm = function(options) { |
|
485 // in jQuery 1.3+ we can fix mistakes with the ready state |
|
486 if (this.length === 0) { |
|
487 var o = { s: this.selector, c: this.context }; |
|
488 if (!$.isReady && o.s) { |
|
489 log('DOM not ready, queuing ajaxForm'); |
|
490 $(function() { |
|
491 $(o.s,o.c).ajaxForm(options); |
|
492 }); |
|
493 return this; |
|
494 } |
|
495 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready() |
|
496 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)')); |
|
497 return this; |
|
498 } |
|
499 |
|
500 return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) { |
|
501 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed |
|
502 e.preventDefault(); |
|
503 $(this).ajaxSubmit(options); |
|
504 } |
|
505 }).bind('click.form-plugin', function(e) { |
|
506 var target = e.target; |
|
507 var $el = $(target); |
|
508 if (!($el.is(":submit,input:image"))) { |
|
509 // is this a child element of the submit el? (ex: a span within a button) |
|
510 var t = $el.closest(':submit'); |
|
511 if (t.length == 0) { |
|
512 return; |
|
513 } |
|
514 target = t[0]; |
|
515 } |
|
516 var form = this; |
|
517 form.clk = target; |
|
518 if (target.type == 'image') { |
|
519 if (e.offsetX != undefined) { |
|
520 form.clk_x = e.offsetX; |
|
521 form.clk_y = e.offsetY; |
|
522 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin |
|
523 var offset = $el.offset(); |
|
524 form.clk_x = e.pageX - offset.left; |
|
525 form.clk_y = e.pageY - offset.top; |
|
526 } else { |
|
527 form.clk_x = e.pageX - target.offsetLeft; |
|
528 form.clk_y = e.pageY - target.offsetTop; |
|
529 } |
|
530 } |
|
531 // clear form vars |
|
532 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100); |
|
533 }); |
|
534 }; |
|
535 |
|
536 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm |
|
537 $.fn.ajaxFormUnbind = function() { |
|
538 return this.unbind('submit.form-plugin click.form-plugin'); |
|
539 }; |
|
540 |
|
541 /** |
|
542 * formToArray() gathers form element data into an array of objects that can |
|
543 * be passed to any of the following ajax functions: $.get, $.post, or load. |
|
544 * Each object in the array has both a 'name' and 'value' property. An example of |
|
545 * an array for a simple login form might be: |
|
546 * |
|
547 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] |
|
548 * |
|
549 * It is this array that is passed to pre-submit callback functions provided to the |
|
550 * ajaxSubmit() and ajaxForm() methods. |
|
551 */ |
|
552 $.fn.formToArray = function(semantic) { |
|
553 var a = []; |
|
554 if (this.length === 0) { |
|
555 return a; |
|
556 } |
|
557 |
|
558 var form = this[0]; |
|
559 var els = semantic ? form.getElementsByTagName('*') : form.elements; |
|
560 if (!els) { |
|
561 return a; |
|
562 } |
|
563 |
|
564 var i,j,n,v,el,max,jmax; |
|
565 for(i=0, max=els.length; i < max; i++) { |
|
566 el = els[i]; |
|
567 n = el.name; |
|
568 if (!n) { |
|
569 continue; |
|
570 } |
|
571 |
|
572 if (semantic && form.clk && el.type == "image") { |
|
573 // handle image inputs on the fly when semantic == true |
|
574 if(!el.disabled && form.clk == el) { |
|
575 a.push({name: n, value: $(el).val()}); |
|
576 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); |
|
577 } |
|
578 continue; |
|
579 } |
|
580 |
|
581 v = $.fieldValue(el, true); |
|
582 if (v && v.constructor == Array) { |
|
583 for(j=0, jmax=v.length; j < jmax; j++) { |
|
584 a.push({name: n, value: v[j]}); |
|
585 } |
|
586 } |
|
587 else if (v !== null && typeof v != 'undefined') { |
|
588 a.push({name: n, value: v}); |
|
589 } |
|
590 } |
|
591 |
|
592 if (!semantic && form.clk) { |
|
593 // input type=='image' are not found in elements array! handle it here |
|
594 var $input = $(form.clk), input = $input[0]; |
|
595 n = input.name; |
|
596 if (n && !input.disabled && input.type == 'image') { |
|
597 a.push({name: n, value: $input.val()}); |
|
598 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); |
|
599 } |
|
600 } |
|
601 return a; |
|
602 }; |
|
603 |
|
604 /** |
|
605 * Serializes form data into a 'submittable' string. This method will return a string |
|
606 * in the format: name1=value1&name2=value2 |
|
607 */ |
|
608 $.fn.formSerialize = function(semantic) { |
|
609 //hand off to jQuery.param for proper encoding |
|
610 return $.param(this.formToArray(semantic)); |
|
611 }; |
|
612 |
|
613 /** |
|
614 * Serializes all field elements in the jQuery object into a query string. |
|
615 * This method will return a string in the format: name1=value1&name2=value2 |
|
616 */ |
|
617 $.fn.fieldSerialize = function(successful) { |
|
618 var a = []; |
|
619 this.each(function() { |
|
620 var n = this.name; |
|
621 if (!n) { |
|
622 return; |
|
623 } |
|
624 var v = $.fieldValue(this, successful); |
|
625 if (v && v.constructor == Array) { |
|
626 for (var i=0,max=v.length; i < max; i++) { |
|
627 a.push({name: n, value: v[i]}); |
|
628 } |
|
629 } |
|
630 else if (v !== null && typeof v != 'undefined') { |
|
631 a.push({name: this.name, value: v}); |
|
632 } |
|
633 }); |
|
634 //hand off to jQuery.param for proper encoding |
|
635 return $.param(a); |
|
636 }; |
|
637 |
|
638 /** |
|
639 * Returns the value(s) of the element in the matched set. For example, consider the following form: |
|
640 * |
|
641 * <form><fieldset> |
|
642 * <input name="A" type="text" /> |
|
643 * <input name="A" type="text" /> |
|
644 * <input name="B" type="checkbox" value="B1" /> |
|
645 * <input name="B" type="checkbox" value="B2"/> |
|
646 * <input name="C" type="radio" value="C1" /> |
|
647 * <input name="C" type="radio" value="C2" /> |
|
648 * </fieldset></form> |
|
649 * |
|
650 * var v = $(':text').fieldValue(); |
|
651 * // if no values are entered into the text inputs |
|
652 * v == ['',''] |
|
653 * // if values entered into the text inputs are 'foo' and 'bar' |
|
654 * v == ['foo','bar'] |
|
655 * |
|
656 * var v = $(':checkbox').fieldValue(); |
|
657 * // if neither checkbox is checked |
|
658 * v === undefined |
|
659 * // if both checkboxes are checked |
|
660 * v == ['B1', 'B2'] |
|
661 * |
|
662 * var v = $(':radio').fieldValue(); |
|
663 * // if neither radio is checked |
|
664 * v === undefined |
|
665 * // if first radio is checked |
|
666 * v == ['C1'] |
|
667 * |
|
668 * The successful argument controls whether or not the field element must be 'successful' |
|
669 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls). |
|
670 * The default value of the successful argument is true. If this value is false the value(s) |
|
671 * for each element is returned. |
|
672 * |
|
673 * Note: This method *always* returns an array. If no valid value can be determined the |
|
674 * array will be empty, otherwise it will contain one or more values. |
|
675 */ |
|
676 $.fn.fieldValue = function(successful) { |
|
677 for (var val=[], i=0, max=this.length; i < max; i++) { |
|
678 var el = this[i]; |
|
679 var v = $.fieldValue(el, successful); |
|
680 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) { |
|
681 continue; |
|
682 } |
|
683 v.constructor == Array ? $.merge(val, v) : val.push(v); |
|
684 } |
|
685 return val; |
|
686 }; |
|
687 |
|
688 /** |
|
689 * Returns the value of the field element. |
|
690 */ |
|
691 $.fieldValue = function(el, successful) { |
|
692 var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); |
|
693 if (successful === undefined) { |
|
694 successful = true; |
|
695 } |
|
696 |
|
697 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || |
|
698 (t == 'checkbox' || t == 'radio') && !el.checked || |
|
699 (t == 'submit' || t == 'image') && el.form && el.form.clk != el || |
|
700 tag == 'select' && el.selectedIndex == -1)) { |
|
701 return null; |
|
702 } |
|
703 |
|
704 if (tag == 'select') { |
|
705 var index = el.selectedIndex; |
|
706 if (index < 0) { |
|
707 return null; |
|
708 } |
|
709 var a = [], ops = el.options; |
|
710 var one = (t == 'select-one'); |
|
711 var max = (one ? index+1 : ops.length); |
|
712 for(var i=(one ? index : 0); i < max; i++) { |
|
713 var op = ops[i]; |
|
714 if (op.selected) { |
|
715 var v = op.value; |
|
716 if (!v) { // extra pain for IE... |
|
717 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value; |
|
718 } |
|
719 if (one) { |
|
720 return v; |
|
721 } |
|
722 a.push(v); |
|
723 } |
|
724 } |
|
725 return a; |
|
726 } |
|
727 return $(el).val(); |
|
728 }; |
|
729 |
|
730 /** |
|
731 * Clears the form data. Takes the following actions on the form's input fields: |
|
732 * - input text fields will have their 'value' property set to the empty string |
|
733 * - select elements will have their 'selectedIndex' property set to -1 |
|
734 * - checkbox and radio inputs will have their 'checked' property set to false |
|
735 * - inputs of type submit, button, reset, and hidden will *not* be effected |
|
736 * - button elements will *not* be effected |
|
737 */ |
|
738 $.fn.clearForm = function() { |
|
739 return this.each(function() { |
|
740 $('input,select,textarea', this).clearFields(); |
|
741 }); |
|
742 }; |
|
743 |
|
744 /** |
|
745 * Clears the selected form elements. |
|
746 */ |
|
747 $.fn.clearFields = $.fn.clearInputs = function() { |
|
748 return this.each(function() { |
|
749 var t = this.type, tag = this.tagName.toLowerCase(); |
|
750 if (t == 'text' || t == 'password' || tag == 'textarea') { |
|
751 this.value = ''; |
|
752 } |
|
753 else if (t == 'checkbox' || t == 'radio') { |
|
754 this.checked = false; |
|
755 } |
|
756 else if (tag == 'select') { |
|
757 this.selectedIndex = -1; |
|
758 } |
|
759 }); |
|
760 }; |
|
761 |
|
762 /** |
|
763 * Resets the form data. Causes all form elements to be reset to their original value. |
|
764 */ |
|
765 $.fn.resetForm = function() { |
|
766 return this.each(function() { |
|
767 // guard against an input with the name of 'reset' |
|
768 // note that IE reports the reset function as an 'object' |
|
769 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) { |
|
770 this.reset(); |
|
771 } |
|
772 }); |
|
773 }; |
|
774 |
|
775 /** |
|
776 * Enables or disables any matching elements. |
|
777 */ |
|
778 $.fn.enable = function(b) { |
|
779 if (b === undefined) { |
|
780 b = true; |
|
781 } |
|
782 return this.each(function() { |
|
783 this.disabled = !b; |
|
784 }); |
|
785 }; |
|
786 |
|
787 /** |
|
788 * Checks/unchecks any matching checkboxes or radio buttons and |
|
789 * selects/deselects and matching option elements. |
|
790 */ |
|
791 $.fn.selected = function(select) { |
|
792 if (select === undefined) { |
|
793 select = true; |
|
794 } |
|
795 return this.each(function() { |
|
796 var t = this.type; |
|
797 if (t == 'checkbox' || t == 'radio') { |
|
798 this.checked = select; |
|
799 } |
|
800 else if (this.tagName.toLowerCase() == 'option') { |
|
801 var $sel = $(this).parent('select'); |
|
802 if (select && $sel[0] && $sel[0].type == 'select-one') { |
|
803 // deselect all other options |
|
804 $sel.find('option').selected(false); |
|
805 } |
|
806 this.selected = select; |
|
807 } |
|
808 }); |
|
809 }; |
|
810 |
|
811 // helper fn for console logging |
|
812 // set $.fn.ajaxSubmit.debug to true to enable debug logging |
|
813 function log() { |
|
814 if ($.fn.ajaxSubmit.debug) { |
|
815 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,''); |
|
816 if (window.console && window.console.log) { |
|
817 window.console.log(msg); |
|
818 } |
|
819 else if (window.opera && window.opera.postError) { |
|
820 window.opera.postError(msg); |
|
821 } |
|
822 } |
|
823 }; |
|
824 |
|
825 })(jQuery); |