最近工作中遇到上传文件问题,主要需求是一步点击上传,兼容ie8+,当时用的dojox/form/uploader控件,这两天扒了一下源码,明白了原理拿出来分享一下。
总体思路如下:
1、对于支持XMLHttpRequest2的浏览器使用FormData通过ajax上传
2、对于ie10一下的浏览器使用iframe异步上传,还需后台服务器做相应处理,这部分也是dojo/request/iframe上传文件的原理。
 
一、使用FormData上传文件
  FormData最频繁使用的功能就是表单序列化及创建与表单格式相同的数据。append方法接收两个参数,字段名与字段值,字段值可以是FileBlob、String.
 var data = new FormData(form);
 data.append("name", "woodtree");
 data.append(file.name, file);
 data.append(name, Blob);

  如果直接向FormData的构造函数中传入表单元素,可以将表单元素的数据预先填入。

 new FormData(document.forms[0])

  FormData的另一个便利之处就是不用明确指定Content-Type头部,xhr对象能够根据FormData实例自动配置适当的头部。下面是一个简单的上传文件demo。

 <!doctype html>
 <html>
   <head>
     <meta charset="utf-8">
     <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
     <title>FormData</title>
   </head>
   <body>
       <form id="uploader" action="/upload" enctype="multipart/form-data">
           <input id="app" type="file" multiple>
           <input type="submit" value="Submit">
       </form>
       <script>
         var form = document.getElementById('uploader');
         var app = document.getElementById('app');
         form.addEventListener('submit', function(evt) {
             evt.preventDefault();//组织页面刷新
             var data = new FormData();
             for (var i = 0, len = app.files.length; i < len; i++) {
                 //file property: name, size, type, lastModifiedDate
                 var file = app.files[i];
                 data.append(file.name, file);
             }

             var xhr = new XMLHttpRequest();
             xhr.onload = function() {
                 alert(JSON.parse(xhr.responseText).success);
             };
             xhr.onerror = function(err) {
                 console.error(err);
             };
             xhr.open('post', './upload', true);
             xhr.send(data);
         }, false);
     </script>
   </body>
 </html>

  server端代码使用formidable模块将文件暂存在tmp目录下。

 var http = require('http');
 var url = require('url');
 var fs = require('fs');
 var qs = require('querystring');
 var request = require('request');
 var formidable = require('formidable');

 http.createServer(function(req, res){
     var _url = url.parse(req.url);
     if (_url.pathname === '/index') {
         fs.readFile('./index.html', function(err, data) {
           res.writeHead(200, {"Content-Type": "text/html; charset=UTF-8"});
             res.write(data);
             res.end();
         });
     } else if (_url.pathname === '/upload') {
         console.log(req.headers['content-type']);
         handle(req, res);
     }
 }).listen(8888);
 var handle = function(req, res) {
     if (req.headers['content-type'].indexOf('multipart/form-data') >= 0) {
         var formStream = new formidable.IncomingForm();
         formStream.uploadDir = './tmp';
         formStream.parse(req, function(err, fields, files) {
             res.writeHead(200, {"Content-Type": "application/json"});
             if (err) {
                 res.write('{"success": false}');
             } else {
                 res.write('{"success": true}');
             }
             res.end();
         });
     }
 }

  查看请求,xhr自动为我们设置请求头部。

  兼容性问题

 
二、使用iframe上传文件
  兼容旧版本的ie浏览器实现无刷新上传,只能借由iframe来实现,大多数类库的做法是动态插入一个iframe元素,将form元素的target属性设置为新添加的iframe,这样只刷新了iframe的内容而避免页面跳转到form元素的action属性所指定的url。这里我们根据dojo/request/iframe模块的原理来实现上传文件。
  该模块需要后台返回响应的格式来配合。将需要返回的信息放在`textarea`标签内。然后绑定iframe的load事件,通过`doc.getElementsByTagName('textarea')`取得textarea中的数据。
 <html>
   <body>
     <textarea>
       uploadInfo
     </textarea>
   </body>
 </html>

  下面是简单的demo

<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
        <title>ArcGIS Web Application</title>
    </head>
    <body class="claro">
        <form id="uploader" method="post" action="/upload" target="appFrame" encoding="multipart/form-data" enctype="multipart/form-data">
            <input id="appInput" name="app" type="file" >
        </form>
        <iframe id="frame" name="appFrame" src="" style="visibility:hidden;"></iframe>
        <script type="text/javascript">
            var upload = document.getElementById('placeholder');
            var uploader = document.getElementById('uploader');
            var app = document.getElementsByName('app')[0];
            var clickLietener = function() {
                app.click();
            }
            var changeListener = function() {
                uploader.submit();
            }
            if (app.addEventListener) {
                app.addEventListener('change', changeListener, false);
            } else if (app.attachEvent) {
                app.attachEvent('onchange', changeListener);
            }
            var appFrame = document.getElementById('frame');
            var listener = function() {
                var doc = appFrame.contentWindow.document;
                var textAreas = doc.getElementsByTagName('textarea');
                if (textAreas && textAreas.length > 0) {
                    var response = textAreas[0].value;
                    alert(response);
                }
            }
            if (appFrame.addEventListener) {
                appFrame.addEventListener('load', function(evt) {
                    listener();
                }, false);
            } else if(appFrame.attachEvent) {
                appFrame.attachEvent('onload', function() {
                    listener();
                });
            }

        </script>
    </body>
