Chinaunix首页 | 论坛 | 博客
  • 博客访问: 398965
  • 博文数量: 69
  • 博客积分: 1984
  • 博客等级: 上尉
  • 技术积分: 953
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-28 00:43
个人简介

学无所长,一事无成

文章分类

全部博文(69)

文章存档

2015年(19)

2014年(14)

2013年(9)

2012年(17)

2010年(10)

我的朋友

分类: JavaScript

2015-05-26 11:41:49

原文地址:
备注:通读全文,实践过后才发现此文有些内容已过时,大坑慎入。

Project owner: Mike Wilcox
Author: Mike Wilcox
since: 1.6

A widget that adds functionality to a standard HTML file input which allows file uploading to a server. The widget hides the actual uploader and substitutes a dijit.form.Button, so that the file input matches the rest of the user interface. If the browser supports the HTML5 file input specification, that functionality is used. If it is not supported (IE or older browsers) plugins are available to enhance the Uploader.

dojox.form.Uploader 用于取代 dojox.form.FileUploader,并且功能进行了增强。The multiple problems created by Flash are avoided because it is not used in Mozilla and Webkit browsers. Support for FileUploader will cease as of 1.6, but the code will remain until 2.0 for backwards compatibility.

  • Uploader 是一个 Dijit, 并且支持所有其他 form widget 的相同功能。FileUploader (注意是另外一个 Digit)也这样宣称,但实际上它是有问题的,因为它是基于 Flash plugin 实现随后的 build process and dependency。
  • Uploader 可以放置在 form 中 “正常工作”。实际上 Uploader 是将 form 表单的 submit event 封装起来,并取代了 form 的职能,实现 field 输入值的收集,以及利用 form 表单的 action 属性上传数据到服务端(或者将 form 表单的url 属性传递给 Uploader)。
  • 支持 tabIndex 属性。
  • 有一个附加小部件 dojox.form.uploader.FileList 可以显示选择的文件集合,并显示上传进度。

当采用编程方式使用 Uploader 时,你不能使用 require() 中的引用参数 - 你必须使用全局参数。这是因为plugins 重定义了原始的 Uploader class,而 AMD 是不会有时机这样干的。因此,以下代码不会正常工作:

  1. require(['dojox/form/Uploader', 'dojox/form/uploader/plugins/Flash'], function(Uploader){
  2.     myUploader = Uploader(...);
  3. })
直到 Dojo 2.0 版本前(2.0 就解决这个问题了嘛,看来很值得期待!),你都必须这样使用:
  1. require(['dojox/form/Uploader', 'dojox/form/uploader/plugins/Flash'], function(Uploader){
  2.     myUploader = new dojox.form.Uploader(...); // disregard reference argument
  3. })

因为 Uploader 使用当前的 form 表单,因此你必须将表单的 enctype 属性设置为 “multipart/form-data”,这样才能使其上传文件。

Uploader 仅仅是模拟实现的 HTML5 file inputs 的功能,因此 FileUploader 中的某些功能在 Uploader 中尚未实现。比如一个比较重要的功能缺失,往当前已选择的文件集中添加或删除文件。目前已有计划要实现这个功能。

HTML5 规范中定义了 File 掩码(用于限定选择的文件类型),但由于浏览器功能缺失。此功能不支持。

使用指南

Uploader 有几种不同的使用方式。

无插件

Uploader 如不指定 plugins 则采用 “Form” 表单模式。这种模式不会采用 Ajax 方式上传。这种方式要求你自己处理上传,不管你是通过 Ajax 处理,还是通过 POST后刷新整个页面。在测试文件中(dojo子目录下有)只有 form POSTs 会正常工作,页面会跳转到 UploadFile.php 。Multiple file selection is used in browsers that support it, and in the others, multiple selections are added one at a time by adding file inputs.

使用 HTML5 Plugin 的

HTML5 上传插件不支持 IE, it is more of a base class for IFrame or Flash, or used in cases where IE is not a requirement. 对于支持 HTML5 的浏览器,这个 puglin 提供 Ajax 上传的能力。

使用 IFrame Plugin 的

IFrame 插件在 IE 中使用 IFrame 进行上传。其他浏览器将使用 HTML5 插件,除非强行指定了 force=”iframe 。

使用 Flash Plugin 的

Flash 插件将在非 HTML5 浏览器中使用 SWF 进行上传。所有其他的浏览器将使用 HTML5 插件,除非强行指定了force=”flash” 。只所以要提供 force=”flash” ,是因为 Flash 中有些功能 HTML5 尚不具备。但还是不推荐采用此种方式,因为 Mozilla 和 Webkit 使用 Flash 插件会遇到很多问题:

  • Changing the display style on the DOM element that holds a SWF will reset the SWF. This makes it difficult to support Flash in Dijit Tabs and Dialog boxes.
  • Initialization of a SWF is not always consistent and occasionally throws errors.
  • HTTPS support in Mozilla is problematic.

