• Javascrpt无刷新文件上传


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

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

    1 new FormData(document.forms[0])

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

     1 <!doctype html>
     2 <html>
     3   <head>
     4     <meta charset="utf-8">
     5     <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
     6     <title>FormData</title>
     7   </head>
     8   <body>
     9       <form id="uploader" action="/upload" enctype="multipart/form-data">
    10           <input id="app" type="file" multiple>
    11           <input type="submit" value="Submit">
    12       </form>
    13       <script>
    14         var form = document.getElementById('uploader');
    15         var app = document.getElementById('app');
    16         form.addEventListener('submit', function(evt) {
    17             evt.preventDefault();//组织页面刷新
    18             var data = new FormData();
    19             for (var i = 0, len = app.files.length; i < len; i++) {
    20                 //file property: name, size, type, lastModifiedDate
    21                 var file = app.files[i];
    22                 data.append(file.name, file);
    23             }
    24 
    25             var xhr = new XMLHttpRequest();
    26             xhr.onload = function() {
    27                 alert(JSON.parse(xhr.responseText).success);
    28             };
    29             xhr.onerror = function(err) {
    30                 console.error(err);
    31             };
    32             xhr.open('post', './upload', true);
    33             xhr.send(data);
    34         }, false);
    35     </script>
    36   </body>
    37 </html>
    View Code

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

     1 var http = require('http');
     2 var url = require('url');
     3 var fs = require('fs');
     4 var qs = require('querystring');
     5 var request = require('request');
     6 var formidable = require('formidable');
     7 
     8 http.createServer(function(req, res){
     9     var _url = url.parse(req.url);
    10     if (_url.pathname === '/index') {
    11         fs.readFile('./index.html', function(err, data) {
    12           res.writeHead(200, {"Content-Type": "text/html; charset=UTF-8"});
    13             res.write(data);
    14             res.end();
    15         });
    16     } else if (_url.pathname === '/upload') {
    17         console.log(req.headers['content-type']);
    18         handle(req, res);
    19     }
    20 }).listen(8888);
    21 var handle = function(req, res) {
    22     if (req.headers['content-type'].indexOf('multipart/form-data') >= 0) {
    23         var formStream = new formidable.IncomingForm();
    24         formStream.uploadDir = './tmp';
    25         formStream.parse(req, function(err, fields, files) {
    26             res.writeHead(200, {"Content-Type": "application/json"});
    27             if (err) {
    28                 res.write('{"success": false}');
    29             } else {
    30                 res.write('{"success": true}');
    31             }
    32             res.end();
    33         });
    34     }
    35 }
    View Code

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

      兼容性问题

     
    二、使用iframe上传文件
      兼容旧版本的ie浏览器实现无刷新上传,只能借由iframe来实现,大多数类库的做法是动态插入一个iframe元素,将form元素的target属性设置为新添加的iframe,这样只刷新了iframe的内容而避免页面跳转到form元素的action属性所指定的url。这里我们根据dojo/request/iframe模块的原理来实现上传文件。
      该模块需要后台返回响应的格式来配合。将需要返回的信息放在`textarea`标签内。然后绑定iframe的load事件,通过`doc.getElementsByTagName('textarea')`取得textarea中的数据。
    1 <html>
    2   <body>
    3     <textarea>
    4       uploadInfo
    5     </textarea>
    6   </body>
    7 </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>
    View Code
     1 var http = require('http');
     2 var url = require('url');
     3 var fs = require('fs');
     4 var qs = require('querystring');
     5 var formidable = require('formidable');
     6 
     7 http.createServer(function(req, res) {
     8   var _url = url.parse(req.url);
     9   if (_url.pathname === '/index') {
    10     fs.readFile('./index.html', function(err, data) {
    11       res.writeHead(200, {
    12         "Content-Type": "text/html; charset=UTF-8"
    13       });
    14       res.write(data);
    15       res.end();
    16     });
    17   } else if (_url.pathname === '/upload') {
    18     var formStream = new formidable.IncomingForm();
    19     formStream.uploadDir = './tmp';
    20     formStream.parse(req, function(err, fields, files) {
    21       console.log(fields);
    22       console.log(files);
    23       var info = null;
    24       var accept = req.headers.accept;
    25       if (err) {
    26         info = {success: false};
    27       } else {
    28         info = {success: true};
    29       }
    30       if (accept.indexOf('application/json') > -1) {
    31         res.writeHead(200, {
    32           "Content-Type": "application/json;charset=utf-8"
    33         });
    34         res.write(JSON.stringify(info));
    35       } else {
    36         res.writeHead(200, {
    37           "Content-Type": "text/html; charset=UTF-8"
    38         });
    39         var responseText = '<html><body><textarea>' +
    40           JSON.stringify(info) +
    41           '</textarea></body></html>';
    42         res.write(responseText);
    43       }
    44       res.end();
    45     });
    46   }
    47 }).listen(8888);
    View Code

      后台代码需要注意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”错误。

      

     1 <!DOCTYPE HTML>
     2 <html>
     3     <head>
     4         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
     5         <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
     6     <meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
     7         <title>ArcGIS Web Application</title>
     8     </head>
     9     <body class="claro">
    10         <form id="uploader" method="post" action="/upload" target="appFrame" encoding="multipart/form-data" enctype="multipart/form-data">
    11             <label id="placeholder" for="appInput">upload</label>
    12             <input id="appInput" name="app" type="file" style="position:absolute;left:-800px;">
    13         </form>
    14         <iframe id="frame" name="appFrame" src="" style="visibility:hidden;"></iframe>
    15         <script type="text/javascript">
    16             var upload = document.getElementById('placeholder');
    17             var uploader = document.getElementById('uploader');
    18             var app = document.getElementsByName('app')[0];
    19             var changeListener = function() {
    20                 uploader.submit();
    21             }
    22             if (app.addEventListener) {
    23                 app.addEventListener('change', changeListener, false);
    24             } else if (app.attachEvent) {
    25                 app.attachEvent('onchange', changeListener);
    26             }
    27             var appFrame = document.getElementById('frame');
    28             var listener = function() {
    29                 var doc = appFrame.contentWindow.document;
    30                 var textAreas = doc.getElementsByTagName('textarea');
    31                 if (textAreas && textAreas.length > 0) {
    32                     var response = textAreas[0].value;
    33                     alert(response);
    34                 }
    35             }
    36             if (appFrame.addEventListener) {
    37                 appFrame.addEventListener('load', function(evt) {
    38                     listener();
    39                 }, false);
    40             } else if(appFrame.attachEvent) {
    41                 appFrame.attachEvent('onload', function() {
    42                     listener();
    43                 });
    44             }
    45             
    46         </script>
    47     </body>
    48 </html>
    View Code

    参考资料

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

    Uploading Files with AJAX

    ie javascript form submit with file input

  • 相关阅读:
    RHEL7使用ssm命令管理LVM
    LVM命令摘要
    Linux用户磁盘配额
    Linux磁盘分区实战案例
    通过yum升级CentOS/RHEL最小化安装
    RHEL7下PXE+FTP+Kickstart无人值守安装操作系统
    配置VSFTP服务器
    RHEL7下PXE+NFS+Kickstart无人值守安装操作系统
    RHEL7下PXE+Apache+Kickstart无人值守安装操作系统
    html5实现图片自适应手机屏幕页面的css
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/4352013.html
Copyright © 2020-2023  润新知