Javascript無刷新文件上傳

最近工做中遇到上傳文件問題,主要需求是一步點擊上傳,兼容ie8+,當時用的dojox/form/uploader控件,這兩天扒了一下源碼,明白了原理拿出來分享一下。
整體思路以下:javascript

  1. 對於支持XMLHttpRequest2的瀏覽器使用FormData經過ajax上傳
  2. 對於ie10一下的瀏覽器使用iframe異步上傳,還需後臺服務器作相應處理,這部分也是dojo/request/iframe上傳文件的原理。

1、使用FormData上傳文件

FormData最頻繁使用的功能就是表單序列化及建立與表單格式相同的數據。append方法接收兩個參數,字段名與字段值,字段值能夠是File、Blob、String.html

  1. var data = new FormData(form);
  2. data.append("name", "woodtree");
  3. data.append(file.name, file);
  4. data.append(name, Blob);

若是直接向FormData的構造函數中傳入表單元素,能夠將表單元素的數據預先填入。java

  1. new FormData(document.forms[0])

FormData的另外一個便利之處就是不用明確指定Content-Type頭部,xhr對象可以根據FormData實例自動配置適當的頭部。下面是一個簡單的上傳文件demo。ajax

<!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目錄下。json

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自動爲咱們設置請求頭部。瀏覽器

clipboard.png

兼容性問題服務器

clipboard.png

2、使用iframe上傳文件

兼容舊版本的ie瀏覽器實現無刷新上傳,只能藉由iframe來實現,大多數類庫的作法是動態插入一個iframe元素,將form元素的target屬性設置爲新添加的iframe,這樣只刷新了iframe的內容而避免頁面跳轉到form元素的action屬性所指定的url。這裏咱們根據dojo/request/iframe模塊的原理來實現上傳文件。
該模塊須要後臺返回響應的格式來配合。將須要返回的信息放在textarea標籤內。而後綁定iframe的load事件,經過doc.getElementsByTagName('textarea')取得textarea中的數據。app

<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響應頭的設置,ie八、9碰到不知如何渲染的MIME類型會把它當成文件下載下來。這裏和這裏函數

clipboard.png

clipboard.png

clipboard.png

不知你們有沒有注意到,上面的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」錯誤。

  

clipboard.png

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

轉自:Javascript無刷新文件上傳

相關文章
相關標籤/搜索