From 72c9c277094018620fdb4d78f316d91ca96d136c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Thu, 30 Dec 2010 16:22:09 +0100 Subject: [PATCH 1/7] initial tests upload --- successupload.php | 8 +++++ tests.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++ testsupload.html | 26 ++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 successupload.php create mode 100644 tests.js create mode 100644 testsupload.html diff --git a/successupload.php b/successupload.php new file mode 100644 index 00000000..669ecc04 --- /dev/null +++ b/successupload.php @@ -0,0 +1,8 @@ + $_FILES, + 'post' => $_POST, + 'get' => $_GET +); +echo json_encode($request); diff --git a/tests.js b/tests.js new file mode 100644 index 00000000..50c81d47 --- /dev/null +++ b/tests.js @@ -0,0 +1,76 @@ +QUnit.config.autostart = false; + +$(document).ready(function() { + $("#forms-ready").click(function(e) { + e.preventDefault(); + QUnit.start(); + }); +}); + +asyncTest("upload 1 file", function() { + var f = $("#form1").ajaxSubmit( + { + dataType: 'json', + success: function(data) { + start(); + ok(true); + ok(data.files.upload); + equal(data.files.upload.error, 0); + }, + error: function() { + start(); + ok(false, "test failed") + } + }); +}); + +asyncTest("upload error when wrong datatype", function() { + var f = $("#form1").attr('action', '404.php').ajaxSubmit( + { + dataType: 'json', + success: function(data) { + start(); + ok(false); + }, + error: function() { + start(); + ok(true, "error"); + } + }).attr('action', 'successupload.php'); +}); + +asyncTest("upload with iframe forced: iframe = true", function() { + var f = $("#form1").ajaxSubmit( + { + dataType: 'json', + iframe : true, + success: function(data) { + start(); + ok(true); + ok(data.files.upload); + }, + error: function() { + start(); + ok(false, "test failed") + } + }); +}); + +asyncTest("upload with extra params", function() { + var f = $("#form1").ajaxSubmit( + { + dataType: 'json', + iframe : true, + data: {chuck: "norris"}, + success: function(data) { + start(); + ok(true); + ok(data.files.upload); + equal(data.post.chuck, "norris"); + }, + error: function() { + start(); + ok(false, "test failed") + } + }); +}); diff --git a/testsupload.html b/testsupload.html new file mode 100644 index 00000000..738d8306 --- /dev/null +++ b/testsupload.html @@ -0,0 +1,26 @@ + + + + + jquery.form.js Unit Test + + + + + + + + +

jQuery form.js - Test Suite

+

+
+