</html>
 var http = require('http');
 var url = require('url');
 var fs = require('fs');
 var qs = require('querystring');
 var formidable = require('formidable');

 http.createServer(function(req, res) {
   var _url = url.parse(req.url);
   if (_url.pathname === '/index') {
     fs.readFile('./index.html', function(err, data) {
       res.writeHead(200, {
         "Content-Type": "text/html; charset=UTF-8"
       });
       res.write(data);
       res.end();
     });
   } else if (_url.pathname === '/upload') {
     var formStream = new formidable.IncomingForm();
     formStream.uploadDir = './tmp';
     formStream.parse(req, function(err, fields, files) {
       console.log(fields);
       console.log(files);
       var info = null;
       var accept = req.headers.accept;
       if (err) {
         info = {success: false};
       } else {
         info = {success: true};
       }
       if (accept.indexOf('application/json') > -1) {
         res.writeHead(200, {
           "Content-Type": "application/json;charset=utf-8"
         });
         res.write(JSON.stringify(info));
       } else {
         res.writeHead(200, {
           "Content-Type": "text/html; charset=UTF-8"
         });
         var responseText = '<html><body><textarea>' +
           JSON.stringify(info) +
           '</textarea></body></html>';
         res.write(responseText);
       }
       res.end();
     });
   }
 }).listen(8888);

  后台代码需要注意Content-Type响应头的设置,ie8、9碰到不知如何渲染的MIME类型会把它当成文件下载下来。这里这里

  不知大家有没有注意到,上面的demo是一步上传,选择好文件后直接上传到服务器,ie8以上的浏览器没问题,如果是在ie8中情况就有些棘手。ie中文件上传控件长成这个样子,单击一下button会弹出文件选择框,如果单击的是text部分,没有反映,你需要双击才会弹出选择框。一个办法是让鼠标尽量单击button部分,button的大小跟font-size有关。但如果你的可点击区域太大。。。。。

  所幸还是有解决办法的,这时需要在form中加一个label标签,for属性指向file。这样点击label时会触发for指向元素的click事件,这时label的自然行为。同时把file移除屏幕外。注意一定不能用input[type=button],在点击button时候调用file的click事件,然后在file change事件中调用form.submit方法,这种行为在ie中是被禁止的,回报“access denied”错误。

  

 <!DOCTYPE HTML>
 <html>
     <head>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
         <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
     <meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
         <title>ArcGIS Web Application</title>
     </head>
     <body class="claro">
         <form id="uploader" method="post" action="/upload" target="appFrame" encoding="multipart/form-data" enctype="multipart/form-data">
             <label id="placeholder" for="appInput">upload</label>
             <input id="appInput" name="app" type="file" style="position:absolute;left:-800px;">
         </form>
         <iframe id="frame" name="appFrame" src="" style="visibility:hidden;"></iframe>
         <script type="text/javascript">
             var upload = document.getElementById('placeholder');
             var uploader = document.getElementById('uploader');
             var app = document.getElementsByName('app')[0];
             var changeListener = function() {
                 uploader.submit();
             }
             if (app.addEventListener) {
                 app.addEventListener('change', changeListener, false);
             } else if (app.attachEvent) {
                 app.attachEvent('onchange', changeListener);
             }
             var appFrame = document.getElementById('frame');
             var listener = function() {
                 var doc = appFrame.contentWindow.document;
                 var textAreas = doc.getElementsByTagName('textarea');
                 if (textAreas && textAreas.length > 0) {
                     var response = textAreas[0].value;
                     alert(response);
                 }
             }
             if (appFrame.addEventListener) {
                 appFrame.addEventListener('load', function(evt) {
                     listener();
                 }, false);
             } else if(appFrame.attachEvent) {
                 appFrame.attachEvent('onload', function() {
                     listener();
                 });
             }

         </script>
     </body>
 </html>

参考资料

文件上传的渐进式增强 - 阮一峰的网络日志

Uploading Files with AJAX

ie javascript form submit with file input

