├── 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("