这些问题在 IE 中都不会发生。

- 标签式

要使用 Uploader很简单,先 require ,然后在表单中的 input 上添加上合适的 data-dojo-type 属性: 

  1. <script>
  2. dojo.require("dojox.form.Uploader");
  3. </script>

  4. <form method="post" action="UploadFile.php" id="myForm" enctype="multipart/form-data" >
  5.    <input name="uploadedfile" multiple="true" type="file" data-dojo-type="dojox.form.Uploader" label="Select Some Files" id="uploader" />
  6.    <input type="submit" label="Submit" data-dojo-type="dijit.form.Button" />
  7. </form>

插件本身会初始化,因此使用它只需要 require 就行了。下面这个例子使用 IFrame plugin,它继承自 HTML5 插件。因此 HTML5 风格的 file inputs 也可用于 IFrame 使用的任何地方。 (the HTML is exactly the same as the previous example):

  1. dojo.require("dojox.form.Uploader");
  2. dojo.require("dojox.form.uploader.plugins.IFrame")
  1. <form method="post" action="UploadFile.php" id="myForm" enctype="multipart/form-data" >
  2.    <input name="uploadedfile" multiple="true" type="file" data-dojo-type="dojox.form.Uploader" label="Select Some Files" id="uploader" />
  3.    <input type="submit" label="Submit" data-dojo-type="dijit.form.Button" />
  4. </form>
 

- 编程式

下面例子演示通过编程创建 Uploader,使用的是 Flash plugin:

  1. dojo.require("dojox.form.Uploader");
  2. dojo.require("dojox.form.uploader.plugins.Flash");

  3. var u = new dojox.form.Uploader({label:"Programmatic Uploader", multiple:true, uploadOnSelect:true, url:"UploadFile.php"});
  4. dojo.byId("myDiv").appendChild(u.domNode)

Uploader 的一个主要属性是 “name”,它被用作创建的 file input 的 name 属性,或者是 Flash 绑定到的每个文件的  field ame 。缺省名字是 “uploadedfile”,同指明的 UploadFile.php  协同工作。HTML5 规范定义此属性是一个 array-like ,因此在 field name 后用方括号括住。如果使用的是支持 HTML5 的浏览器,Uploader 会添加括号。同时会在 name 后添加 “s” 帮助 server script 识别 file data。如果是使用 Flash plugin,Uploader 会在 name 后添加 “Flash”,or whatever the “flashFieldName” property is set to。如果是使用的标准 file input (“Form” 或 IFrame),name 保持原样。

几种不同的 name 属性样式有助于 server page 知道发送的是什么数据从而采用适当的处理方式。

  • flashFieldName 是用于追加到 name 属性后的文本。这个有助于 server 了解它要处理的是什么类型的文件数据。
  • multiple 是否允许选择多个文件。
  • url 如果表单中没有指明 action ,那需要提供一个  url 进行文件上传。
  • label 指明button 上显示的文字。
  • tabIndex 缺省为 “0”。修改这个值可以可以调整页面的 tab order 顺序。

Flash plugin 还有一些针对上传文件的其他属性。

- 方法

因为 Uploader 没有 plugins 将无法上传,其仅有如下方法:

  • reset 清除选择文件列表。
  • getFileList 返回一个代表 file data 的对象数组

启动 plugin ,如下方法就可用了:

  • upload 此方法上传文件以及作为参数传入的任何数据。
  • submit 此方法上传文件,并在 form element 作为参数传入时,将其转化为 JavaScript object。

服务器端上传的文件会存入一个临时目录。一个误解是你会以为需要服务端语言来实现这些,其实不是。接收上传文件是由你的 server 处理的,比如 Apache 或 IIS 。而将文件从临时目录放到某个 upload 目录下就是你的任务了。不管是 HTML 或 Flash,上传任务都将由多个 transfer 完成(the payload is done with a multipart transfer)。一旦上传结束,server 端脚本(server script) 将被调用。

在 Flash 方式上传多个文件时,图片会并行上传(除非指明 deferredUploading=true),但 server script 却只能一次接收一个文件。因此如果上传了五个文件,则 server script 会被调用五次。

在 HTML 方式上传多个文件时,所有文件一次性一起上传,在全部五个文件都成功上传到临时目录后,server script 仅会被调用一次。对于传统的 HTML (表单模式或 IFrame plugin),每个文件引用的方式采用数字序列:uploadedfile0, uploadedfile1, uploadedfile2, 等等。对于 HTML5 方式的上传,server script 会寻找 “uploadedfiles” (末尾多了个 “s”哦)。Uploader 会在 name 后添加方括号以匹配规范(“uploadedfiles[]”)。最终结果就是上传的多个文件将作为数组传递给 server script。 