Javascrpt无刷新文件上传的更多相关文章

  1. Asp.Net实现无刷新文件上传并显示进度条(非服务器控件实现)(转)

    Asp.Net实现无刷新文件上传并显示进度条(非服务器控件实现) 相信通过Asp.Net的服务器控件上传文件在简单不过了,通过AjaxToolkit控件实现上传进度也不是什么难事,为什么还要自己辛辛苦 ...

  2. 使用PHP和HTML5 FormData实现无刷新文件上传教程

    无刷新文件上传是一个常见而又有点复杂的问题,常见的解决方案是构造 iframe 方式实现. 在 HTML5 中提供了一个 FormData 对象 API,通过 FormData 可以方便地构造一个表单 ...

  3. 【JS】ajax 实现无刷新文件上传

    一.摘要 最近在做个东西,需要实现页面无刷新文件上传,目前看到的方法有两种 1) 通过隐藏iframe 实现页面无刷新,适用于不关心上传结果 <form target="hiddenF ...

  4. 实用ExtJS教程100例-009:ExtJS Form无刷新文件上传

    文件上传在Web程序开发中必不可少,ExtJS Form中有一个filefield字段,用来选择文件并上传.今天我们来演示一下如何通过filefield实现ExtJS Form无刷新的文件上传. 首先 ...

  5. Asp.Net 无刷新文件上传并显示进度条的实现方法及思路

    相信通过Asp.Net的服务器控件上传文件在简单不过了,通过AjaxToolkit控件实现上传进度也不是什么难事,为什么还要自己辛辛苦苦来 实现呢?我并不否认”拿来主义“,只是我个人更喜欢凡是求个所以 ...

  6. ie8实现无刷新文件上传

    ie8由于无法使用FormData,想要无刷新上传文件就显得比较麻烦.这里推荐使用jQuery-File-Upload插件,它能够很方便的解决ie8无刷新文件上传问题.(最低兼容到ie6) jQuer ...

  7. SpringMVC ajax技术无刷新文件上传下载删除示例

    参考 Spring MVC中上传文件实例 SpringMVC结合ajaxfileupload.js实现ajax无刷新文件上传 Spring MVC 文件上传下载 (FileOperateUtil.ja ...

  8. jquery无刷新文件上传 解决IE安全性问题

    很多项目中都需要有文件上传的功能,一般文件上传有几种方式,input file表单上传,flash上传. flash就不说了,能接受flash的就用吧. 下面介绍的这种是基于input file控件的 ...

  9. php利用iframe实现无刷新文件上传功能

    上传原理很简单就是利用表单的打开方式为iframe的name名,这样就可以在当前页面的iframe打来了,实现文件上传,再利用js返回上传结果. form target .在 action 属性中规定 ...

随机推荐

  1. 直传文件到Azure Storage的Blob服务中

    (此文章同时发表在本人微信公众号“dotNET每日精华文章”,欢迎右边二维码来关注.) 题记:为了庆祝获得微信公众号赞赏功能,忙里抽闲分享一下最近工作的一点心得:如何直接从浏览器中上传文件到Azure ...

  2. 基于ASP.NET的comet简单实现 http长连接,IAsyncResult

    http://www.cnblogs.com/hanxianlong/archive/2010/04/27/1722018.html 我潜水很多年,今天忽然出现.很久没写过博客了,不是因为不想写,而是 ...

  3. JAVA - Blowfish加密出现java.security.InvalidKeyException: Illegal key size 解决方案

    最近用java进行一个blowfish的加密算法,但是在我们的eclipse上报出Illegal key size的错误.google后发现原因是:ymmetricDS加密symmetric.prop ...

  4. js中位运算的运用

    原文:js中位运算的运用 我们可能很少在编程中用位运算,如果没深入学习,可能也很难理解.平时的数值运算,其实是要先转换成二进制再进行运算的,而位运算就是直接进行二进制运算,所以位运算的执行效率肯定是更 ...

  5. 【界面优化】使用viewpagerindicator添加下划线滑动动画

    开源代码viewpagerindicator里面没有实现tab下划线切换过程中的移动动画,都是很突兀的多个fragement之间的切换,导致用户体验略差,google了下相关问题,发现一片博文: ht ...

  6. Struts2自定义拦截器Interceptor以及拦截器登录实例

    1.在Struts2自定义拦截器有三种方式: -->实现Interceptor接口 public class QLInterceptorAction implements Interceptor ...

  7. 程序员常用字体(vs2008字体修改方案)

    字体不仅是设计师手中重要的武器,对我们开发人员来说,字体的选择也有许多讲究,一个好的.适合展示代码的字体,应该具备以下要素: 等宽的字符 简洁.清晰并且规范的字符形状 支持ASCII码为128以上的扩 ...

  8. Android Studio导入项目一直卡在Building gradle project info的解决方案

    出现了一个很神奇的现象,Android Studio导入其它项目均正常,但是导入某个项目(两天前还正常打开的项目)却一直卡在Building gradle project info 尝试了重启Andr ...

  9. Use of undefined constant FTP_BINARY - assumed &#39;FTP_BINARY

    用Laravel中的filesystems里面的ftp上传文件时报错.在windows上开发,文件上传的时候碰到上面的问题,搜了些资料,发现是php7的ftp拓展默认未开启. 第一步:检查extens ...

  10. vue 返回上一页后,上一页由参数渲染的内容无法显示

    思路1:将参数传递给第二个页面后,返回上一页时,再讲参数传回第一页(此方法适用于层级少的)(亲测有效) 思路2:将参数放到全局变量中(还未尝试过)