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是一个非常优秀的上传组件,主要使用了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
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 >

    <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
>

{% 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
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”>

<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 + ‘%’

               
);

        });

    }

},

 

Related Posts

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注