With a multipart request the POST data is the contents for the first part and the uploaded files is an array (or an object) of each additional part. 参考你的 server 文档了解如何引用 files (下一节使用 PHP 示例)。

返回数据需要按照特定要求进行格式化,对于 Flash 和 HTML 格式有所不同。参考下面 Server Side Return Data 一节。

- 服务器端的 PHP 代码

Uploader 小部件中带有一个可运行的 PHP 文件,dojox/form/tests/UploadFile.php, to use as a reference for how your server side code should work. UploadFile.php 有两个依赖文件, dojo/tests/resources/JSON.php 用于将返回结果转化为 JSON 字符串,dojox/form/tests/cLog.php 用来记录日志信息到一个文本文件,存储位置相对于 PHP 文件而定。

UploadFile.php 以如下四种方式之一运行:

  1. 接收来自 Flash 的一个或多个文件 (uploadedfilesFlash)
  2. 接收来自 HTML 的一个文件 (uploadedfiles0)
  3. 接收来自 HTML 的多个文件 (uploadedfiles0, uploadedfiles1, 等等...)
  4. 接收来自 HTML5 的一个文件数组 (uploadedfiles[])

PHP 文件会检查 header 搜索 Uploader 的参数集合。不管你如何设定参数,server 端必须对应匹配。

对于 HTML uploader 所对应的 field name 同样运作。唯一不同就是你使用 HTML 进行多个文件上传时, this essentially continues to add fileInputs to the form, and in doing so, appends numbers to the fileInput field names, starting with ‘0’. That’s why one file fieldname will look like “myFieldName” but two files will look like [ “myFieldName0”, “myFieldName1” ] to the server side code.

- 服务器端返回的数据

服务器端如何返回数据并不难,但很重要。如果设置错误,会导致报错,以及 Uploader 的 “onComplete” 无法触发。

注意 对于 Flash uploader 和 HTML uploader 需要不同格式的返回数据。你需要检查 post 数据来判定使用哪种类型的返回数据

如果在 post 数据中发现了 uploadedfilesFlash ,而且客户端使用的是 Flash,那么需要的所有返回数据就是一个 key-value 字符串,在函数末尾,简单地将其返回即可。Flash 会将这些 key-value 对存入一个 object 传递给 javascript。同时你需要插入一个 exit  或者其他什么指定用来终止剩余代码的执行。例子如下:


$data .='file='.$file.',name='.$name.',width='.$width.',height='.$height.',type='.$type;
echo($data);
exit;

对非 PHP 程序员,做点解释:


$name = 文件名,比如 "PIC01.jpg"
$file = 文件以及路径名,比如 "uploaded/PIC01.jpg"
$width, $height = the dimensions (if you are working with images)
$type = 文件扩展名 - JPG, GIF, PNG, 等.

最终返回给 Flash 的数据会类似如下:


"file=uploaded/PIC01.jpg,name=PIC01.jpg,width=320,height=240,type=jpg"

This string should be returned, or printed, or echoed.

You can add an error key if one file was in error; say if it was not of the correct type. This error code or message will be returned in the onComplete dataArray. It's important to note that as far as the Uploader is concerned, everything was a success. It's up to your custom code to test for this error.

The return string with an error might look like:


"file=uploaded/PIC01.jpg,name=PIC01.jpg,width=320,height=240,type=jpg,error=Not recognized file type"

You can also send back arbitrary parameters from your server-side script using this comma-delimited format. For example, adding variables foo and abc:


"file=uploaded/PIC01.jpg,name=PIC01.jpg,width=320,height=240,type=jpg,foo=bar,abc=123"

Then you can access these variables in the client-side functions using dataArray[i].additionalParams.foo and dataArray[i].additionalParams.abc.

如果使用 IFrame plugin ,客户端的代码比较复杂,as reading back from an iframe presents problems. 要想跨浏览器精准识别 iframe 的返回数据,代码需要包裹在 '; tpl += '

'; tpl += '
'; tpl += '
'; tpl += ''; tpl += '
'; tpl += '
'; tpl += '
'; $('.z_move_comment').html(''); $(this).parents('.Blog_right1_8').find('.z_move_comment').html(tpl).show(); }); //引用的评论提交 $('#quota_sbumit').live('click' , function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } var bid = $(this).attr('bid'); var tid = $(this).attr('tid');//被引用人的id var qid = $(this).attr('cid');//引用的id var url = $(this).attr('url'); var text = $('#rmsg').val(); var tname = $(this).attr('tname'); if(text == '' || text=='文明上网,理性发言...') { showErrorMsg('评论内容不能为空!' , '消息提示'); return false; } else { if(mb_strlen(text) > 1000){ showErrorMsg('评论内容不能超过500个汉字' , '消息提示'); return false; } } $.ajax({ type: "post", url: url , data: {'bid': bid , 'to' : tid , 'qid' : qid , 'text': text , 'tname' : tname }, dataType: 'json', success: function(data){ if(data.code == 1){ var tpl = '
'; tpl+= ''; tpl+= '
'; tpl+= '

