├── LICENSE ├── README.md └── ajaxfileupload.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 gaojr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ajaxfileupload 2 | 3 | 基于 ajaxfileupload.js 文件的增强版 ajaxfileupload.js 4 | 5 | ## 使用方法 6 | 7 | 1. 页面部分 8 | 9 | 通过设置 `input[type=file]` 的 `accept` 属性,可限制上传文件的类型,多个类型用英文逗号隔开。 10 | 11 | ```html 12 |
13 | 14 | 15 |
16 | ``` 17 | 18 | **注意: `input[type=file]` 的 `name` 属性为必填** 19 | 20 | 2. 样式部分 21 | 22 | * 原因: 一般来说,默认的 `input[type=file]` 太丑了,且不同浏览器下视觉效果差距较大,因此会对其进行隐藏,使用其他标签替代 23 | * 问题: 由于 IE 安全限制问题,没有点击到 `input[type=file]` 的浏览按钮就不允许上传 24 | * 解决方案: 让 file 标签盖在替代标签上,但 file 是透明的,这样用户看到的是替代标签的外观,实际点击是 file 标签 25 | 26 | ```css 27 | input#fileId { 28 | position: absolute; 29 | width: 0; 30 | opacity: 0; 31 | } 32 | lable#label_fileId { 33 | position: absolute; 34 | display: inline; 35 | } 36 | ``` 37 | 38 | 3. 前端交互部分 39 | 40 | ```javascript 41 | /** 42 | * 上传方法 43 | */ 44 | function ajaxFileUpload() { 45 | // 封装参数 46 | var data = { "key1": "value1", "key2": "value2" }; 47 | // 开始上传 48 | $.ajaxFileUpload({ 49 | secureuri: false,// 是否启用安全提交,默认为 false 50 | type: "POST", 51 | url: getUrl(), 52 | fileElementId: "fileId",// input[type=file] 的 id 53 | dataType: "json",// 返回值类型,一般位 `json` 或者 `text` 54 | data: data,// 添加参数,无参数时注释掉 55 | success: function (data, status) { 56 | // 成功 57 | }, 58 | error: function (data, status, e) { 59 | // 失败 60 | } 61 | }); 62 | } 63 | /** 64 | * 获取 url 65 | */ 66 | function getUrl() { 67 | var url = "../ajaxfileupload.do";// 后台方法的 url 68 | var jsessionid = getSessionId(); 69 | if (jsessionid) 70 | url += ";jsessionid=" + jsessionid; 71 | return url; 72 | function getSessionId() { 73 | var cookie = document.cookie; 74 | if (cookie.length > 0) { 75 | c_start = cookie.indexOf("JSESSIONID="); 76 | if (c_start != -1) { 77 | c_start += "JSESSIONID=".length; 78 | c_end = cookie.indexOf(";", c_start); 79 | if (c_end == -1) 80 | c_end = cookie.length; 81 | return unescape(cookie.substring(c_start, c_end)); 82 | } 83 | } 84 | } 85 | } 86 | ``` 87 | 88 | 4. 后端接收与返回 89 | 90 | ```java 91 | @RequestMapping(value = "ajaxfileupload.do", method = RequestMethod.POST) 92 | public void excelImport(HttpServletResponse response, 93 | @RequestParam MultipartFile fileName, 94 | @RequestParam(required = false) String key1, 95 | @RequestParam(required = false) String key2) { 96 | // @RequestParam(required = false) 表示该参数可以为 null 97 | // 参数不为 null 时: 98 | // key1.euqals("value1") 结果为 true 99 | // key2.euqals("value2") 结果为 true 100 | } 101 | ``` 102 | 103 | **注意: ** 104 | 1. `fileName` 参数名称要与 html 中 `input[type=file]` 的 `name` 属性相同 105 | 2. `key1`、`key2` 等参数名称要与 js 中封装参数 data 的键相同 106 | 107 | ## 原文件的问题 & 原因 & 解决 108 | 109 | 1. 运行时报 `jQuery.handleError is not a function` 错误 110 | 111 | * 原因: ajaxfileupload.js 是在 jQuery 1.4.2 版本之前写的,之后的版本已经没有了 handleError 方法 112 | 113 | * 解决: 将 1.4.2 版本中的该方法复制到 js 文件中 114 | 115 | ```javascript 116 | jQuery.extend({ 117 | // 手动添加在 jQuery 1.4.2 之前的版本才有的 handlerError 方法 118 | handleError: function (s, xhr, status, e) { 119 | // If a local callback was specified, fire it 120 | if (s.error) 121 | s.error.call(s.context || s, xhr, status, e); 122 | // Fire the global callback 123 | if (s.global) 124 | (s.context ? jQuery(s.context) : jQuery.event).trigger("ajaxError", [xhr, s, e]); 125 | }, 126 | ... 127 | }) 128 | ``` 129 | 130 | 2. 执行成功后,始终指向 error 方法处理,无法执行 sucess 方法 131 | 132 | * 原因: `dataType` 为 `json` 时,若返回 `data` 为 `json` 格式的字符串时,会出现问题 133 | 134 | * 解决1: 将 `uploadHttpData` 方法中 if(type == "json") 里的局部变量改为方法内的变量 135 | 136 | ```javascript 137 | uploadHttpData: function (r, type) { 138 | var data = !type; 139 | ... 140 | if (type == "json") { 141 | data = r.responseText;// 去掉前面的 var 142 | var rx = new RegExp("(.*?)", "i"); 143 | var am = rx.exec(data); 144 | data = (am) ? am[1] : "";// 去掉前面的 var 145 | eval("data = " + data);// 返回 json 对象,注释掉可返回 json 格式的字符串 146 | } 147 | ... 148 | return data; 149 | } 150 | ``` 151 | 152 | * 解决2: 将上传方法 $.ajaxFileUpload() 中的 `dataType` 设置为 `text`,直接获取字符串 153 | 154 | 3. 无法带参数提交,只能上传文件 155 | 156 | * 原因: 原作者只完成了文件提交功能…… 157 | 158 | * 解决: 修改 createUploadForm 方法及其调用位置 159 | 160 | ```javascript 161 | createUploadForm: function (id, fileElementId, data) {// 添加 data 参数 162 | ... 163 | $(oldElement).appendTo(form); 164 | 165 | // 增加参数的支持 166 | if (data) { 167 | for (var i in data) 168 | $('').appendTo(form); 169 | } 170 | 171 | // set attributes 172 | ... 173 | }, 174 | ... 175 | ajaxFileUpload: function (s) { 176 | ... 177 | if (s.data) form = jQuery.addOtherRequestsToForm(form, s.data); 178 | var io = jQuery.createUploadIframe(id, s.secureuri, s.data);// 添加传入参数 s.data 179 | var frameId = 'jUploadFrame' + id; 180 | ... 181 | } 182 | ``` 183 | 184 | 4. 造成 input[type=file] 的 change 事件只能触发一次 185 | 186 | * 原因: ajaxfileupload.js 会将原 file 元素替换成新的 file 元素,且替换时未绑定事件 187 | 188 | * 解决: 在 createUploadForm 方法 `$(oldElement).clone()` 处添加 true 参数 189 | 190 | ```javascript 191 | createUploadForm: function (id, fileElementId, data) { 192 | ... 193 | var newElement = $(oldElement).clone(true);// true: 复制元素的同时复制事件 194 | $(oldElement).attr('id', fileId); 195 | $(oldElement).before(newElement); 196 | $(oldElement).appendTo(form); 197 | ... 198 | } 199 | ``` 200 | 201 | ## 参考 202 | 203 | [ajaxfileupload.js 问题汇总及解决](https://blog.yadgen.com/?p=970) 204 | 205 | [关于 AjaxFileUpload 后台返回 Json 的处理](https://blog.csdn.net/gisredevelopment/article/details/29869109) 206 | 207 | [关于 ajaxFileUpload 造成 input[type=file] change 事件只能触发一次的问题](https://blog.csdn.net/sinat_34930640/article/details/77368681) 208 | 209 | [MIME 参考手册](http://www.w3school.com.cn/media/media_mimeref.asp) 210 | 211 | [IE input file 隐藏不能上传文件解决方法](http://www.qttc.net/201305334.html) -------------------------------------------------------------------------------- /ajaxfileupload.js: -------------------------------------------------------------------------------- 1 | jQuery.extend({ 2 | // 手动添加在 jQuery 1.4.2 之前的版本才有的 handlerError 方法 3 | handleError: function (s, xhr, status, e) { 4 | // If a local callback was specified, fire it 5 | if (s.error) 6 | s.error.call(s.context || s, xhr, status, e); 7 | // Fire the global callback 8 | if (s.global) 9 | (s.context ? jQuery(s.context) : jQuery.event).trigger("ajaxError", [xhr, s, e]); 10 | }, 11 | 12 | createUploadIframe: function (id, uri) { 13 | // create frame 14 | var frameId = 'jUploadFrame' + id; 15 | 16 | if (window.ActiveXObject) { 17 | var io = document.createElement("iframe"); 18 | io.id = frameId; 19 | io.name = frameId; 20 | if (typeof uri == 'boolean') { 21 | io.src = 'javascript:false'; 22 | } 23 | else if (typeof uri == 'string') { 24 | io.src = uri; 25 | } 26 | } 27 | else { 28 | var io = document.createElement('iframe'); 29 | io.id = frameId; 30 | io.name = frameId; 31 | } 32 | io.style.position = 'absolute'; 33 | io.style.top = '-1000px'; 34 | io.style.left = '-1000px'; 35 | 36 | document.body.appendChild(io); 37 | 38 | return io 39 | }, 40 | 41 | createUploadForm: function (id, fileElementId, data) { 42 | // create form 43 | var formId = 'jUploadForm' + id; 44 | var fileId = 'jUploadFile' + id; 45 | var form = $('
'); 46 | var oldElement = $('#' + fileElementId); 47 | var newElement = $(oldElement).clone(true);// true:复制元素的同时复制事件 48 | $(oldElement).attr('id', fileId); 49 | $(oldElement).before(newElement); 50 | $(oldElement).appendTo(form); 51 | 52 | // 增加参数的支持 53 | if (data) { 54 | for (var i in data) 55 | $('').appendTo(form); 56 | } 57 | 58 | // set attributes 59 | $(form).css('position', 'absolute'); 60 | $(form).css('top', '-1200px'); 61 | $(form).css('left', '-1200px'); 62 | $(form).appendTo('body'); 63 | return form; 64 | }, 65 | 66 | addOtherRequestsToForm: function (form, data) { 67 | // add extra parameter 68 | var originalElement = $(''); 69 | for (var key in data) { 70 | name = key; 71 | value = data[key]; 72 | var cloneElement = originalElement.clone(); 73 | cloneElement.attr({ 'name': name, 'value': value }); 74 | $(cloneElement).appendTo(form); 75 | } 76 | return form; 77 | }, 78 | 79 | ajaxFileUpload: function (s) { 80 | // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout 81 | s = jQuery.extend({}, jQuery.ajaxSettings, s); 82 | var id = new Date().getTime() 83 | var form = jQuery.createUploadForm(id, s.fileElementId, s.data);// 添加传入参数 s.data 84 | if (s.data) form = jQuery.addOtherRequestsToForm(form, s.data); 85 | var io = jQuery.createUploadIframe(id, s.secureuri); 86 | var frameId = 'jUploadFrame' + id; 87 | var formId = 'jUploadForm' + id; 88 | // Watch for a new set of requests 89 | if (s.global && !jQuery.active++) 90 | jQuery.event.trigger("ajaxStart"); 91 | var requestDone = false; 92 | // Create the request object 93 | var xml = {} 94 | if (s.global) 95 | jQuery.event.trigger("ajaxSend", [xml, s]); 96 | // Wait for a response to come back 97 | var uploadCallback = function (isTimeout) { 98 | var io = document.getElementById(frameId); 99 | try { 100 | if (io.contentWindow) { 101 | xml.responseText = io.contentWindow.document.body ? io.contentWindow.document.body.innerHTML : null; 102 | xml.responseXML = io.contentWindow.document.XMLDocument ? io.contentWindow.document.XMLDocument : io.contentWindow.document; 103 | 104 | } else if (io.contentDocument) { 105 | xml.responseText = io.contentDocument.document.body ? io.contentDocument.document.body.innerHTML : null; 106 | xml.responseXML = io.contentDocument.document.XMLDocument ? io.contentDocument.document.XMLDocument : io.contentDocument.document; 107 | } 108 | } catch (e) { 109 | jQuery.handleError(s, xml, null, e); 110 | } 111 | if (xml || isTimeout == "timeout") { 112 | requestDone = true; 113 | var status; 114 | try { 115 | status = isTimeout != "timeout" ? "success" : "error"; 116 | // Make sure that the request was successful or notmodified 117 | if (status != "error") { 118 | // process the data (runs the xml through httpData regardless of callback) 119 | var data = jQuery.uploadHttpData(xml, s.dataType); 120 | // If a local callback was specified, fire it and pass it the data 121 | if (s.success) 122 | s.success(data, status); 123 | // Fire the global callback 124 | if (s.global) 125 | jQuery.event.trigger("ajaxSuccess", [xml, s]); 126 | } else 127 | jQuery.handleError(s, xml, status); 128 | } catch (e) { 129 | status = "error"; 130 | jQuery.handleError(s, xml, status, e); 131 | } 132 | 133 | // The request was completed 134 | if (s.global) 135 | jQuery.event.trigger("ajaxComplete", [xml, s]); 136 | 137 | // Handle the global AJAX counter 138 | if (s.global && ! --jQuery.active) 139 | jQuery.event.trigger("ajaxStop"); 140 | 141 | // Process result 142 | if (s.complete) 143 | s.complete(xml, status); 144 | 145 | jQuery(io).unbind(); 146 | 147 | setTimeout(function () { 148 | try { 149 | $(io).remove(); 150 | $(form).remove(); 151 | } catch (e) { 152 | jQuery.handleError(s, xml, null, e); 153 | } 154 | }, 100); 155 | 156 | xml = null; 157 | } 158 | } 159 | // Timeout checker 160 | if (s.timeout > 0) { 161 | setTimeout(function () { 162 | // Check to see if the request is still happening 163 | if (!requestDone) uploadCallback("timeout"); 164 | }, s.timeout); 165 | } 166 | try { 167 | // var io = $('#' + frameId); 168 | var form = $('#' + formId); 169 | $(form).attr('action', s.url); 170 | $(form).attr('method', 'POST'); 171 | $(form).attr('target', frameId); 172 | if (form.encoding) 173 | form.encoding = 'multipart/form-data'; 174 | else 175 | form.enctype = 'multipart/form-data'; 176 | $(form).submit(); 177 | } catch (e) { 178 | jQuery.handleError(s, xml, null, e); 179 | } 180 | if (window.attachEvent) 181 | document.getElementById(frameId).attachEvent('onload', uploadCallback); 182 | else { 183 | document.getElementById(frameId).addEventListener('load', uploadCallback, false); 184 | } 185 | return { abort: function () { } }; 186 | }, 187 | 188 | uploadHttpData: function (r, type) { 189 | var data = !type; 190 | data = type == "xml" || data ? r.responseXML : r.responseText; 191 | // If the type is "script", eval it in global context 192 | if (type == "script") 193 | jQuery.globalEval(data); 194 | // Get the JavaScript object, if JSON is used. 195 | if (type == "json") { 196 | // If you add mimetype in your response, 197 | // you have to delete the '
' tag.
198 |             // The pre tag in Chrome has attribute, so have to use regex to remove
199 |             data = r.responseText;
200 |             var rx = new RegExp("(.*?)", "i");
201 |             var am = rx.exec(data);
202 |             // this is the desired data extracted
203 |             data = (am) ? am[1] : "";// the only submatch or empty
204 |             eval("data = " + data);// 返回 json 对象,注释掉可返回 json 格式的字符串
205 |         }
206 |         // evaluate scripts within html
207 |         if (type == "html")
208 |             jQuery("
").html(data).evalScripts(); 209 | // alert($('param', data).each(function(){alert($(this).attr('value'));})); 210 | 211 | return data; 212 | } 213 | }) 214 | --------------------------------------------------------------------------------