一、前言
确定了渐进式增强的上传方式,接下来我们需要将上传功能从具体的业务逻辑中剥离出来,作为公共组件供业务层调用。这就要求我们必须对业务层隐藏上传细节,只暴露统一的上传API。这时候大家是不是跟我一样想到了Facade模式?
二、Facade模式实现文件上传,代码示例:
/* 上传组件,IE浏览器默认flash上传,其它浏览器html5 示例: var fileUpload = new FileUpload({ container: document.getElementById("uploadBtn"), onselect: function (files) { var self = this; $(files).each(function (i, n) { updateUI(n); }); setTimeout(function () { //异步,等待onselect函数return后才能调用upload self.upload(); }, 10); }, onprogress: function (fileInfo) { updateUI(fileInfo); }, oncomplete: function (fileInfo, responseText) { updateUI(fileInfo); } }); */ function FileUpload(options) { var uploader=null; if (options) { //为什么要多创建一级div容器?flash 的activex创建后,再改变位置会引起activex对象失效,所以要在创建前就定好位 var div = document.createElement("div"); div.id = "flashUploadDiv"; document.body.appendChild(div); var c = $(options.container); //绝对定位到上传按钮的坐标,flash本身为透明遮罩 $(div).css({ position: "absolute", left: c.offset().left + "px", opacity:0, top: c.offset().top + "px" }); if ($.browser.msie || options.uploadType == "flash") { //flash上传方式 var url = "Richinfo_annex_upload.swf"; var so = new SWFObject(url, "flashupload", c.width(), c.height()); so.addParam("wmode", "transparent"); so.write("flashUploadDiv"); options.activexObj = document.getElementById("flashupload"); window.JSForFlashUpload = new FlashUpload(options); uploader = JSForFlashUpload; } else { $(div).html([‘<form style="" enctype="multipart/form-data" id="fromAttach" method="post" action="" target="frmAttachTarget">‘, ‘<input style="height: ‘, c.height(), ‘px;width:‘, c.width(), ‘px" type="file" name="uploadInput" id="uploadInput" multiple="true">‘, ‘</form>‘, ‘<iframe id="frmAttachTarget" style="display: none" name="frmAttachTarget"></iframe>‘].join("")); options.uploadInput = document.getElementById("uploadInput"); uploader = new Html5Upload(options); } } this.upload = function () {//触发上传请求 //alert("uploader.load"); uploader.upload(); }, this.cancel = function () {//取消上传 uploader.cancel(); } this.getUploadFiles = function () {//获取上传队列 uploader.getUploadFiles(); } $.extend(options, this);//继承FileUpload的能力 }
var FlashUpload = function(options){ var resultObject = { activexObj: options.activexObj, upload:function(){ this.activexObj.uploadAll(); }, cancel: function () { this.activexObj.cancel(); }, getUploadUrl: function () { return this.agent.getUploadUrl(); }, getUploadFiles: function () { return this.uploadFiles; }, onload: function (param) { this.agent = {}; if (options) { this.agent = options; } param["filter"] = ["images图片(*.jpg;*.png;*.bmp)", "video(*.flv;*.avi;*.rmvb)"]; param["uploadFieldName"] = "filedata"; //options["filter"] = ["eml邮件(*.eml)"]; //options["filter"] = ["所有文件(*.*)"]; return param; }, onselect: function (xmlFileList, jsonFileList) { for (var i = 0; i < jsonFileList.length; i++) { jsonFileList[i].fileName = decodeURIComponent(jsonFileList[i].fileName); jsonFileList[i].state = "waiting"; /*if (jsonFileList[i].fileSize > 100000) { //大于100K不上传 jsonFileList.splice(i, 1); i--; }*/ } //uploadView.onselect(jsonFileList); this.agent.onselect && this.agent.onselect(jsonFileList); this.uploadFiles = jsonFileList; return jsonFileList; }, onprogress: function (taskId, sendedSize, uploadSpeed, fileInfo) { fileInfo.taskId = taskId; fileInfo.sendedSize = sendedSize; fileInfo.percent = Math.round((sendedSize / fileInfo.fileSize) * 100); fileInfo.state = "uploading"; fileInfo.fileName = decodeURIComponent(fileInfo.fileName);//防止乱码,flash里面做了encode //alert(fileInfo.percent); this.agent.onprogress && this.agent.onprogress(fileInfo); }, oncomplete: function (taskId, responseText, fileInfo) { fileInfo.taskId = taskId; fileInfo.state = "complete"; fileInfo.fileName = decodeURIComponent(fileInfo.fileName);//防止乱码,flash里面做了encode this.agent.oncomplete && this.agent.oncomplete(fileInfo, responseText); }, onerror: function (taskId, errorCode, errorMsg) { alert("文件上传失败:" + errorMsg); this.agent.onerror && this.agent.onerror(errorMsg); }, onmouseover: function () { }, onmouseout: function () { }, onclick: function () { return true;//返回false不会弹出文件选择框 //alert("onclick"); } } return resultObject; }
var Html5Upload = function (options) { var resultObject = { uploadInput: null, currentFile: null, uploadFiles:[],//待上传的文件 completeFiles:[],//已完成的文件 init: function () { var self = this; this.agent = options; this.uploadInput = options.uploadInput; this.uploadInput.onclick = this.onclick; this.uploadInput.onchange = function () { var files = this.files; var result = []; for (var i = 0; i < files.length; i++) { console.log(files[i]); result.push({ fileName: files[i].name, fileSize: files[i].size, fileData: files[i], state : "waiting", taskId: Math.random().toString().substr(2) }); } self.uploadFiles = result; self.onselect(result); } }, getFileUploadXHR: function () { //单例 if (!window.fileUploadXHR) { fileUploadXHR = new XMLHttpRequest(); } this.xhr = window.fileUploadXHR; return fileUploadXHR; }, getUploadUrl: function () { //获取上传地址 return this.agent.getUploadUrl(); }, getUploadFiles:function(){ //获取上传队列 return this.uploadFiles.concat(this.completeFiles); }, upload: function () {//开始上传请求 this.uploadNextFile(); }, cancel:function(){ //取消上传 this.xhr.abort(); }, uploadNextFile: function () { //每个上传文件会触发 var fileInfo = this.uploadFiles.shift(); this.completeFiles.push(fileInfo); //存入已完成列表 this.currentFile = fileInfo; if (fileInfo) { var self = this; var xhr = this.getFileUploadXHR(); xhr.upload.onabort = function (oEvent) { }; xhr.upload.onerror = function (oEvent) { self.onerror(oEvent); }; xhr.upload.onload = function (oEvent) { self.onload(oEvent); }; xhr.upload.onloadend = function (oEvent) { }; xhr.upload.onloadstart = function (oEvent) { }; xhr.upload.onprogress = function (oEvent) { console.log(oEvent); fileInfo.state = "uploading"; fileInfo.sendedSize = oEvent.position; fileInfo.percent = Math.round((oEvent.position / oEvent.total) * 100); self.onprogress(fileInfo); }; //xhr.ontimeout = function(oEvent){This.ontimeout(oEvent);}; xhr.onreadystatechange = function (oEvent) { if (xhr.readyState == 4) { if (xhr.status == 200) { var responseText = xhr.responseText; self.oncomplete(fileInfo); } } }; var url = this.getUploadUrl(); xhr.open("POST", url, true); //xhr.timeout = this.timeout; //timeout function getFormData(fileInfo) { var formData = new FormData(); formData.append("filedata", fileInfo.fileData); return formData; } var fd = getFormData(fileInfo); xhr.send(fd); } }, onclick: function () { }, onselect:function(files){ this.agent.onselect && this.agent.onselect(files); }, onload:function(e){ }, onprogress: function (fileInfo) { this.agent.onprogress && this.agent.onprogress(fileInfo); }, oncomplete: function (fileInfo) { fileInfo.state = "complete"; this.agent.oncomplete && this.agent.oncomplete(fileInfo); this.uploadNextFile(); } } resultObject.init(); return resultObject; }
三、调用示例:
<html> <head> <script src="jquery-1.8.3.js"></script> <script src="swfobject.js"></script> <script src="upload.js"></script> </head> <body> <div style="position:absolute"> </div> <ul id="uploadList"></ul> </body> <script> function updateUI(fileInfo) { var ul = $("#uploadList"); switch (fileInfo.state) { case "waiting": ul.append("<li taskId=‘" + fileInfo.taskId + "‘>" + fileInfo.fileName + "(等待上传...)</li>"); break; case "uploading": ul.find("li[taskId=" + fileInfo.taskId + "]").html(fileInfo.fileName + "(" + fileInfo.percent + "%)"); break; case "complete": ul.find("li[taskId=" + fileInfo.taskId + "]").html(fileInfo.fileName + "(完成)"); break; } } var fileUpload = new FileUpload({ container: document.getElementById("uploadBtn"), //uploadType:"flash", getUploadUrl: function () { return "upload.ashx"; }, onselect: function (files) { var self = this; $(files).each(function (i, n) { updateUI(n); }); setTimeout(function () { //异步,等待onselect函数return后才能调用upload self.upload(); }, 10); }, onprogress: function (fileInfo) { updateUI(fileInfo); }, oncomplete: function (fileInfo, responseText) { updateUI(fileInfo); console.log(this.getUploadFiles()); } }); </script> </html>
四、结束语
以上源码仅提供上传组件化思路,实际应用中要考虑更多,比如:弱网络环境下需要分块上传,断点续传,异常情况下的日志上报等等。
以上源码非本人原创,代码来源于139邮箱前端团队内部分享。希望对大家有所帮助。
五、参考资料
百度前端上传组件:http://fex.baidu.com/blog/2014/04/html5-uploader/?qq-pf-to=pcqq.group
时间: 2024-10-22 08:42:16