jQuery File Upload

时间:2023-03-08 17:31:00
jQuery File Upload

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 &amp; 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 + '%'

);

});

}

},