jQuery File Upload介绍.............................................. 2
实现基本原理...................................................... 3
什么是XHR?...................................................... 4
最简模型......................................................... 4
XHR响应为Json时IE的下载BUG................................... 5
需要哪些JS?.................................................. 6
jQuery File Upload UI构成元素.................................... 7
全局控制按钮 (必须)............................................ 7
整体上传进度 (可选)............................................ 8
文件显示容器 (必须)............................................ 8
文件预览模板 (必须)............................................ 8
上传后文件回调显示模板 (必须).................................... 9
JS模板引擎....................................................... 9
什么是模板引擎?............................................... 9
模板引擎有什么优势?........................................... 10
tmpl.min.js................................................ 11
上传过程............................................................ 12
PHP文件上传原理................................................. 12
上传过程........................................................ 12
构造函数__construct.......................................... 14
initialize()................................................... 15
post()......................................................... 15
handle_file_upload()........................................... 16
更新进度条.......................................................... 17
获取xhr对象.................................................... 18
jquery.fileupload-ui.js........................................ 18
jQuery File Upload介绍
jQuery File Upload是一个非常优秀的上传组件,主要使用了XHR作为上传方式,并且利用了相当多的现代浏览器功能,所以可以实现诸如多选批量上传、超大文件上传、图片预览、拖拽上传、上传进度显示、跨域上传等功能 (完全无需flash的依赖)
github地址
https://github.com/blueimp/jQuery-File-Upload/
运行截图
图片轮播
实现基本原理
简单的来说,它就是在文件上传的基础上增加了一些其他的功能:利用Canvas to Blob和 canvas显示图片预览。使用video和audio标签来显示音频和视频预览。利用XHR的特性无需刷新Server端就能得到上传文件的信息。
上传本身和普通上传没有区别,都是从tmp文件夹中读取信息,然后移动文件到指定的地方。
至于进度条,则是通过监听XHR的progress事件得到
什么是XHR?
XHR的全称是XMLHttpRequest。XMLHttpRequest对象可以在不向服务器提交整个页面的情况下,实现局部更新网页。当页面全部加载完毕后,客户端通过该对象向服务器请求数据,服务器端接受数据并处理后,向客户端反馈数据。 XMLHttpRequest 对象提供了对 HTTP 协议的完全的访问,包括做出 POST 和 HEAD 请求以及普通的 GET 请求的能力。XMLHttpRequest 可以同步或异步返回 Web 服务器的响应,并且能以文本或者一个 DOM 文档形式返回内容。尽管名为 XMLHttpRequest,它并不限于和 XML 文档一起使用:它可以接收任何形式的文本文档。XMLHttpRequest 对象是为 AJAX 的 Web 应用程序架构的一项关键功能。
最简模型
来看一下最基本的Demo,没有进度条,也没有缩略图。但是它完成了最核心的功能,无刷新上传。
必须包括以下文件
jQuery核心库,建议使用jQuery 1.8以上版本
js/vendor/jquery.ui.widget.js : jQuery UI Widget
js/jquery.iframe-transport.js : 扩展iframe数据传输
js/jquery.fileupload.js : jQuery File Upload核心类
js/cors/jquery.xdr-transport.js 在IE下应载入此文件解决跨域问题
此时只需要加载一个上传按钮
<input id="fileupload" type="file" name="files[]" data-url="server/php/" multiple>
以及一行代码
$('#fileupload').fileupload();
就完成了一个最基本的上传组件。这个最简单的上传组件可以将选中的文件以表单形式提交到data-url约定的URL,同时提供了足够多的设置和基础事件可供扩展。
想要在完成后显示上传的文件信息?
$('#fileupload').fileupload({
url: url,
dataType: 'json',
done: function (e, data) {
$.each(data.result.files, function (index, file) {
$('<p/>').text(file.name).appendTo('#files');
});
}
})
想要加上进度条?只需要再加一个progress属性(这个是显示全部文件上传进度)
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .bar').css(
'width',
progress + '%'
);
}
当然了 既然这里都用到了元素选择器 那么我也得加一个容器来显示progress
<div id="progress">
<div class="bar" style="width: 0%;"></div>
</div>
通过点击按钮来上传而不是在File Chooser中选择了文件就立刻上传。
$(function () {
$('#fileupload').fileupload({
dataType: 'json',
add: function (e, data) {
data.context = $('<button/>').text('Upload')
.appendTo(document.body)
.click(function () {
data.context = $('<p/>').text('Uploading...').replaceAll($(this));
data.submit();
});
},
done: function (e, data) {
data.context.text('Upload finished.');
}
});
});
XHR响应为Json时IE的下载BUG
这里需要特别注意的是,由于jQuery File Upload都是采用XHR在传递数据,服务器端返回的通常是JSON格式的响应,但是IE会将这些JSON响应误认为是文件传输,然后直接弹出下载框询问是否需要下载。
解决这个问题的方法是必须将相应的Http Head从
Content-Type: application/json
更改为
Content-Type: text/plain
需要哪些JS?
既然是以jQuery为基础的,那么肯定需要有jQuery 另外还需要jQueryUI
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="js/vendor/jquery.ui.widget.js"></script>
JS模板引擎 用于渲染上传、下载的项目
<script src="http://blueimp.github.io/JavaScript-Templates/js/tmpl.min.js"></script>
Load Image 预览图片
<script src="http://blueimp.github.io/JavaScript-Load-Image/js/load-image.min.js"></script>
图片剪裁需要用到Canvas to Blob plugin
<script src="http://blueimp.github.io/JavaScript-Canvas-to-Blob/js/canvas-to-blob.min.js"></script>
Bootstrap JS 用于响应式设计
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
图片carousel
<script src="http://blueimp.github.io/Gallery/js/jquery.blueimp-gallery.min.js"></script>
Iframe Transport 对于不支持 XHR file 上传的浏览器需要
<script src="js/jquery.iframe-transport.js"></script>
负责上传 必需
<script src="js/jquery.fileupload.js"></script>
上传处理 准备预览图 audio预览等
<script src="js/jquery.fileupload-process.js"></script>
待上传image 生成预览图 并剪裁 调用canvas.toBlob 绘制预览图
<script src="js/jquery.fileupload-image.js"></script>
待上传声音文件 生成声音试听(audio标签)
<script src="js/jquery.fileupload-audio.js"></script>
待上传声音文件 生成视频预览(video标签)
<script src="js/jquery.fileupload-video.js"></script>
文件检测 类型 大小 数量等
<script src="js/jquery.fileupload-validate.js"></script>
上传UI
<script src="js/jquery.fileupload-ui.js"></script>
设置上传参数 路径
<script src="js/main.js"></script>
jQuery File Upload UI构成元素
UI的部件都是硬编码的HTML class,无法更改。核心的几个部件为
全局控制按钮 (必须)
<div class="fileupload-buttonbar">
<span class="fileinput-button"><input type="file" name="files[]" multiple></span>
<button type="submit" class="start">Start upload</button>
<button type="reset" class="cancel">Cancel upload</button>
<button type="button" class="delete">Delete</button>
<input type="checkbox" class="toggle">
</div>
最外层容器为.fileupload-buttonbar,内部包含
文件选择按钮 .fileinput-button (必须),内部必须包裹一个input:file
开始上传按钮 .start
取消上传按钮 .cancel
删除按钮 .delete
文件勾选按钮 .toggle
整体上传进度 (可选)
<div class="fileupload-progress">
<div class="progress">
<div class="bar" style="width:0%;"></div>
</div>
<div class="progress-extended"></div>
</div>
最外层容器为.fileupload-progress,内部包含
上传进度条容器.progress
上传进度条 .bar
上传进度文本 .progress-extended
文件显示容器 (必须)
<div class="files"></div>
文件预览模板 (必须)
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<div class="template-upload">
{% if (file.error) { %}
<div class="error">{%=file.error%}</div>
{% } else { %}
<div class="preview"><span class="fade"></span></div>
<div class="name"><span>{%=file.name%}</span></div>
<div class="size"><span>{%=o.formatFileSize(file.size)%}</span></div>
<div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" style="height:5px;"><div class="bar" style="width:0%;"></div></div>
<span class="start">
{% if (!o.options.autoUpload) { %}
<button>Start Upload</button>
{% } %}
</span>
{% } %}
<span class="cancel"><button>Cancel</button></span>
</div>
{% } %}
</script>
上传后文件回调显示模板 (必须)
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<div class="template-download">
{% if (file.error) { %}
<div class="error">{%=file.error%}</div>
<span class="cancel"><button class="btn btn-block"><i class="icon-ban-circle"></i>Cancel</span>
{% } else { %}
<div class="preview"><img src="{%=file.thumbnail_url%}"></div>
<div class="name"><span>{%=file.name%}</span></div>
<div class="size"><span>{%=o.formatFileSize(file.size)%}</span></div>
<div class="delete"><button data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">Delete</button>
</div>
{% } %}
</div>
{% } %}
</script>
JS模板引擎
什么是模板引擎?
什么是模板引擎,说的简单点,就是一个字符串中有几个变量待定。比如
var tpl = 'Hei, my name is <%name%>, and I\'m <%age%> years old.';
通过模板引擎函数把数据塞进去,
var data = {
"name": "Barret Lee",
"age": "20"
};
var result = tplEngine(tpl, data);
//Hei, my name is Barret Lee, and I'm 20 years old.
模板引擎有什么优势?
在使用JavaScript进行前端开发的时候,做的最多的事情,除了dealing with dom以外,就是围绕json数据的操作了。而数据操作最麻烦的就是用json生成dom对象了,通常我们会写一堆for, switch, if之类的代码来支持data生成view, 这样的代码一般会像:
var data = [{name: 'Claire', sex: 'female', age: 18, flag: true},
{name: 'Mark', sex: 'male', age: 25, flag: true},
{name: 'Dennis', sex: 'male', age: 32, flag: false},
{name: 'Tracy', sex: 'female', age: 23, flag: true},
{name: 'Wane', sex: 'male', age: 18, flag: true}],
html = ['<ul>'], item;
for (var i = 0, l = data.length; i < l; i++) {
item = data[i];
if (item.flag) {
html.push('<li>');
switch (item.sex) {
case 'male':
html.push('<span style=”color: blue”>');
break;
case 'female':
default:
html.push('<span style=”color: red”>');
break;
}
html.push('name: ' + item.name + ', age: ' + item.age);
html.push('</span></li>');
}
}
html = html.push('</ul>').join('');
这样做,随着数据结构越来越复杂很快你就会发现代码越来越臃肿,而且html完全嵌入代码,几乎不可维护。实际上,将展现逻辑同数据分开在服务器端脚本中是很容易的事情,因为服务器端脚本一般都支持模板技术。相信大家对<% %>之类的标记已经熟悉到烦了。模板语言的好处是能用一种灵活、易扩展的方式来将展现标记(如 html)、数据(如json)和控制代码(如javascript)分离。现在也有不少浏览器端用javascript实现的模板引擎,如extjs的xtemplate,jTemplate,TrimPath等。实现的思路都一样:将一段定义好的模板代码,像<% do something %>之类的最后形成为js代码;然后将json data作为这段js代码的输入,最终产生一段需要的文本
tmpl.min.js
Github https://github.com/blueimp/JavaScript-Templates/blob/master/README.md
FileUpload使用的是作者本人开发的模板引擎, 这个引擎非常的轻量,不到1kb,并且同样快速且强大,而且它无需任何库的依赖。兼容像node.js这样的服务端的环境,也可以被RequireJS这样的模块加载工具使用。
添加一个script tag,其type指定为"text/x-tmpl",以及一个唯一的id并将你的模板定义作为这其内容。
<script type="text/x-tmpl"id="tmpl-demo">
<h3>{%=o.title%}</h3>
<p>Releasedunderthe
<ahref="{%=o.license.url%}">{%=o.license.name%}</a>.</p>
<h4>Features</h4>
<ul>
{%for(vari=0;i<o.features.length;i++){%}
<li>{%=o.features[i]%}</li>
{%}%}
</ul>
</script>
这里面的变量o是模板中指向数据的变量。当然了这里的o是可以自己设置的,可以去作者的官方文档中获取查看详情。
创建一个对象作为模板中得数据
var data = {
"title": "JavaScript Templates",
"license": {
"name": "MIT license",
"url": "http://www.opensource.org/licenses/MIT"
},
"features": [
"lightweight & fast",
"powerful",
"zero dependencies"
]
};
这里的o就是整个Json对象
有了上述代码就可以准备使用JS模板了,通过调用tmpl()函数以及你的模板id就可以生成结果。
document.getElementById("result").innerHTML = tmpl("tmpl-demo", data);
window.tmpl返回经过模板处理后的HTML语句
<h3>JavaScript Templates</h3>
<p>Released under the <a href="http://www.opensource.org/licenses/MIT">MIT license</a>.</p>
<h4>Features</h4>
<ul>
<li>lightweight & fast</li>
<li>powerful</li>
<li>zero dependencies</li>
</ul>
上传过程
UI工作过程
用户点击.fileinput-button选择要上传的文件(多个)
文件选择后,文件信息被整理为数组置入文件预览模板#template-upload
模板引擎循环处理文件信息并生成模板.template-upload
每生成一个模板,模板就被插入到文件显示容器.files的最后。
用户点击上传按钮.start上传,文件信息被转换为XHR请求至服务器端
UI获得服务器端生成JSON响应文件
JSON响应信息也被整理成数组置入回调显示模板#template-download
模板引擎循环处理文件信息并生成模板.template-download
每生成一个模板,会将此模板替换对应的.template-upload部分
PHP文件上传原理
在PHP中,文件上传功能是使用PHP提供的文件函数来实现的。PHP的文件上传实际上是移动文件。一旦你选择了一个文件并上传,就会在系统的临时目录中有这么一个文件,然后调用函数将该文件移动到指定的目录就可以了。
要实现文件的上传,需要在表单标签中设置enctype="multipart/form-data"。提交后就可以通过$_FILES来得到相应的文件信息。
比如
<input name="userfile" type="file">
那么
$_FILES['userfile']['name']:客户端机器文件的原名称。
$_FILES['userfile']['type']:文件的MIME类型,例如"image/gif"。
$_FILES['userfile']['tmp_name']:文件被上传后在服务端储存的临时文件名。
tmp_name就是待上传的文件的临时文件夹的路径,最后执行
move_uploaded_file ( $file ['tmp_name'], $dest );
上传完毕。
上传过程
在main.js中设置了上传路径为server/php/ 默认请求server/php/下的index.php
这个上传组件还支持很多种不同的服务端,你可以看到server下还有go python nodejs这几种。
咱们还是先看应用最广泛的php吧。进入server/php下发现index.php只有三句话
error_reporting(E_ALL | E_STRICT);
require('UploadHandler.php');
$upload_handler = new UploadHandler();
很显然UploadHandler是在UploadHandler.php中定义的一个类
构造函数__construct
构造函数定义了一些常量,最后构造函数调用了initialize()。
得到该php脚本相对于localhost所在目录
'script_url' => $this->get_full_url().'/',
得到当前目录 (文件目录)
'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/files/',
上传文件路径(相对于服务器)
'upload_url' => $this->get_full_url().'/files/',
针对linux mac OS 设置文件夹权限
'mkdir_mode' => 0755,
根据这个名字获取HTML页面中input中的内容
'param_name' => 'files',
设置跨域访问 *表示允许跨域访问
'access_control_allow_origin' => '*',
规定了允许请求的方法的类型
'access_control_allow_methods' => array(
'OPTIONS',
'HEAD',
'GET',
'POST',
'PUT',
'PATCH',
'DELETE'
),
允许上传的文件类型 这个正则表示的所有后缀的文件都可以
'accept_file_types' => '/.+$/i',
initialize()
根据请求的method调用不同的函数 默认情况下会以POST的形式上传
如果你是对某个已经上传的附件删除 发送请求的类型将会是DELETE
通过$_SERVER['REQUEST_METHOD']得到请求的类型
上传是以POST的方式,故进入到post()中
post()
根据参数判断是否是删除请求,如果是,则跳转到DELETE中去
if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') {
return $this->delete($print_response);
}
获取待上传的文件的信息,从$_FILES['files']中取数据并存入$upload中
$upload = isset($_FILES[$this->options['param_name']]) ?
$_FILES[$this->options['param_name']] : null;
得到的$upload格式如下,其中tmp_name就是临时文件的路径
array(5) {
["name"]=>array(1) { [0]=> string(6) "11.PNG"}
["type"]=>array(1) { [0]=> string(9) "image/png"}
["tmp_name"]=>array(1) { [0]=> string(26) "/private/var/tmp/phpfptIaB"}
["error"]=>array(1) { [0]=> int(0) }
["size"]=>array(1) { [0]=> int(28527) }
}
在得到了文件的基本信息之后就调用handle_file_upload来处理要上传的文件
if ($upload && is_array($upload['tmp_name'])) {
foreach ($upload['tmp_name'] as $index => $value) {
$files[] = $this->handle_file_upload(
$upload['tmp_name'][$index],
$file_name ? $file_name : $upload['name'][$index],
$size ? $size : $upload['size'][$index],
$upload['type'][$index],
$upload['error'][$index],
$index,
$content_range
);
}
}
handle_file_upload()
获取唯一的文件名, 由于所有的文件都放在files中 不可避免的会出现同名文件
$file->name = $this->get_file_name($uploaded_file, $name, $size, $type, $error, $index, $content_range);
比如files中有abc.jpg这个文件,而用户又再次上传了一个名为abc.jpg的文件,通过get_file_name就可以返回abc(1).jpg作为写入的文件名。之后如果由用户再次上传了abc.jpg,该函数将返回abc(2).jpg
检测文件的合法性,
$this->validate($uploaded_file, $file, $error, $index)
创建上传文件夹并赋予读写权限
if (!is_dir($upload_dir)) {
mkdir($upload_dir, $this->options['mkdir_mode'], true);
}
获取真实的上传路径
$file_path = $this->get_upload_path($file->name);
格式为files/filename(index).extension
移动文件
move_uploaded_file($uploaded_file, $file_path);
准备返回文件数据(上传后的文件名,文件大小,以及相对服务器的地址用于下载)
$file->url = $this->get_download_url($file->name);
if ($this->is_valid_image_file($file_path)) {
$this->handle_image_file($file_path, $file);
}
最后输出响应,为JSON格式,用于页面中显示文件
{
"files": [
{
"name": "PNG.png",
"size": 42971,
"type": "image/png",
"url": "http://localhost/UPLOAD/server/php/files/PNG.png",
"thumbnailUrl": "http://localhost/UPLOAD/server/php/files/thumbnail/PNG.png",
"deleteUrl": "http://localhost/UPLOAD/server/php/?file=PNG.png",
"deleteType": "DELETE"
}
]
}
更新进度条
在jQuery 1.5+的版本上,如果通过XMLHttpRequest上传文件,可以通过监听XMLHttpRequest.upload对象的progress事件来查看进度。
只要在$.ajax请求中拿到原始的XMLHttpRequest,然后监听upload对象的progress事件.
获取xhr对象
在jquery.fileuoload.js中绑定progress事件
_initProgressListener: function (options) {
var that = this,
xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
if (xhr.upload) {
$(xhr.upload).bind('progress', function (e) {
......
});
options.xhr = function () {
return xhr;
};
}
},
jquery.fileupload-ui.js
前面已经提到fileupload-ui是用来操作上传时DOM的更新,其progress就是用来更新每一项上传的进度。
progress: function (e, data) {
if (e.isDefaultPrevented()) {
return false;
}
var progress = Math.floor(data.loaded / data.total * 100);
if (data.context) {
data.context.each(function () {
$(this).find('.progress')
.attr('aria-valuenow', progress)
.children().first().css(
'width',
progress + '%'
);
});
}
},