+
    + +
    + + +
    + + + From 4463c1c43b71460b2f64cfe96893d8d0e00b4064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Thu, 30 Dec 2010 17:58:07 +0100 Subject: [PATCH 2/7] use FormData object when browser support fileapi (doesn't work for firefox 3.6) --- jquery.form.js | 94 ++++++++++++++++++++++++++++++++++---------------- tests.js | 1 + 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/jquery.form.js b/jquery.form.js index ddc5fab3..ac8b5d81 100644 --- a/jquery.form.js +++ b/jquery.form.js @@ -144,7 +144,7 @@ $.fn.ajaxSubmit = function(options) { } options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg - var context = options.context || options; // jQuery 1.4+ supports scope context + var context = options.context || options; // jQuery 1.4+ supports scope context for (var i=0, max=callbacks.length; i < max; i++) { callbacks[i].apply(context, [data, status, xhr || $form, $form]); } @@ -154,30 +154,36 @@ $.fn.ajaxSubmit = function(options) { var fileInputs = $('input:file', this).length > 0; var mp = 'multipart/form-data'; var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); - - // options.iframe allows user to force iframe mode - // 06-NOV-09: now defaulting to iframe mode if file input is detected - if (options.iframe !== false && (fileInputs || options.iframe || multipart)) { - // hack to fix Safari hang (thanks to Tim Molendijk for this) - // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d - if (options.closeKeepAlive) { - $.get(options.closeKeepAlive, fileUpload); - } - else { - fileUpload(); - } - } - else { - $.ajax(options); - } + var fileAPI = !!(fileInputs && $('input:file', this).get(0).files); + log("fileAPI :" + fileAPI); + var shouldUseFrame = (fileInputs || multipart) && !fileAPI; + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if (options.iframe !== false && (options.iframe || shouldUseFrame)) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) { + $.get(options.closeKeepAlive, fileUploadIframe); + } + else { + fileUploadIframe(); + } + } + else if ((fileInputs || multipart) && fileAPI) { + fileUploadXhr(); + } + else { + $.ajax(options); + } // fire 'notify' event this.trigger('form-submit-notify', [this, options]); return this; - // private function for handling file uploads (hat tip to YAHOO!) - function fileUpload() { + // private function for handling file uploads in iframe (hat tip to YAHOO!) + function fileUploadIframe() { var form = $form[0]; if ($(':input[name=submit],:input[id=submit]', form).length) { @@ -186,7 +192,7 @@ $.fn.ajaxSubmit = function(options) { alert('Error: Form elements must not have name or id of "submit".'); return; } - + var s = $.extend(true, {}, $.ajaxSettings, options); s.context = s.context || s; var id = 'jqFormIO' + (new Date().getTime()), fn = '_'+id; @@ -228,7 +234,7 @@ $.fn.ajaxSubmit = function(options) { } if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) { - if (s.global) { + if (s.global) { $.active--; } return; @@ -315,7 +321,7 @@ $.fn.ajaxSubmit = function(options) { else { setTimeout(doSubmit, 10); // this lets dom updates render } - + var data, doc, domCheckCount = 50; function cb() { @@ -324,7 +330,7 @@ $.fn.ajaxSubmit = function(options) { } $io.removeData('form-plugin-onload'); - + var ok = true; try { if (timedOut) { @@ -332,7 +338,7 @@ $.fn.ajaxSubmit = function(options) { } // extract the server response from the iframe doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document; - + var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc); log('isXml='+isXml); if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) { @@ -350,7 +356,7 @@ $.fn.ajaxSubmit = function(options) { //log('response detected'); cbInvoked = true; - xhr.responseText = doc.documentElement ? doc.documentElement.innerHTML : null; + xhr.responseText = doc.documentElement ? doc.documentElement.innerHTML : null; xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc; xhr.getResponseHeader = function(header){ var headers = {'content-type': s.dataType}; @@ -374,7 +380,7 @@ $.fn.ajaxSubmit = function(options) { else if (b) { xhr.responseText = b.innerHTML; } - } + } } else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) { xhr.responseXML = toXml(xhr.responseText); @@ -387,7 +393,7 @@ $.fn.ajaxSubmit = function(options) { xhr.error = e; $.handleError(s, xhr, 'error', e); } - + if (xhr.aborted) { log('upload aborted'); ok = false; @@ -430,6 +436,36 @@ $.fn.ajaxSubmit = function(options) { return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null; } } + + // private function for handling file uploads with xmlhttprequest (hat type to jquery-sexypost) + function fileUploadXhr() { + // this function will POST the contents of the selected form via XmlHttpRequest. + + var data = new FormData(); + + $("input:text, input:hidden, input:password, textarea", $form).each(function(){ + data.append($(this).attr("name"), $(this).val()); + }); + + $("input:file", $form).each(function(){ + var files = this.files; + for (i=0; i< max; i++) { el = els[i]; diff --git a/tests.js b/tests.js index 50c81d47..cae6dee2 100644 --- a/tests.js +++ b/tests.js @@ -1,4 +1,5 @@ QUnit.config.autostart = false; +$.fn.ajaxSubmit.debug = true; $(document).ready(function() { $("#forms-ready").click(function(e) { From 436e509acc69cf55dbc5fbaeb85e72e480e96b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Thu, 30 Dec 2010 20:59:59 +0100 Subject: [PATCH 3/7] save previous beforeSend when uploading with FormData --- jquery.form.js | 2 ++ tests.js | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/jquery.form.js b/jquery.form.js index ac8b5d81..136f3132 100644 --- a/jquery.form.js +++ b/jquery.form.js @@ -460,8 +460,10 @@ $.fn.ajaxSubmit = function(options) { data.append($(this).attr("name"), $(this).val()); }); options.data = null; + var originalBeforeSend = options.beforeSend; options.beforeSend = function(xhr, options) { // et toc ! options.data = data; + if (originalBeforeSend) originalBeforeSend(xhr, options); } $.ajax(options); } diff --git a/tests.js b/tests.js index cae6dee2..273b08af 100644 --- a/tests.js +++ b/tests.js @@ -9,14 +9,21 @@ $(document).ready(function() { }); asyncTest("upload 1 file", function() { + var beforeSendCalled = 0; var f = $("#form1").ajaxSubmit( { dataType: 'json', + beforeSend: function(xhr, options) { + beforeSendCalled++; + ok(xhr, "beforeSend xhr ok"); + ok(options, "beforeSend options ok"); + }, success: function(data) { start(); - ok(true); - ok(data.files.upload); - equal(data.files.upload.error, 0); + ok(true, "fine"); + equal(beforeSendCalled, 1, "beforeSend called"); + ok(data.files.upload, "file uploaded"); + equal(data.files.upload.error, 0, "file uploaded without errors"); }, error: function() { start(); From d62ee31a9ee2a6e6fb35605af9c63d5ca81d30b5 Mon Sep 17 00:00:00 2001 From: _gr_ Date: Fri, 7 Jan 2011 17:07:56 +0100 Subject: [PATCH 4/7] add brutal progress callback on xhr2 --- jquery.form.js | 7 ++++++- tests.js | 32 +++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/jquery.form.js b/jquery.form.js index 136f3132..38998cca 100644 --- a/jquery.form.js +++ b/jquery.form.js @@ -171,6 +171,7 @@ $.fn.ajaxSubmit = function(options) { } } else if ((fileInputs || multipart) && fileAPI) { + options.progress = options.progress || $.noop(); fileUploadXhr(); } else { @@ -442,7 +443,6 @@ $.fn.ajaxSubmit = function(options) { // this function will POST the contents of the selected form via XmlHttpRequest. var data = new FormData(); - $("input:text, input:hidden, input:password, textarea", $form).each(function(){ data.append($(this).attr("name"), $(this).val()); }); @@ -461,8 +461,13 @@ $.fn.ajaxSubmit = function(options) { }); options.data = null; var originalBeforeSend = options.beforeSend; + _options = options; options.beforeSend = function(xhr, options) { // et toc ! options.data = data; + xhr.upload.onprogress = function(event) { + console.log(event); + _options.progress(event.position, event.total); + } if (originalBeforeSend) originalBeforeSend(xhr, options); } $.ajax(options); diff --git a/tests.js b/tests.js index 273b08af..adfeac54 100644 --- a/tests.js +++ b/tests.js @@ -7,7 +7,7 @@ $(document).ready(function() { QUnit.start(); }); }); - +/* asyncTest("upload 1 file", function() { var beforeSendCalled = 0; var f = $("#form1").ajaxSubmit( @@ -82,3 +82,33 @@ asyncTest("upload with extra params", function() { } }); }); +*/ +asyncTest("upload 1 file and got at least a progress event ", function() { + var beforeSendCalled = 0; + var f = $("#form1").ajaxSubmit( + { + dataType: 'json', + beforeSend: function(xhr, options) { + beforeSendCalled++; + ok(xhr, "beforeSend xhr ok"); + ok(options, "beforeSend options ok"); + }, + success: function(data) { + start(); + ok(true, "fine"); + equal(beforeSendCalled, 1, "beforeSend called"); + ok(data.files.upload, "file uploaded"); + equal(data.files.upload.error, 0, "file uploaded without errors"); + }, + error: function() { + start(); + ok(false, "test failed") + }, + progress: function(position, size) { + start(); + ok(true, "No progress event received"); + console.log("position : " + position + " size : " + size) + } + }); +}); + From 7912eaeeded589f67db3ec879af7c55d73fe7bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Wed, 12 Jan 2011 18:45:26 +0100 Subject: [PATCH 5/7] fix detection of fileapi support with FormData --- jquery.form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.form.js b/jquery.form.js index 38998cca..be3d650d 100644 --- a/jquery.form.js +++ b/jquery.form.js @@ -154,7 +154,7 @@ $.fn.ajaxSubmit = function(options) { var fileInputs = $('input:file', this).length > 0; var mp = 'multipart/form-data'; var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); - var fileAPI = !!(fileInputs && $('input:file', this).get(0).files); + var fileAPI = !!(fileInputs && $('input:file', this).get(0).files && window.FormData); log("fileAPI :" + fileAPI); var shouldUseFrame = (fileInputs || multipart) && !fileAPI; From cd302b07dfea12be5c825a129519e0d5c4da6766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Wed, 12 Jan 2011 18:45:56 +0100 Subject: [PATCH 6/7] removed console.log --- jquery.form.js | 1 - 1 file changed, 1 deletion(-) diff --git a/jquery.form.js b/jquery.form.js index be3d650d..742034b6 100644 --- a/jquery.form.js +++ b/jquery.form.js @@ -465,7 +465,6 @@ $.fn.ajaxSubmit = function(options) { options.beforeSend = function(xhr, options) { // et toc ! options.data = data; xhr.upload.onprogress = function(event) { - console.log(event); _options.progress(event.position, event.total); } if (originalBeforeSend) originalBeforeSend(xhr, options); From 766fe8def1aa002887dc8f94591856baf0c25d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Mon, 24 Jan 2011 14:51:44 +0100 Subject: [PATCH 7/7] Add support of firefox 3.6 with a fake FormData object. See https://github.com/francois2metz/html5-formdata. --- jquery.form.js | 11 +++++++++++ testsupload.html | 1 + 2 files changed, 12 insertions(+) diff --git a/jquery.form.js b/jquery.form.js index 742034b6..0ff4ba6a 100644 --- a/jquery.form.js +++ b/jquery.form.js @@ -467,6 +467,17 @@ $.fn.ajaxSubmit = function(options) { xhr.upload.onprogress = function(event) { _options.progress(event.position, event.total); } + /** + * You can use https://github.com/francois2metz/html5-formdata for a fake FormData object + * Only work with Firefox 3.6 + */ + if (data.fake) { + xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary="+ data.boundary); + // with fake FormData object, we must use sendAsBinary + xhr.send = function(data) { + xhr.sendAsBinary(data.toString()); + } + } if (originalBeforeSend) originalBeforeSend(xhr, options); } $.ajax(options); diff --git a/testsupload.html b/testsupload.html index 738d8306..3f748085 100644 --- a/testsupload.html +++ b/testsupload.html @@ -6,6 +6,7 @@ +