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 + '%'

                );

        });

    }

},

 

posted @ 2014-07-03 21:24  cart55free99  阅读(9247)  评论(4编辑  收藏  举报