在实现文件上传时刚开始用的是HTML5的FormData,简单优雅又快捷。但老大说不行,IE不支持不行,好想抽他。
jQuery的上传插件很多,有用HTML5的也有用iFrame的。
内部另一项目用的是lagos(http://lagoscript.org,应该是个泥轰精)开发的版本,拿来试用一下,发现居然没有处理失败的回调函数
原来的代码:
/*
* jQuery.upload v1.0.2
*
* Copyright (c) 2010 lagos
* Dual licensed under the MIT and GPL licenses.
*
* http://lagoscript.org
*/
(function($) {
var uuid = 0;
$.fn.upload = function(url, data, callback, type) {
var self = this, inputs, checkbox, checked,
iframeName = 'jquery_upload' + ++uuid,
iframe = $('<iframe name="' + iframeName + '" style="position:absolute;top:-9999px" />').appendTo('body'),
form = '<form target="' + iframeName + '" method="post" enctype="multipart/form-data" />';
if ($.isFunction(data)) {
type = callback;
callback = data;
data = {};
}
checkbox = $('input:checkbox', this);
checked = $('input:checked', this);
form = self.wrapAll(form).parent('form').attr('action', url);
// Make sure radios and checkboxes keep original values
// (IE resets checkd attributes when appending)
checkbox.removeAttr('checked');
checked.attr('checked', true);
inputs = createInputs(data);
inputs = inputs ? $(inputs).appendTo(form) : null;
form.submit(function() {
iframe.load(function() {
var data = handleData(this, type),
checked = $('input:checked', self);
form.after(self).remove();
checkbox.removeAttr('checked');
checked.attr('checked', true);
if (inputs) {
inputs.remove();
}
setTimeout(function() {
iframe.remove();
if (type === 'script') {
$.globalEval(data);
}
if (callback) {
callback.call(self, data);
}
}, 0);
});
}).submit();
return this;
};
function createInputs(data) {
return $.map(param(data), function(param) {
return '<input type="hidden" name="' + param.name + '" value="' + param.value + '"/>';
}).join('');
}
function param(data) {
if ($.isArray(data)) {
return data;
}
var params = [];
function add(name, value) {
params.push({name:name, value:value});
}
if (typeof data === 'object') {
$.each(data, function(name) {
if ($.isArray(this)) {
$.each(this, function() {
add(name, this);
});
} else {
add(name, $.isFunction(this) ? this() : this);
}
});
} else if (typeof data === 'string') {
$.each(data.split('&'), function() {
var param = $.map(this.split('='), function(v) {
return decodeURIComponent(v.replace(/\+/g, ' '));
});
add(param[0], param[1]);
});
}
return params;
}
function handleData(iframe, type) {
var data, contents = $(iframe).contents().get(0);
if ($.isXMLDoc(contents) || contents.XMLDocument) {
return contents.XMLDocument || contents;
}
data = $(contents).find('body').html();
switch (type) {
case 'xml':
data = parseXml(data);
break;
case 'json':
data = window.eval('(' + data + ')');
break;
}
return data;
}
function parseXml(text) {
if (window.DOMParser) {
return new DOMParser().parseFromString(text, 'application/xml');
} else {
var xml = new ActiveXObject('Microsoft.XMLDOM');
xml.async = false;
xml.loadXML(text);
return xml;
}
}
})(jQuery);
改造了一下,主要是添加失败处理。
iFrame的onload事件一旦发生,说明response已经返回,所以在解析得到响应数据后,可以立刻清除iFrame。
iFrame的HTTP状态码无法判读,因此只能从响应数据判断上传成功还是失败。
服务器端返回的响应应当遵守下面的规则:
1.如果上传失败,HTTP状态码应为500,具体错误信息放在ResponseBody。这是为了兼容普通的XHR请求。
2.如果上传成功,HTTP状态码应为200,返回信息为json格式。
如果上传失败,返回的HTTP响应状态码为200,ResponseBody中放false(json)或者错误信息的字符串(非json),这个修改版的上传插件仍然能用。但服务器端如果要适配XHR文件上传,XHR的失败处理将变得非常丑陋。
/*
* jQuery.upload v1.0.2
*
* Copyright (c) 2010 lagos
* Dual licensed under the MIT and GPL licenses.
* http://lagoscript.org
*
* modified by kuyur(https://kuyur.net)
*/
(function($) {
var uuid = 0;
/*
* expectedType: only allow JSON now.
*/
$.fn.upload = function(url, expectedType, onSuccess, onFailure, sendingData) {
var self = this, inputs, checkbox, checked,
iframeName = 'jquery_upload' + ++uuid,
iframe = $('<iframe name="' + iframeName + '" style="display:none;visibility:hidden;height:0px;" />').appendTo('body'),
form = '<form target="' + iframeName + '" method="post" enctype="multipart/form-data" />';
checkbox = $('input:checkbox', this);
checked = $('input:checked', this);
form = self.wrapAll(form).parent('form').attr('action', url);
// Make sure radios and checkboxes keep original values
// (IE resets checkd attributes when appending)
checkbox.removeAttr('checked');
checked.attr('checked', true);
if (typeof sendingData !== "object" && typeof sendingData !== "string") {
sendingData = {};
}
inputs = createInputs(sendingData);
inputs = inputs ? $(inputs).appendTo(form) : null;
form.submit(function() {
iframe.load(function() {
var iframeDocument = $(this).contents().get(0),
response = $(iframeDocument).text(),
data = handleResponse(response, expectedType),
checked = $('input:checked', self);
form.after(self).remove();
checkbox.removeAttr('checked');
checked.attr('checked', true);
if (inputs) {
inputs.remove();
}
setTimeout(function() {
iframe.remove();
if (data) {
onSuccess.call(self, data);
} else {
onFailure.call(self, response);
}
}, 0);
});
}).submit();
return this;
};
function createInputs(data) {
return $.map(param(data), function(param) {
return '<input type="hidden" name="' + param.name + '" value="' + param.value + '"/>';
}).join('');
}
function param(data) {
if ($.isArray(data)) {
return data;
}
var params = [];
function add(name, value) {
params.push({name:name, value:value});
}
if (typeof data === 'object') {
$.each(data, function(name) {
if ($.isArray(this)) {
$.each(this, function() {
add(name, this);
});
} else {
add(name, $.isFunction(this) ? this() : this);
}
});
} else if (typeof data === 'string') {
$.each(data.split('&'), function() {
var param = $.map(this.split('='), function(v) {
return decodeURIComponent(v.replace(/\+/g, ' '));
});
add(param[0], param[1]);
});
}
return params;
}
function handleResponse(response, expectedType) {
if (typeof response !== 'string') {
return false;
}
try {
return jQuery.parseJSON(response);
} catch (e) {
return false;
}
}
})(jQuery);
用法:
$('#input_id_or_.wrap_class').upload('upload_url', 'json', function(responseObj){}, function(errorMsg){})
expectedType目前只支持json,余甚至想从参数列中去掉它了,对余来说,支持json就足够。处理成功的回调函数的参数是解析后的javascript对象,处理失败的回调函数的参数是失败信息的原始字符串,这两点一定要注意。
具体例子(注意input的name属性一定要和服务器端一致):
<div>
<input type="file" id="upload-file" class="form-wrap" name="file">
<span id="upload-status" class="form-wrap">Please select a file.</span>
</div>
<div class="main">
<div class="left">Name:</div>
<div class="right" id="result-name"></div>
<div class="clear"></div>
<div class="left">Type:</div>
<div class="right" id="result-type"></div>
<div class="clear"></div>
<div class="left">Size:</div>
<div class="right" id="result-size"></div>
<div class="clear"></div>
</div>
$(function(){
$('#upload-file').change(function(){
if (!$(this).val()) {
$('#upload-status').text('Please select a file.');
$('#result-name').text('');
$('#result-type').text('');
$('#result-size').text('');
return;
}
$('#upload-status').html('<img src="wait.gif"> Uploading...');
$('.form-wrap').upload('upload_file.php', 'json',
function(responseObj){
$('#upload-status').text('Upload finished.');
$('#result-name').text(responseObj.Name);
$('#result-type').text(responseObj.Type);
$('#result-size').text(responseObj.Size);
}, function(errorMsg){
$('#upload-status').text('Upload failed. Message from server: ' + errorMsg);
$('#result-name').text('');
$('#result-type').text('');
$('#result-size').text('');
}
);
});
});
余写的HTML5上传插件,很简单的代码。使用了FormData对象以及input的files属性,支持IE10/Chrome/Firefox。
响应仅支持json格式,支持多文件上传(没测试),支持处理上传进度。onSuccess和onFailure的参数同上,onProgress的参数为浏览器内置对象event。
/*
* html5 file upload.
* by kuyur (https://kuyur.net)
*/
(function($) {
/*
* multiple-files upload supported.
* Default file-field will be "file{n} if they are not set."
*/
$.fn.upload5 = function(url, onSuccess, onFailure, onProgress) {
if (typeof FormData === 'undefined') {
return;
}
var inputs = this,
form = new FormData(),
count = 0;
inputs.each(function(index) {
var el = this;
if (el.files && el.files.length > 0) {
form.append(el.name || 'file'+index, el.files[0]);
count++;
}
});
if (count > 0) {
var xhr = new XMLHttpRequest();
xhr.open("post", url, true);
if (typeof onProgress === "function") {
xhr.upload.onprogress = onProgress;
}
xhr.onreadystatechange = function() {
var me = this;
if (me.readyState == 4) {
if (me.status >= 200 && me.status < 300) {
onSuccess($.parseJSON(me.responseText));
} else {
onFailure(me.responseText);
}
}
};
xhr.send(form);
}
};
})(jQuery);
例子(注意input的name属性一定要和服务器端一致):
<div>
<input type="file" id="upload-file-html5" name="file">
<span id="upload-status-html5">Please select a file.</span>
</div>
<div class="main">
<div class="left">Upload status:</div>
<div class="right" id="result-uploadstatus-html5"></div>
<div class="clear"></div>
<div class="left">Name:</div>
<div class="right" id="result-name-html5"></div>
<div class="clear"></div>
<div class="left">Type:</div>
<div class="right" id="result-type-html5"></div>
<div class="clear"></div>
<div class="left">Size:</div>
<div class="right" id="result-size-html5"></div>
<div class="clear"></div>
</div>
$(function(){
$('#upload-file-html5').change(function() {
if (!$(this).val()) {
$('#upload-status-html5').text('Please select a file.');
$('#result-uploadstatus-html5').text('');
$('#result-name-html5').text('');
$('#result-type-html5').text('');
$('#result-size-html5').text('');
return;
}
$('#upload-status-html5').html('<img src="wait.gif"> Uploading...');
$('#upload-file-html5').upload5('upload_file.php',
function(responseObj){
$('#upload-status-html5').text('Upload finished.');
$('#result-name-html5').text(responseObj.Name);
$('#result-type-html5').text(responseObj.Type);
$('#result-size-html5').text(responseObj.Size);
}, function(errorMsg){
$('#upload-status-html5').text('Upload failed. Message from server: ' + errorMsg);
$('#result-uploadstatus-html5').text('');
$('#result-name-html5').text('');
$('#result-type-html5').text('');
$('#result-size-html5').text('');
}, function(event) {
if (event.lengthComputable) {
var completed = (event.loaded / event.total * 100 | 0) + "%";
$('#result-uploadstatus-html5').text(completed);
}
}
);
});
});
用PHP写的一个简单服务器端:
<?php
/*
* A simple file server demo.
* by kuyur(https://kuyur.net)
*/
$error_types = array(
1=>'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
'The uploaded file was only partially uploaded.',
'No file was uploaded.',
6=>'Missing a temporary folder.',
'Failed to write file to disk.',
'A PHP extension stopped the file upload.'
);
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
header("HTTP/1.1 500 Internal Server Error");
die('Error: Only post method allowed.');
}
if (empty($_FILES["file"])) {
header("HTTP/1.1 500 Internal Server Error");
die('Error: File field should name as "file". Or you may need to check post_max_size in php.ini.');
}
if ($_FILES["file"]["error"] > 0) {
header("HTTP/1.1 500 Internal Server Error");
die("Error: " . $error_types[$_FILES['file']['error']]);
} else {
echo '{"Name": "' . $_FILES["file"]["name"] . '", "Type": "' . $_FILES["file"]["type"] . '", "Size": "' . number_format($_FILES["file"]["size"] / 1024, 1) . 'Kb"}';
}
?>
源码下载(压缩包内的jQuery的版本有点老,替换成新版应该也没问题)jquery-upload
评论