Skip to content

Xhr upload file #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 82 additions & 29 deletions jquery.form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand All @@ -154,30 +154,37 @@ $.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 && window.FormData);
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) {
options.progress = options.progress || $.noop();
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) {
Expand All @@ -186,7 +193,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;
Expand Down Expand Up @@ -228,7 +235,7 @@ $.fn.ajaxSubmit = function(options) {
}

if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
if (s.global) {
if (s.global) {
$.active--;
}
return;
Expand Down Expand Up @@ -315,7 +322,7 @@ $.fn.ajaxSubmit = function(options) {
else {
setTimeout(doSubmit, 10); // this lets dom updates render
}

var data, doc, domCheckCount = 50;

function cb() {
Expand All @@ -324,15 +331,15 @@ $.fn.ajaxSubmit = function(options) {
}

$io.removeData('form-plugin-onload');

var ok = true;
try {
if (timedOut) {
throw 'timeout';
}
// 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 == '')) {
Expand All @@ -350,7 +357,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};
Expand All @@ -374,7 +381,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);
Expand All @@ -387,7 +394,7 @@ $.fn.ajaxSubmit = function(options) {
xhr.error = e;
$.handleError(s, xhr, 'error', e);
}

if (xhr.aborted) {
log('upload aborted');
ok = false;
Expand Down Expand Up @@ -430,6 +437,52 @@ $.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<files.length; i++) data.append($(this).attr("name"), files[i]);
});

$("select option:selected", $form).each(function(){
data.append($(this).parent().attr("name"), $(this).val());
});

$("input:checked", $form).each(function(){
data.append($(this).attr("name"), $(this).val());
});
options.data = null;
var originalBeforeSend = options.beforeSend;
_options = options;
options.beforeSend = function(xhr, options) { // et toc !
options.data = data;
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);
}

};

/**
Expand Down Expand Up @@ -462,7 +515,7 @@ $.fn.ajaxForm = function(options) {
log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
return this;
}

return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
e.preventDefault();
Expand Down Expand Up @@ -526,7 +579,7 @@ $.fn.formToArray = function(semantic) {
if (!els) {
return a;
}

var i,j,n,v,el,max,jmax;
for(i=0, max=els.length; i < max; i++) {
el = els[i];
Expand Down
8 changes: 8 additions & 0 deletions successupload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

$request = array(
'files' => $_FILES,
'post' => $_POST,
'get' => $_GET
);
echo json_encode($request);
114 changes: 114 additions & 0 deletions tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
QUnit.config.autostart = false;
$.fn.ajaxSubmit.debug = true;

$(document).ready(function() {
$("#forms-ready").click(function(e) {
e.preventDefault();
QUnit.start();
});
});
/*
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, "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")
}
});
});

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")
}
});
});
*/
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)
}
});
});

27 changes: 27 additions & 0 deletions testsupload.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>jquery.form.js Unit Test</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></script>
<script type="text/javascript" src="https://github.com/francois2metz/html5-formdata/raw/master/formdata.js"></script>
<script type="text/javascript" src="jquery.form.js"></script>
<script type="text/javascript" src="tests.js"></script>
</head>
<body>

<h1 id="qunit-header">jQuery form.js - Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>

<form enctype="multipart/form-data" method="post" action="successupload.php" id="form1">
<input type="file" name="upload">
<button id="forms-ready">Ready</button>
</form>

</body>
</html>