最近工做中遇到上傳文件問題,主要需求是一步點擊上傳,兼容ie8+,當時用的dojox/form/uploader控件,這兩天扒了一下源碼,明白了原理拿出來分享一下。
整體思路以下:javascript
FormData最頻繁使用的功能就是表單序列化及建立與表單格式相同的數據。append方法接收兩個參數,字段名與字段值,字段值能夠是File、Blob、String.html
若是直接向FormData的構造函數中傳入表單元素,能夠將表單元素的數據預先填入。java
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自動爲咱們設置請求頭部。瀏覽器
兼容性問題服務器
兼容舊版本的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類型會把它當成文件下載下來。這裏和這裏函數
不知你們有沒有注意到,上面的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>