' + data.info.username + '' + data.info.dateline + '

'; tpl+= '

' + data.info.quota.username + ':'+ data.info.quota.content + '

'; tpl+= '

' + data.info.content + '

回复 |  删除 |  举报
'; tpl+= ''; tpl+= '
'; $('#replyList .Blog_right1_8:first').before(tpl); $('.z_move_comment').html('').hide(); } else if(data.code == -1){ //showErrorMsg(data.info , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); } }, error: function(){//请求出错处理 } }); }); //底部发表评论 $('#submitmsg').click(function(){ if(allowComment == 1) { showErrorMsg('该博文不允许评论' , '消息提示'); return false; } var bid = $(this).attr('bid'); var toid = $(this).attr('toid'); var text = $('#reply').val(); var url = $(this).attr('url'); if(text == '' || text=='文明上网,理性发言...') { showErrorMsg('评论内容不能为空!' , '消息提示'); return false; } else { if(mb_strlen(text) > 1000){ showErrorMsg('评论内容不能超过500个汉字' , '消息提示'); return false; } } $.ajax({ type: "post", url: url , data: {'bid': bid , 'to' : toid ,'text': text}, dataType: 'json', success: function(data){ if(data.code == 1) { var tpl = '
'; tpl += ''; tpl += '
'; tpl += '

' + data.info.username + '' + data.info.dateline + '

'; tpl += '

' + data.info.content + '

'; tpl += '
回复 |  删除 |  举报
'; tpl += ''; tpl += '
'; $('.Blog_tit3:first').after(tpl); $('#reply').val('文明上网,理性发言...'); } else if(data.code == -1) { showErrorMsg(data.info , '消息提示'); } }, error: function(){//请求出错处理 } }); }); //底部评论重置 $('#reset_comment').click(function(){ $('#reply').val('文明上网,理性发言...'); }); //取消回复 $('#qx_comment').live('click' , function(){ $('.z_move_comment').html('').hide(); }); $('#rmsg, #reply').live({ focus:function(){ if($(this).val() == '文明上网,理性发言...'){ $(this).val(''); } }, blur:function(){ if($(this).val() == ''){ $(this).val('文明上网,理性发言...'); } } }); //删除留言确认 $('.comment_del_mark').live('click' , function(){ var url = $(this).attr('url'); asyncbox.confirm('删除留言','确认', function(action){ if(action == 'ok') { location.href = url; } }); }); //删除时间确认 $('.del_article_id').click(function(){ var delurl = $(this).attr('delurl'); asyncbox.confirm('删除文章','确认', function(action){ if(action == 'ok') { location.href = delurl; } }); }); /* //字数限制 $('#rmsg, #reply').live('keyup', function(){ var id = $(this).attr('id'); var left = Util.calWbText($(this).val(), 500); var eid = '#errmsg'; if(id == 'reply') eid = '#rerrmsg'; if (left >= 0) $(eid).html('您还可以输入' + left + '字'); else $(eid).html('您已超出' + Math.abs(left) + ''); }); */ //加载表情 $('#face').qqFace({id : 'facebox1', assign : 'reply', path : '/image/qqface/'}); $('#mface').qqFace({id : 'facebox', assign : 'rmsg', path:'/image/qqface/'}); /* $('#class_one_id').change(function(){ alert(123213); var id = parseInt($(this).val() , 10); if(id == 0) return false; $('.hidden_son_class span').each(function( index , dom ){ if( dom.attr('cid') == id ) { } }); }); */ //转载文章 var turn_url = "/blog/viewClassPart.html"; $('#repost_bar').click(function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } var id = $(this).attr('bid'); asyncbox.open({ id : 'turn_class_thickbox', url : turn_url, title : '转载文章', width : 330, height : 131, scroll : 'no', data : { 'id' : id }, callback : function(action){ if(action == 'close'){ $.cover(false); } } }); }); /* //转发文章 $('#repost_bar').live('click' , function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } var bid = $(this).attr('bid'); var url = $(this).attr('url'); asyncbox.confirm('转载文章','确认', function(action){ if(action == 'ok'){ $.ajax({ type:"POST", url:url, data: { 'bid' : bid }, dataType: 'json', success:function(msg){ if(msg.error == 0){ showSucceedMsg('转发成功!', '消息提示'); }else if(msg.error == 1){ //location.href = '/index.php?r=site/login'; showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); }else{ showErrorMsg(msg.error_content, '消息提示'); } } }); } }); }); */ });