fine-uploader-rails 3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +30 -0
- data/Rakefile +19 -0
- data/lib/fine-uploader-rails.rb +8 -0
- data/lib/fine-uploader-rails/version.rb +5 -0
- data/vendor/assets/images/loading.gif +0 -0
- data/vendor/assets/images/processing.gif +0 -0
- data/vendor/assets/javascripts/fineuploader.jquery.js +3209 -0
- data/vendor/assets/javascripts/fineuploader.js +3041 -0
- data/vendor/assets/stylesheets/fineuploader.css +148 -0
- metadata +103 -0
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2012 Zak Grant
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Fine Uploader 3.2 for Rails
|
|
2
|
+
|
|
3
|
+
[Fineuploader](http://fineuploader.com/) is a Javascript plugin written by [Andrew Valums](http://github.com/valums/), and actively developed by [Ray Nicholus](http://lnkd.in/Nkhx2C). This plugin uses an XMLHttpRequest (AJAX) for uploading multiple files with a progress-bar in FF3.6+, Safari4+, Chrome and falls back to hidden-iframe-based upload in other browsers (namely IE), providing good user experience everywhere. It does not use Flash, jQuery, or any external libraries.
|
|
4
|
+
|
|
5
|
+
This gem integrates this fantastic plugin with Rails 3.1+ Asset Pipeline.
|
|
6
|
+
|
|
7
|
+
[Plugin documentation](https://github.com/valums/file-uploader/blob/master/readme.md)
|
|
8
|
+
|
|
9
|
+
## Installing Gem
|
|
10
|
+
|
|
11
|
+
gem 'fine-uploader-rails', '~> 3.2'
|
|
12
|
+
|
|
13
|
+
## Using the javascripts
|
|
14
|
+
|
|
15
|
+
Require fineuploader in your app/assets/application.js file
|
|
16
|
+
|
|
17
|
+
//= require fineuploader
|
|
18
|
+
|
|
19
|
+
Fineuploader with JQuery wrapper
|
|
20
|
+
|
|
21
|
+
//= require fineuploader.jquery
|
|
22
|
+
|
|
23
|
+
## Using the stylesheet
|
|
24
|
+
|
|
25
|
+
Require the stylesheet file to app/assets/stylesheets/application.css
|
|
26
|
+
|
|
27
|
+
*= require fineuploader
|
|
28
|
+
|
|
29
|
+
## Thanks
|
|
30
|
+
Thanks to [Andrew Valums](http://github.com/valums/) and [Ray Nicholus](http://lnkd.in/Nkhx2C) for this plugin.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
begin
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
rescue LoadError
|
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
Bundler::GemHelper.install_tasks
|
|
9
|
+
|
|
10
|
+
desc "Bundle the gem"
|
|
11
|
+
task :bundle do
|
|
12
|
+
sh('bundle install')
|
|
13
|
+
sh 'gem build *.gemspec'
|
|
14
|
+
sh 'gem install *.gem'
|
|
15
|
+
sh 'rm *.gem'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
task(:default).clear
|
|
19
|
+
task :default => :bundle
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,3209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http://github.com/Valums-File-Uploader/file-uploader
|
|
3
|
+
*
|
|
4
|
+
* Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
|
|
5
|
+
*
|
|
6
|
+
* Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
|
|
7
|
+
* Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
|
|
8
|
+
*
|
|
9
|
+
* Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
|
|
10
|
+
*/
|
|
11
|
+
/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest*/
|
|
12
|
+
var qq = function(element) {
|
|
13
|
+
"use strict";
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
hide: function() {
|
|
17
|
+
element.style.display = 'none';
|
|
18
|
+
return this;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/** Returns the function which detaches attached event */
|
|
22
|
+
attach: function(type, fn) {
|
|
23
|
+
if (element.addEventListener){
|
|
24
|
+
element.addEventListener(type, fn, false);
|
|
25
|
+
} else if (element.attachEvent){
|
|
26
|
+
element.attachEvent('on' + type, fn);
|
|
27
|
+
}
|
|
28
|
+
return function() {
|
|
29
|
+
qq(element).detach(type, fn);
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
detach: function(type, fn) {
|
|
34
|
+
if (element.removeEventListener){
|
|
35
|
+
element.removeEventListener(type, fn, false);
|
|
36
|
+
} else if (element.attachEvent){
|
|
37
|
+
element.detachEvent('on' + type, fn);
|
|
38
|
+
}
|
|
39
|
+
return this;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
contains: function(descendant) {
|
|
43
|
+
// compareposition returns false in this case
|
|
44
|
+
if (element === descendant) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (element.contains){
|
|
49
|
+
return element.contains(descendant);
|
|
50
|
+
} else {
|
|
51
|
+
/*jslint bitwise: true*/
|
|
52
|
+
return !!(descendant.compareDocumentPosition(element) & 8);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Insert this element before elementB.
|
|
58
|
+
*/
|
|
59
|
+
insertBefore: function(elementB) {
|
|
60
|
+
elementB.parentNode.insertBefore(element, elementB);
|
|
61
|
+
return this;
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
remove: function() {
|
|
65
|
+
element.parentNode.removeChild(element);
|
|
66
|
+
return this;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sets styles for an element.
|
|
71
|
+
* Fixes opacity in IE6-8.
|
|
72
|
+
*/
|
|
73
|
+
css: function(styles) {
|
|
74
|
+
if (styles.opacity !== null){
|
|
75
|
+
if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
|
|
76
|
+
styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
qq.extend(element.style, styles);
|
|
80
|
+
|
|
81
|
+
return this;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
hasClass: function(name) {
|
|
85
|
+
var re = new RegExp('(^| )' + name + '( |$)');
|
|
86
|
+
return re.test(element.className);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
addClass: function(name) {
|
|
90
|
+
if (!qq(element).hasClass(name)){
|
|
91
|
+
element.className += ' ' + name;
|
|
92
|
+
}
|
|
93
|
+
return this;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
removeClass: function(name) {
|
|
97
|
+
var re = new RegExp('(^| )' + name + '( |$)');
|
|
98
|
+
element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
|
|
99
|
+
return this;
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
getByClass: function(className) {
|
|
103
|
+
var candidates,
|
|
104
|
+
result = [];
|
|
105
|
+
|
|
106
|
+
if (element.querySelectorAll){
|
|
107
|
+
return element.querySelectorAll('.' + className);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
candidates = element.getElementsByTagName("*");
|
|
111
|
+
|
|
112
|
+
qq.each(candidates, function(idx, val) {
|
|
113
|
+
if (qq(val).hasClass(className)){
|
|
114
|
+
result.push(val);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return result;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
children: function() {
|
|
121
|
+
var children = [],
|
|
122
|
+
child = element.firstChild;
|
|
123
|
+
|
|
124
|
+
while (child){
|
|
125
|
+
if (child.nodeType === 1){
|
|
126
|
+
children.push(child);
|
|
127
|
+
}
|
|
128
|
+
child = child.nextSibling;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return children;
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
setText: function(text) {
|
|
135
|
+
element.innerText = text;
|
|
136
|
+
element.textContent = text;
|
|
137
|
+
return this;
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
clearText: function() {
|
|
141
|
+
return qq(element).setText("");
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
qq.log = function(message, level) {
|
|
147
|
+
"use strict";
|
|
148
|
+
|
|
149
|
+
if (window.console) {
|
|
150
|
+
if (!level || level === 'info') {
|
|
151
|
+
window.console.log(message);
|
|
152
|
+
}
|
|
153
|
+
else
|
|
154
|
+
{
|
|
155
|
+
if (window.console[level]) {
|
|
156
|
+
window.console[level](message);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
window.console.log('<' + level + '> ' + message);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
qq.isObject = function(variable) {
|
|
166
|
+
"use strict";
|
|
167
|
+
return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
qq.isFunction = function(variable) {
|
|
171
|
+
"use strict";
|
|
172
|
+
return typeof(variable) === "function";
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
qq.isFileOrInput = function(maybeFileOrInput) {
|
|
176
|
+
"use strict";
|
|
177
|
+
if (window.File && maybeFileOrInput instanceof File) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
else if (window.HTMLInputElement) {
|
|
181
|
+
if (maybeFileOrInput instanceof HTMLInputElement) {
|
|
182
|
+
if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (maybeFileOrInput.tagName) {
|
|
188
|
+
if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
|
|
189
|
+
if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return false;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
qq.isXhrUploadSupported = function() {
|
|
199
|
+
"use strict";
|
|
200
|
+
var input = document.createElement('input');
|
|
201
|
+
input.type = 'file';
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
input.multiple !== undefined &&
|
|
205
|
+
typeof File !== "undefined" &&
|
|
206
|
+
typeof FormData !== "undefined" &&
|
|
207
|
+
typeof (new XMLHttpRequest()).upload !== "undefined" );
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
qq.isFolderDropSupported = function(dataTransfer) {
|
|
211
|
+
"use strict";
|
|
212
|
+
return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
qq.isFileChunkingSupported = function() {
|
|
216
|
+
"use strict";
|
|
217
|
+
return !qq.android() && //android's impl of Blob.slice is broken
|
|
218
|
+
qq.isXhrUploadSupported() &&
|
|
219
|
+
(File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
qq.extend = function (first, second, extendNested) {
|
|
223
|
+
"use strict";
|
|
224
|
+
qq.each(second, function(prop, val) {
|
|
225
|
+
if (extendNested && qq.isObject(val)) {
|
|
226
|
+
if (first[prop] === undefined) {
|
|
227
|
+
first[prop] = {};
|
|
228
|
+
}
|
|
229
|
+
qq.extend(first[prop], val, true);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
first[prop] = val;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Searches for a given element in the array, returns -1 if it is not present.
|
|
239
|
+
* @param {Number} [from] The index at which to begin the search
|
|
240
|
+
*/
|
|
241
|
+
qq.indexOf = function(arr, elt, from){
|
|
242
|
+
"use strict";
|
|
243
|
+
|
|
244
|
+
if (arr.indexOf) {
|
|
245
|
+
return arr.indexOf(elt, from);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
from = from || 0;
|
|
249
|
+
var len = arr.length;
|
|
250
|
+
|
|
251
|
+
if (from < 0) {
|
|
252
|
+
from += len;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
for (; from < len; from+=1){
|
|
256
|
+
if (arr.hasOwnProperty(from) && arr[from] === elt){
|
|
257
|
+
return from;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return -1;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
//this is a version 4 UUID
|
|
264
|
+
qq.getUniqueId = function(){
|
|
265
|
+
"use strict";
|
|
266
|
+
|
|
267
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
268
|
+
/*jslint eqeq: true, bitwise: true*/
|
|
269
|
+
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
|
270
|
+
return v.toString(16);
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
//
|
|
275
|
+
// Browsers and platforms detection
|
|
276
|
+
|
|
277
|
+
qq.ie = function(){
|
|
278
|
+
"use strict";
|
|
279
|
+
return navigator.userAgent.indexOf('MSIE') !== -1;
|
|
280
|
+
};
|
|
281
|
+
qq.ie10 = function(){
|
|
282
|
+
"use strict";
|
|
283
|
+
return navigator.userAgent.indexOf('MSIE 10') !== -1;
|
|
284
|
+
};
|
|
285
|
+
qq.safari = function(){
|
|
286
|
+
"use strict";
|
|
287
|
+
return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
|
|
288
|
+
};
|
|
289
|
+
qq.chrome = function(){
|
|
290
|
+
"use strict";
|
|
291
|
+
return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
|
|
292
|
+
};
|
|
293
|
+
qq.firefox = function(){
|
|
294
|
+
"use strict";
|
|
295
|
+
return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
|
|
296
|
+
};
|
|
297
|
+
qq.windows = function(){
|
|
298
|
+
"use strict";
|
|
299
|
+
return navigator.platform === "Win32";
|
|
300
|
+
};
|
|
301
|
+
qq.android = function(){
|
|
302
|
+
"use strict";
|
|
303
|
+
return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
//
|
|
307
|
+
// Events
|
|
308
|
+
|
|
309
|
+
qq.preventDefault = function(e){
|
|
310
|
+
"use strict";
|
|
311
|
+
if (e.preventDefault){
|
|
312
|
+
e.preventDefault();
|
|
313
|
+
} else{
|
|
314
|
+
e.returnValue = false;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Creates and returns element from html string
|
|
320
|
+
* Uses innerHTML to create an element
|
|
321
|
+
*/
|
|
322
|
+
qq.toElement = (function(){
|
|
323
|
+
"use strict";
|
|
324
|
+
var div = document.createElement('div');
|
|
325
|
+
return function(html){
|
|
326
|
+
div.innerHTML = html;
|
|
327
|
+
var element = div.firstChild;
|
|
328
|
+
div.removeChild(element);
|
|
329
|
+
return element;
|
|
330
|
+
};
|
|
331
|
+
}());
|
|
332
|
+
|
|
333
|
+
//key and value are passed to callback for each item in the object or array
|
|
334
|
+
qq.each = function(obj, callback) {
|
|
335
|
+
"use strict";
|
|
336
|
+
var key, retVal;
|
|
337
|
+
if (obj) {
|
|
338
|
+
for (key in obj) {
|
|
339
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
340
|
+
retVal = callback(key, obj[key]);
|
|
341
|
+
if (retVal === false) {
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* obj2url() takes a json-object as argument and generates
|
|
351
|
+
* a querystring. pretty much like jQuery.param()
|
|
352
|
+
*
|
|
353
|
+
* how to use:
|
|
354
|
+
*
|
|
355
|
+
* `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
|
|
356
|
+
*
|
|
357
|
+
* will result in:
|
|
358
|
+
*
|
|
359
|
+
* `http://any.url/upload?otherParam=value&a=b&c=d`
|
|
360
|
+
*
|
|
361
|
+
* @param Object JSON-Object
|
|
362
|
+
* @param String current querystring-part
|
|
363
|
+
* @return String encoded querystring
|
|
364
|
+
*/
|
|
365
|
+
qq.obj2url = function(obj, temp, prefixDone){
|
|
366
|
+
"use strict";
|
|
367
|
+
/*jshint laxbreak: true*/
|
|
368
|
+
var i, len,
|
|
369
|
+
uristrings = [],
|
|
370
|
+
prefix = '&',
|
|
371
|
+
add = function(nextObj, i){
|
|
372
|
+
var nextTemp = temp
|
|
373
|
+
? (/\[\]$/.test(temp)) // prevent double-encoding
|
|
374
|
+
? temp
|
|
375
|
+
: temp+'['+i+']'
|
|
376
|
+
: i;
|
|
377
|
+
if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
|
|
378
|
+
uristrings.push(
|
|
379
|
+
(typeof nextObj === 'object')
|
|
380
|
+
? qq.obj2url(nextObj, nextTemp, true)
|
|
381
|
+
: (Object.prototype.toString.call(nextObj) === '[object Function]')
|
|
382
|
+
? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
|
|
383
|
+
: encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
if (!prefixDone && temp) {
|
|
389
|
+
prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
|
|
390
|
+
uristrings.push(temp);
|
|
391
|
+
uristrings.push(qq.obj2url(obj));
|
|
392
|
+
} else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
|
|
393
|
+
// we wont use a for-in-loop on an array (performance)
|
|
394
|
+
for (i = -1, len = obj.length; i < len; i+=1){
|
|
395
|
+
add(obj[i], i);
|
|
396
|
+
}
|
|
397
|
+
} else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
|
|
398
|
+
// for anything else but a scalar, we will use for-in-loop
|
|
399
|
+
for (i in obj){
|
|
400
|
+
if (obj.hasOwnProperty(i)) {
|
|
401
|
+
add(obj[i], i);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (temp) {
|
|
409
|
+
return uristrings.join(prefix);
|
|
410
|
+
} else {
|
|
411
|
+
return uristrings.join(prefix)
|
|
412
|
+
.replace(/^&/, '')
|
|
413
|
+
.replace(/%20/g, '+');
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
qq.obj2FormData = function(obj, formData, arrayKeyName) {
|
|
418
|
+
"use strict";
|
|
419
|
+
if (!formData) {
|
|
420
|
+
formData = new FormData();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
qq.each(obj, function(key, val) {
|
|
424
|
+
key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
|
|
425
|
+
|
|
426
|
+
if (qq.isObject(val)) {
|
|
427
|
+
qq.obj2FormData(val, formData, key);
|
|
428
|
+
}
|
|
429
|
+
else if (qq.isFunction(val)) {
|
|
430
|
+
formData.append(encodeURIComponent(key), encodeURIComponent(val()));
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
formData.append(encodeURIComponent(key), encodeURIComponent(val));
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
return formData;
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
qq.obj2Inputs = function(obj, form) {
|
|
441
|
+
"use strict";
|
|
442
|
+
var input;
|
|
443
|
+
|
|
444
|
+
if (!form) {
|
|
445
|
+
form = document.createElement('form');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
qq.obj2FormData(obj, {
|
|
449
|
+
append: function(key, val) {
|
|
450
|
+
input = document.createElement('input');
|
|
451
|
+
input.setAttribute('name', key);
|
|
452
|
+
input.setAttribute('value', val);
|
|
453
|
+
form.appendChild(input);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
return form;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
qq.setCookie = function(name, value, days) {
|
|
461
|
+
var date = new Date(),
|
|
462
|
+
expires = "";
|
|
463
|
+
|
|
464
|
+
if (days) {
|
|
465
|
+
date.setTime(date.getTime()+(days*24*60*60*1000));
|
|
466
|
+
expires = "; expires="+date.toGMTString();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
document.cookie = name+"="+value+expires+"; path=/";
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
qq.getCookie = function(name) {
|
|
473
|
+
var nameEQ = name + "=",
|
|
474
|
+
ca = document.cookie.split(';'),
|
|
475
|
+
c;
|
|
476
|
+
|
|
477
|
+
for(var i=0;i < ca.length;i++) {
|
|
478
|
+
c = ca[i];
|
|
479
|
+
while (c.charAt(0)==' ') {
|
|
480
|
+
c = c.substring(1,c.length);
|
|
481
|
+
}
|
|
482
|
+
if (c.indexOf(nameEQ) === 0) {
|
|
483
|
+
return c.substring(nameEQ.length,c.length);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
qq.getCookieNames = function(regexp) {
|
|
489
|
+
var cookies = document.cookie.split(';'),
|
|
490
|
+
cookieNames = [];
|
|
491
|
+
|
|
492
|
+
qq.each(cookies, function(idx, cookie) {
|
|
493
|
+
cookie = cookie.trim();
|
|
494
|
+
|
|
495
|
+
var equalsIdx = cookie.indexOf("=");
|
|
496
|
+
|
|
497
|
+
if (cookie.match(regexp)) {
|
|
498
|
+
cookieNames.push(cookie.substr(0, equalsIdx));
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
return cookieNames;
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
qq.deleteCookie = function(name) {
|
|
506
|
+
qq.setCookie(name, "", -1);
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
qq.areCookiesEnabled = function() {
|
|
510
|
+
var randNum = Math.random() * 100000,
|
|
511
|
+
name = "qqCookieTest:" + randNum;
|
|
512
|
+
qq.setCookie(name, 1);
|
|
513
|
+
|
|
514
|
+
if (qq.getCookie(name)) {
|
|
515
|
+
qq.deleteCookie(name);
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
return false;
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
|
|
523
|
+
* implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
|
|
524
|
+
*/
|
|
525
|
+
qq.parseJson = function(json) {
|
|
526
|
+
/*jshint evil: true*/
|
|
527
|
+
if (typeof JSON.parse === "function") {
|
|
528
|
+
return JSON.parse(json);
|
|
529
|
+
} else {
|
|
530
|
+
return eval("(" + json + ")");
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* A generic module which supports object disposing in dispose() method.
|
|
536
|
+
* */
|
|
537
|
+
qq.DisposeSupport = function() {
|
|
538
|
+
"use strict";
|
|
539
|
+
var disposers = [];
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
/** Run all registered disposers */
|
|
543
|
+
dispose: function() {
|
|
544
|
+
var disposer;
|
|
545
|
+
do {
|
|
546
|
+
disposer = disposers.shift();
|
|
547
|
+
if (disposer) {
|
|
548
|
+
disposer();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
while (disposer);
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
/** Attach event handler and register de-attacher as a disposer */
|
|
555
|
+
attach: function() {
|
|
556
|
+
var args = arguments;
|
|
557
|
+
/*jslint undef:true*/
|
|
558
|
+
this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
/** Add disposer to the collection */
|
|
562
|
+
addDisposer: function(disposeFunction) {
|
|
563
|
+
disposers.push(disposeFunction);
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
};
|
|
567
|
+
qq.UploadButton = function(o){
|
|
568
|
+
this._options = {
|
|
569
|
+
element: null,
|
|
570
|
+
// if set to true adds multiple attribute to file input
|
|
571
|
+
multiple: false,
|
|
572
|
+
acceptFiles: null,
|
|
573
|
+
// name attribute of file input
|
|
574
|
+
name: 'file',
|
|
575
|
+
onChange: function(input){},
|
|
576
|
+
hoverClass: 'qq-upload-button-hover',
|
|
577
|
+
focusClass: 'qq-upload-button-focus'
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
qq.extend(this._options, o);
|
|
581
|
+
this._disposeSupport = new qq.DisposeSupport();
|
|
582
|
+
|
|
583
|
+
this._element = this._options.element;
|
|
584
|
+
|
|
585
|
+
// make button suitable container for input
|
|
586
|
+
qq(this._element).css({
|
|
587
|
+
position: 'relative',
|
|
588
|
+
overflow: 'hidden',
|
|
589
|
+
// Make sure browse button is in the right side
|
|
590
|
+
// in Internet Explorer
|
|
591
|
+
direction: 'ltr'
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
this._input = this._createInput();
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
qq.UploadButton.prototype = {
|
|
598
|
+
/* returns file input element */
|
|
599
|
+
getInput: function(){
|
|
600
|
+
return this._input;
|
|
601
|
+
},
|
|
602
|
+
/* cleans/recreates the file input */
|
|
603
|
+
reset: function(){
|
|
604
|
+
if (this._input.parentNode){
|
|
605
|
+
qq(this._input).remove();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
qq(this._element).removeClass(this._options.focusClass);
|
|
609
|
+
this._input = this._createInput();
|
|
610
|
+
},
|
|
611
|
+
_createInput: function(){
|
|
612
|
+
var input = document.createElement("input");
|
|
613
|
+
|
|
614
|
+
if (this._options.multiple){
|
|
615
|
+
input.setAttribute("multiple", "multiple");
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
|
|
619
|
+
|
|
620
|
+
input.setAttribute("type", "file");
|
|
621
|
+
input.setAttribute("name", this._options.name);
|
|
622
|
+
|
|
623
|
+
qq(input).css({
|
|
624
|
+
position: 'absolute',
|
|
625
|
+
// in Opera only 'browse' button
|
|
626
|
+
// is clickable and it is located at
|
|
627
|
+
// the right side of the input
|
|
628
|
+
right: 0,
|
|
629
|
+
top: 0,
|
|
630
|
+
fontFamily: 'Arial',
|
|
631
|
+
// 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
|
|
632
|
+
fontSize: '118px',
|
|
633
|
+
margin: 0,
|
|
634
|
+
padding: 0,
|
|
635
|
+
cursor: 'pointer',
|
|
636
|
+
opacity: 0
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
this._element.appendChild(input);
|
|
640
|
+
|
|
641
|
+
var self = this;
|
|
642
|
+
this._disposeSupport.attach(input, 'change', function(){
|
|
643
|
+
self._options.onChange(input);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
this._disposeSupport.attach(input, 'mouseover', function(){
|
|
647
|
+
qq(self._element).addClass(self._options.hoverClass);
|
|
648
|
+
});
|
|
649
|
+
this._disposeSupport.attach(input, 'mouseout', function(){
|
|
650
|
+
qq(self._element).removeClass(self._options.hoverClass);
|
|
651
|
+
});
|
|
652
|
+
this._disposeSupport.attach(input, 'focus', function(){
|
|
653
|
+
qq(self._element).addClass(self._options.focusClass);
|
|
654
|
+
});
|
|
655
|
+
this._disposeSupport.attach(input, 'blur', function(){
|
|
656
|
+
qq(self._element).removeClass(self._options.focusClass);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// IE and Opera, unfortunately have 2 tab stops on file input
|
|
660
|
+
// which is unacceptable in our case, disable keyboard access
|
|
661
|
+
if (window.attachEvent){
|
|
662
|
+
// it is IE or Opera
|
|
663
|
+
input.setAttribute('tabIndex', "-1");
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return input;
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
qq.FineUploaderBasic = function(o){
|
|
670
|
+
var that = this;
|
|
671
|
+
this._options = {
|
|
672
|
+
debug: false,
|
|
673
|
+
button: null,
|
|
674
|
+
multiple: true,
|
|
675
|
+
maxConnections: 3,
|
|
676
|
+
disableCancelForFormUploads: false,
|
|
677
|
+
autoUpload: true,
|
|
678
|
+
request: {
|
|
679
|
+
endpoint: '/server/upload',
|
|
680
|
+
params: {},
|
|
681
|
+
paramsInBody: false,
|
|
682
|
+
customHeaders: {},
|
|
683
|
+
forceMultipart: true,
|
|
684
|
+
inputName: 'qqfile',
|
|
685
|
+
uuidName: 'qquuid',
|
|
686
|
+
totalFileSizeName: 'qqtotalfilesize'
|
|
687
|
+
},
|
|
688
|
+
validation: {
|
|
689
|
+
allowedExtensions: [],
|
|
690
|
+
sizeLimit: 0,
|
|
691
|
+
minSizeLimit: 0,
|
|
692
|
+
stopOnFirstInvalidFile: true
|
|
693
|
+
},
|
|
694
|
+
callbacks: {
|
|
695
|
+
onSubmit: function(id, fileName){},
|
|
696
|
+
onComplete: function(id, fileName, responseJSON){},
|
|
697
|
+
onCancel: function(id, fileName){},
|
|
698
|
+
onUpload: function(id, fileName){},
|
|
699
|
+
onUploadChunk: function(id, fileName, chunkData){},
|
|
700
|
+
onResume: function(id, fileName, chunkData){},
|
|
701
|
+
onProgress: function(id, fileName, loaded, total){},
|
|
702
|
+
onError: function(id, fileName, reason) {},
|
|
703
|
+
onAutoRetry: function(id, fileName, attemptNumber) {},
|
|
704
|
+
onManualRetry: function(id, fileName) {},
|
|
705
|
+
onValidateBatch: function(fileData) {},
|
|
706
|
+
onValidate: function(fileData) {}
|
|
707
|
+
},
|
|
708
|
+
messages: {
|
|
709
|
+
typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
|
|
710
|
+
sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
|
|
711
|
+
minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
|
|
712
|
+
emptyError: "{file} is empty, please select files again without it.",
|
|
713
|
+
noFilesError: "No files to upload.",
|
|
714
|
+
onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
|
|
715
|
+
},
|
|
716
|
+
retry: {
|
|
717
|
+
enableAuto: false,
|
|
718
|
+
maxAutoAttempts: 3,
|
|
719
|
+
autoAttemptDelay: 5,
|
|
720
|
+
preventRetryResponseProperty: 'preventRetry'
|
|
721
|
+
},
|
|
722
|
+
classes: {
|
|
723
|
+
buttonHover: 'qq-upload-button-hover',
|
|
724
|
+
buttonFocus: 'qq-upload-button-focus'
|
|
725
|
+
},
|
|
726
|
+
chunking: {
|
|
727
|
+
enabled: false,
|
|
728
|
+
partSize: 2000000,
|
|
729
|
+
paramNames: {
|
|
730
|
+
partIndex: 'qqpartindex',
|
|
731
|
+
partByteOffset: 'qqpartbyteoffset',
|
|
732
|
+
chunkSize: 'qqchunksize',
|
|
733
|
+
totalFileSize: 'qqtotalfilesize',
|
|
734
|
+
totalParts: 'qqtotalparts',
|
|
735
|
+
filename: 'qqfilename'
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
resume: {
|
|
739
|
+
enabled: false,
|
|
740
|
+
id: null,
|
|
741
|
+
cookiesExpireIn: 7, //days
|
|
742
|
+
paramNames: {
|
|
743
|
+
resuming: "qqresume"
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
formatFileName: function(fileName) {
|
|
747
|
+
if (fileName.length > 33) {
|
|
748
|
+
fileName = fileName.slice(0, 19) + '...' + fileName.slice(-14);
|
|
749
|
+
}
|
|
750
|
+
return fileName;
|
|
751
|
+
},
|
|
752
|
+
text: {
|
|
753
|
+
sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
qq.extend(this._options, o, true);
|
|
758
|
+
this._wrapCallbacks();
|
|
759
|
+
this._disposeSupport = new qq.DisposeSupport();
|
|
760
|
+
|
|
761
|
+
// number of files being uploaded
|
|
762
|
+
this._filesInProgress = [];
|
|
763
|
+
|
|
764
|
+
this._storedFileIds = [];
|
|
765
|
+
|
|
766
|
+
this._autoRetries = [];
|
|
767
|
+
this._retryTimeouts = [];
|
|
768
|
+
this._preventRetries = [];
|
|
769
|
+
|
|
770
|
+
this._paramsStore = this._createParamsStore();
|
|
771
|
+
this._endpointStore = this._createEndpointStore();
|
|
772
|
+
|
|
773
|
+
this._handler = this._createUploadHandler();
|
|
774
|
+
|
|
775
|
+
if (this._options.button){
|
|
776
|
+
this._button = this._createUploadButton(this._options.button);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
this._preventLeaveInProgress();
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
qq.FineUploaderBasic.prototype = {
|
|
783
|
+
log: function(str, level) {
|
|
784
|
+
if (this._options.debug && (!level || level === 'info')) {
|
|
785
|
+
qq.log('[FineUploader] ' + str);
|
|
786
|
+
}
|
|
787
|
+
else if (level && level !== 'info') {
|
|
788
|
+
qq.log('[FineUploader] ' + str, level);
|
|
789
|
+
|
|
790
|
+
}
|
|
791
|
+
},
|
|
792
|
+
setParams: function(params, fileId) {
|
|
793
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
|
794
|
+
if (fileId == null) {
|
|
795
|
+
this._options.request.params = params;
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
this._paramsStore.setParams(params, fileId);
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
setEndpoint: function(endpoint, fileId) {
|
|
802
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
|
803
|
+
if (fileId == null) {
|
|
804
|
+
this._options.request.endpoint = endpoint;
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
this._endpointStore.setEndpoint(endpoint, fileId);
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
getInProgress: function(){
|
|
811
|
+
return this._filesInProgress.length;
|
|
812
|
+
},
|
|
813
|
+
uploadStoredFiles: function(){
|
|
814
|
+
"use strict";
|
|
815
|
+
var idToUpload;
|
|
816
|
+
|
|
817
|
+
while(this._storedFileIds.length) {
|
|
818
|
+
idToUpload = this._storedFileIds.shift();
|
|
819
|
+
this._filesInProgress.push(idToUpload);
|
|
820
|
+
this._handler.upload(idToUpload);
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
clearStoredFiles: function(){
|
|
824
|
+
this._storedFileIds = [];
|
|
825
|
+
},
|
|
826
|
+
retry: function(id) {
|
|
827
|
+
if (this._onBeforeManualRetry(id)) {
|
|
828
|
+
this._handler.retry(id);
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
cancel: function(fileId) {
|
|
836
|
+
this._handler.cancel(fileId);
|
|
837
|
+
},
|
|
838
|
+
reset: function() {
|
|
839
|
+
this.log("Resetting uploader...");
|
|
840
|
+
this._handler.reset();
|
|
841
|
+
this._filesInProgress = [];
|
|
842
|
+
this._storedFileIds = [];
|
|
843
|
+
this._autoRetries = [];
|
|
844
|
+
this._retryTimeouts = [];
|
|
845
|
+
this._preventRetries = [];
|
|
846
|
+
this._button.reset();
|
|
847
|
+
this._paramsStore.reset();
|
|
848
|
+
this._endpointStore.reset();
|
|
849
|
+
},
|
|
850
|
+
addFiles: function(filesOrInputs) {
|
|
851
|
+
var self = this,
|
|
852
|
+
verifiedFilesOrInputs = [],
|
|
853
|
+
index, fileOrInput;
|
|
854
|
+
|
|
855
|
+
if (filesOrInputs) {
|
|
856
|
+
if (!window.FileList || !(filesOrInputs instanceof FileList)) {
|
|
857
|
+
filesOrInputs = [].concat(filesOrInputs);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
for (index = 0; index < filesOrInputs.length; index+=1) {
|
|
861
|
+
fileOrInput = filesOrInputs[index];
|
|
862
|
+
|
|
863
|
+
if (qq.isFileOrInput(fileOrInput)) {
|
|
864
|
+
verifiedFilesOrInputs.push(fileOrInput);
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
|
|
872
|
+
this._uploadFileList(verifiedFilesOrInputs);
|
|
873
|
+
}
|
|
874
|
+
},
|
|
875
|
+
getUuid: function(fileId) {
|
|
876
|
+
return this._handler.getUuid(fileId);
|
|
877
|
+
},
|
|
878
|
+
getResumableFilesData: function() {
|
|
879
|
+
return this._handler.getResumableFilesData();
|
|
880
|
+
},
|
|
881
|
+
getSize: function(fileId) {
|
|
882
|
+
return this._handler.getSize(fileId);
|
|
883
|
+
},
|
|
884
|
+
getFile: function(fileId) {
|
|
885
|
+
return this._handler.getFile(fileId);
|
|
886
|
+
},
|
|
887
|
+
_createUploadButton: function(element){
|
|
888
|
+
var self = this;
|
|
889
|
+
|
|
890
|
+
var button = new qq.UploadButton({
|
|
891
|
+
element: element,
|
|
892
|
+
multiple: this._options.multiple && qq.isXhrUploadSupported(),
|
|
893
|
+
acceptFiles: this._options.validation.acceptFiles,
|
|
894
|
+
onChange: function(input){
|
|
895
|
+
self._onInputChange(input);
|
|
896
|
+
},
|
|
897
|
+
hoverClass: this._options.classes.buttonHover,
|
|
898
|
+
focusClass: this._options.classes.buttonFocus
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
this._disposeSupport.addDisposer(function() { button.dispose(); });
|
|
902
|
+
return button;
|
|
903
|
+
},
|
|
904
|
+
_createUploadHandler: function(){
|
|
905
|
+
var self = this;
|
|
906
|
+
|
|
907
|
+
return new qq.UploadHandler({
|
|
908
|
+
debug: this._options.debug,
|
|
909
|
+
forceMultipart: this._options.request.forceMultipart,
|
|
910
|
+
maxConnections: this._options.maxConnections,
|
|
911
|
+
customHeaders: this._options.request.customHeaders,
|
|
912
|
+
inputName: this._options.request.inputName,
|
|
913
|
+
uuidParamName: this._options.request.uuidName,
|
|
914
|
+
totalFileSizeParamName: this._options.request.totalFileSizeName,
|
|
915
|
+
demoMode: this._options.demoMode,
|
|
916
|
+
paramsInBody: this._options.request.paramsInBody,
|
|
917
|
+
paramsStore: this._paramsStore,
|
|
918
|
+
endpointStore: this._endpointStore,
|
|
919
|
+
chunking: this._options.chunking,
|
|
920
|
+
resume: this._options.resume,
|
|
921
|
+
log: function(str, level) {
|
|
922
|
+
self.log(str, level);
|
|
923
|
+
},
|
|
924
|
+
onProgress: function(id, fileName, loaded, total){
|
|
925
|
+
self._onProgress(id, fileName, loaded, total);
|
|
926
|
+
self._options.callbacks.onProgress(id, fileName, loaded, total);
|
|
927
|
+
},
|
|
928
|
+
onComplete: function(id, fileName, result, xhr){
|
|
929
|
+
self._onComplete(id, fileName, result, xhr);
|
|
930
|
+
self._options.callbacks.onComplete(id, fileName, result);
|
|
931
|
+
},
|
|
932
|
+
onCancel: function(id, fileName){
|
|
933
|
+
self._onCancel(id, fileName);
|
|
934
|
+
self._options.callbacks.onCancel(id, fileName);
|
|
935
|
+
},
|
|
936
|
+
onUpload: function(id, fileName){
|
|
937
|
+
self._onUpload(id, fileName);
|
|
938
|
+
self._options.callbacks.onUpload(id, fileName);
|
|
939
|
+
},
|
|
940
|
+
onUploadChunk: function(id, fileName, chunkData){
|
|
941
|
+
self._options.callbacks.onUploadChunk(id, fileName, chunkData);
|
|
942
|
+
},
|
|
943
|
+
onResume: function(id, fileName, chunkData) {
|
|
944
|
+
return self._options.callbacks.onResume(id, fileName, chunkData);
|
|
945
|
+
},
|
|
946
|
+
onAutoRetry: function(id, fileName, responseJSON, xhr) {
|
|
947
|
+
self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
|
|
948
|
+
|
|
949
|
+
if (self._shouldAutoRetry(id, fileName, responseJSON)) {
|
|
950
|
+
self._maybeParseAndSendUploadError(id, fileName, responseJSON, xhr);
|
|
951
|
+
self._options.callbacks.onAutoRetry(id, fileName, self._autoRetries[id] + 1);
|
|
952
|
+
self._onBeforeAutoRetry(id, fileName);
|
|
953
|
+
|
|
954
|
+
self._retryTimeouts[id] = setTimeout(function() {
|
|
955
|
+
self._onAutoRetry(id, fileName, responseJSON)
|
|
956
|
+
}, self._options.retry.autoAttemptDelay * 1000);
|
|
957
|
+
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
},
|
|
966
|
+
_preventLeaveInProgress: function(){
|
|
967
|
+
var self = this;
|
|
968
|
+
|
|
969
|
+
this._disposeSupport.attach(window, 'beforeunload', function(e){
|
|
970
|
+
if (!self._filesInProgress.length){return;}
|
|
971
|
+
|
|
972
|
+
var e = e || window.event;
|
|
973
|
+
// for ie, ff
|
|
974
|
+
e.returnValue = self._options.messages.onLeave;
|
|
975
|
+
// for webkit
|
|
976
|
+
return self._options.messages.onLeave;
|
|
977
|
+
});
|
|
978
|
+
},
|
|
979
|
+
_onSubmit: function(id, fileName){
|
|
980
|
+
if (this._options.autoUpload) {
|
|
981
|
+
this._filesInProgress.push(id);
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
_onProgress: function(id, fileName, loaded, total){
|
|
985
|
+
},
|
|
986
|
+
_onComplete: function(id, fileName, result, xhr){
|
|
987
|
+
this._removeFromFilesInProgress(id);
|
|
988
|
+
this._maybeParseAndSendUploadError(id, fileName, result, xhr);
|
|
989
|
+
},
|
|
990
|
+
_onCancel: function(id, fileName){
|
|
991
|
+
this._removeFromFilesInProgress(id);
|
|
992
|
+
|
|
993
|
+
clearTimeout(this._retryTimeouts[id]);
|
|
994
|
+
|
|
995
|
+
var storedFileIndex = qq.indexOf(this._storedFileIds, id);
|
|
996
|
+
if (!this._options.autoUpload && storedFileIndex >= 0) {
|
|
997
|
+
this._storedFileIds.splice(storedFileIndex, 1);
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
_removeFromFilesInProgress: function(id) {
|
|
1001
|
+
var index = qq.indexOf(this._filesInProgress, id);
|
|
1002
|
+
if (index >= 0) {
|
|
1003
|
+
this._filesInProgress.splice(index, 1);
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
_onUpload: function(id, fileName){},
|
|
1007
|
+
_onInputChange: function(input){
|
|
1008
|
+
if (qq.isXhrUploadSupported()){
|
|
1009
|
+
this.addFiles(input.files);
|
|
1010
|
+
} else {
|
|
1011
|
+
this.addFiles(input);
|
|
1012
|
+
}
|
|
1013
|
+
this._button.reset();
|
|
1014
|
+
},
|
|
1015
|
+
_onBeforeAutoRetry: function(id, fileName) {
|
|
1016
|
+
this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + fileName + "...");
|
|
1017
|
+
},
|
|
1018
|
+
_onAutoRetry: function(id, fileName, responseJSON) {
|
|
1019
|
+
this.log("Retrying " + fileName + "...");
|
|
1020
|
+
this._autoRetries[id]++;
|
|
1021
|
+
this._handler.retry(id);
|
|
1022
|
+
},
|
|
1023
|
+
_shouldAutoRetry: function(id, fileName, responseJSON) {
|
|
1024
|
+
if (!this._preventRetries[id] && this._options.retry.enableAuto) {
|
|
1025
|
+
if (this._autoRetries[id] === undefined) {
|
|
1026
|
+
this._autoRetries[id] = 0;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
return this._autoRetries[id] < this._options.retry.maxAutoAttempts
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
return false;
|
|
1033
|
+
},
|
|
1034
|
+
//return false if we should not attempt the requested retry
|
|
1035
|
+
_onBeforeManualRetry: function(id) {
|
|
1036
|
+
if (this._preventRetries[id]) {
|
|
1037
|
+
this.log("Retries are forbidden for id " + id, 'warn');
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
else if (this._handler.isValid(id)) {
|
|
1041
|
+
var fileName = this._handler.getName(id);
|
|
1042
|
+
|
|
1043
|
+
if (this._options.callbacks.onManualRetry(id, fileName) === false) {
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
|
|
1048
|
+
this._filesInProgress.push(id);
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
this.log("'" + id + "' is not a valid file ID", 'error');
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
},
|
|
1056
|
+
_maybeParseAndSendUploadError: function(id, fileName, response, xhr) {
|
|
1057
|
+
//assuming no one will actually set the response code to something other than 200 and still set 'success' to true
|
|
1058
|
+
if (!response.success){
|
|
1059
|
+
if (xhr && xhr.status !== 200 && !response.error) {
|
|
1060
|
+
this._options.callbacks.onError(id, fileName, "XHR returned response code " + xhr.status);
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
var errorReason = response.error ? response.error : "Upload failure reason unknown";
|
|
1064
|
+
this._options.callbacks.onError(id, fileName, errorReason);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
_uploadFileList: function(files){
|
|
1069
|
+
var validationDescriptors, index, batchInvalid;
|
|
1070
|
+
|
|
1071
|
+
validationDescriptors = this._getValidationDescriptors(files);
|
|
1072
|
+
batchInvalid = this._options.callbacks.onValidateBatch(validationDescriptors) === false;
|
|
1073
|
+
|
|
1074
|
+
if (!batchInvalid) {
|
|
1075
|
+
if (files.length > 0) {
|
|
1076
|
+
for (index = 0; index < files.length; index++){
|
|
1077
|
+
if (this._validateFile(files[index])){
|
|
1078
|
+
this._uploadFile(files[index]);
|
|
1079
|
+
} else {
|
|
1080
|
+
if (this._options.validation.stopOnFirstInvalidFile){
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
this._error('noFilesError', "");
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
_uploadFile: function(fileContainer){
|
|
1092
|
+
var id = this._handler.add(fileContainer);
|
|
1093
|
+
var fileName = this._handler.getName(id);
|
|
1094
|
+
|
|
1095
|
+
if (this._options.callbacks.onSubmit(id, fileName) !== false){
|
|
1096
|
+
this._onSubmit(id, fileName);
|
|
1097
|
+
if (this._options.autoUpload) {
|
|
1098
|
+
this._handler.upload(id);
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
this._storeFileForLater(id);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
1105
|
+
_storeFileForLater: function(id) {
|
|
1106
|
+
this._storedFileIds.push(id);
|
|
1107
|
+
},
|
|
1108
|
+
_validateFile: function(file){
|
|
1109
|
+
var validationDescriptor, name, size;
|
|
1110
|
+
|
|
1111
|
+
validationDescriptor = this._getValidationDescriptor(file);
|
|
1112
|
+
name = validationDescriptor.name;
|
|
1113
|
+
size = validationDescriptor.size;
|
|
1114
|
+
|
|
1115
|
+
if (this._options.callbacks.onValidate(validationDescriptor) === false) {
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (!this._isAllowedExtension(name)){
|
|
1120
|
+
this._error('typeError', name);
|
|
1121
|
+
return false;
|
|
1122
|
+
|
|
1123
|
+
}
|
|
1124
|
+
else if (size === 0){
|
|
1125
|
+
this._error('emptyError', name);
|
|
1126
|
+
return false;
|
|
1127
|
+
|
|
1128
|
+
}
|
|
1129
|
+
else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
|
|
1130
|
+
this._error('sizeError', name);
|
|
1131
|
+
return false;
|
|
1132
|
+
|
|
1133
|
+
}
|
|
1134
|
+
else if (size && size < this._options.validation.minSizeLimit){
|
|
1135
|
+
this._error('minSizeError', name);
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
return true;
|
|
1140
|
+
},
|
|
1141
|
+
_error: function(code, fileName){
|
|
1142
|
+
var message = this._options.messages[code];
|
|
1143
|
+
function r(name, replacement){ message = message.replace(name, replacement); }
|
|
1144
|
+
|
|
1145
|
+
var extensions = this._options.validation.allowedExtensions.join(', ').toLowerCase();
|
|
1146
|
+
|
|
1147
|
+
r('{file}', this._options.formatFileName(fileName));
|
|
1148
|
+
r('{extensions}', extensions);
|
|
1149
|
+
r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
|
|
1150
|
+
r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
|
|
1151
|
+
|
|
1152
|
+
this._options.callbacks.onError(null, fileName, message);
|
|
1153
|
+
|
|
1154
|
+
return message;
|
|
1155
|
+
},
|
|
1156
|
+
_isAllowedExtension: function(fileName){
|
|
1157
|
+
var allowed = this._options.validation.allowedExtensions,
|
|
1158
|
+
valid = false;
|
|
1159
|
+
|
|
1160
|
+
if (!allowed.length) {
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
qq.each(allowed, function(idx, allowedExt) {
|
|
1165
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
|
1166
|
+
var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
|
|
1167
|
+
|
|
1168
|
+
if (fileName.match(extRegex) != null) {
|
|
1169
|
+
valid = true;
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
return valid;
|
|
1175
|
+
},
|
|
1176
|
+
_formatSize: function(bytes){
|
|
1177
|
+
var i = -1;
|
|
1178
|
+
do {
|
|
1179
|
+
bytes = bytes / 1024;
|
|
1180
|
+
i++;
|
|
1181
|
+
} while (bytes > 99);
|
|
1182
|
+
|
|
1183
|
+
return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
|
|
1184
|
+
},
|
|
1185
|
+
_wrapCallbacks: function() {
|
|
1186
|
+
var self, safeCallback;
|
|
1187
|
+
|
|
1188
|
+
self = this;
|
|
1189
|
+
|
|
1190
|
+
safeCallback = function(name, callback, args) {
|
|
1191
|
+
try {
|
|
1192
|
+
return callback.apply(self, args);
|
|
1193
|
+
}
|
|
1194
|
+
catch (exception) {
|
|
1195
|
+
self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
for (var prop in this._options.callbacks) {
|
|
1200
|
+
(function() {
|
|
1201
|
+
var callbackName, callbackFunc;
|
|
1202
|
+
callbackName = prop;
|
|
1203
|
+
callbackFunc = self._options.callbacks[callbackName];
|
|
1204
|
+
self._options.callbacks[callbackName] = function() {
|
|
1205
|
+
return safeCallback(callbackName, callbackFunc, arguments);
|
|
1206
|
+
}
|
|
1207
|
+
}());
|
|
1208
|
+
}
|
|
1209
|
+
},
|
|
1210
|
+
_parseFileName: function(file) {
|
|
1211
|
+
var name;
|
|
1212
|
+
|
|
1213
|
+
if (file.value){
|
|
1214
|
+
// it is a file input
|
|
1215
|
+
// get input value and remove path to normalize
|
|
1216
|
+
name = file.value.replace(/.*(\/|\\)/, "");
|
|
1217
|
+
} else {
|
|
1218
|
+
// fix missing properties in Safari 4 and firefox 11.0a2
|
|
1219
|
+
name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
return name;
|
|
1223
|
+
},
|
|
1224
|
+
_parseFileSize: function(file) {
|
|
1225
|
+
var size;
|
|
1226
|
+
|
|
1227
|
+
if (!file.value){
|
|
1228
|
+
// fix missing properties in Safari 4 and firefox 11.0a2
|
|
1229
|
+
size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
return size;
|
|
1233
|
+
},
|
|
1234
|
+
_getValidationDescriptor: function(file) {
|
|
1235
|
+
var name, size, fileDescriptor;
|
|
1236
|
+
|
|
1237
|
+
fileDescriptor = {};
|
|
1238
|
+
name = this._parseFileName(file);
|
|
1239
|
+
size = this._parseFileSize(file);
|
|
1240
|
+
|
|
1241
|
+
fileDescriptor.name = name;
|
|
1242
|
+
if (size) {
|
|
1243
|
+
fileDescriptor.size = size;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
return fileDescriptor;
|
|
1247
|
+
},
|
|
1248
|
+
_getValidationDescriptors: function(files) {
|
|
1249
|
+
var self = this,
|
|
1250
|
+
fileDescriptors = [];
|
|
1251
|
+
|
|
1252
|
+
qq.each(files, function(idx, file) {
|
|
1253
|
+
fileDescriptors.push(self._getValidationDescriptor(file));
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
return fileDescriptors;
|
|
1257
|
+
},
|
|
1258
|
+
_createParamsStore: function() {
|
|
1259
|
+
var paramsStore = {},
|
|
1260
|
+
self = this;
|
|
1261
|
+
|
|
1262
|
+
return {
|
|
1263
|
+
setParams: function(params, fileId) {
|
|
1264
|
+
var paramsCopy = {};
|
|
1265
|
+
qq.extend(paramsCopy, params);
|
|
1266
|
+
paramsStore[fileId] = paramsCopy;
|
|
1267
|
+
},
|
|
1268
|
+
|
|
1269
|
+
getParams: function(fileId) {
|
|
1270
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
|
1271
|
+
var paramsCopy = {};
|
|
1272
|
+
|
|
1273
|
+
if (fileId != null && paramsStore[fileId]) {
|
|
1274
|
+
qq.extend(paramsCopy, paramsStore[fileId]);
|
|
1275
|
+
}
|
|
1276
|
+
else {
|
|
1277
|
+
qq.extend(paramsCopy, self._options.request.params);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
return paramsCopy;
|
|
1281
|
+
},
|
|
1282
|
+
|
|
1283
|
+
remove: function(fileId) {
|
|
1284
|
+
return delete paramsStore[fileId];
|
|
1285
|
+
},
|
|
1286
|
+
|
|
1287
|
+
reset: function() {
|
|
1288
|
+
paramsStore = {};
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1291
|
+
},
|
|
1292
|
+
_createEndpointStore: function() {
|
|
1293
|
+
var endpointStore = {},
|
|
1294
|
+
self = this;
|
|
1295
|
+
|
|
1296
|
+
return {
|
|
1297
|
+
setEndpoint: function(endpoint, fileId) {
|
|
1298
|
+
endpointStore[fileId] = endpoint;
|
|
1299
|
+
},
|
|
1300
|
+
|
|
1301
|
+
getEndpoint: function(fileId) {
|
|
1302
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
|
1303
|
+
if (fileId != null && endpointStore[fileId]) {
|
|
1304
|
+
return endpointStore[fileId];
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
return self._options.request.endpoint;
|
|
1308
|
+
},
|
|
1309
|
+
|
|
1310
|
+
remove: function(fileId) {
|
|
1311
|
+
return delete endpointStore[fileId];
|
|
1312
|
+
},
|
|
1313
|
+
|
|
1314
|
+
reset: function() {
|
|
1315
|
+
endpointStore = {};
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
/*globals qq, document*/
|
|
1321
|
+
qq.DragAndDrop = function(o) {
|
|
1322
|
+
"use strict";
|
|
1323
|
+
|
|
1324
|
+
var options, dz, dirPending,
|
|
1325
|
+
droppedFiles = [],
|
|
1326
|
+
droppedEntriesCount = 0,
|
|
1327
|
+
droppedEntriesParsedCount = 0,
|
|
1328
|
+
disposeSupport = new qq.DisposeSupport();
|
|
1329
|
+
|
|
1330
|
+
options = {
|
|
1331
|
+
dropArea: null,
|
|
1332
|
+
extraDropzones: [],
|
|
1333
|
+
hideDropzones: true,
|
|
1334
|
+
multiple: true,
|
|
1335
|
+
classes: {
|
|
1336
|
+
dropActive: null
|
|
1337
|
+
},
|
|
1338
|
+
callbacks: {
|
|
1339
|
+
dropProcessing: function(isProcessing, files) {},
|
|
1340
|
+
error: function(code, filename) {},
|
|
1341
|
+
log: function(message, level) {}
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
qq.extend(options, o);
|
|
1346
|
+
|
|
1347
|
+
function maybeUploadDroppedFiles() {
|
|
1348
|
+
if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
|
|
1349
|
+
options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
|
|
1350
|
+
dz.dropDisabled(false);
|
|
1351
|
+
options.callbacks.dropProcessing(false, droppedFiles);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
function addDroppedFile(file) {
|
|
1355
|
+
droppedFiles.push(file);
|
|
1356
|
+
droppedEntriesParsedCount+=1;
|
|
1357
|
+
maybeUploadDroppedFiles();
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function traverseFileTree(entry) {
|
|
1361
|
+
var dirReader, i;
|
|
1362
|
+
|
|
1363
|
+
droppedEntriesCount+=1;
|
|
1364
|
+
|
|
1365
|
+
if (entry.isFile) {
|
|
1366
|
+
entry.file(function(file) {
|
|
1367
|
+
addDroppedFile(file);
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
else if (entry.isDirectory) {
|
|
1371
|
+
dirPending = true;
|
|
1372
|
+
dirReader = entry.createReader();
|
|
1373
|
+
dirReader.readEntries(function(entries) {
|
|
1374
|
+
droppedEntriesParsedCount+=1;
|
|
1375
|
+
for (i = 0; i < entries.length; i+=1) {
|
|
1376
|
+
traverseFileTree(entries[i]);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
dirPending = false;
|
|
1380
|
+
|
|
1381
|
+
if (!entries.length) {
|
|
1382
|
+
maybeUploadDroppedFiles();
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function handleDataTransfer(dataTransfer) {
|
|
1389
|
+
var i, items, entry;
|
|
1390
|
+
|
|
1391
|
+
options.callbacks.dropProcessing(true);
|
|
1392
|
+
dz.dropDisabled(true);
|
|
1393
|
+
|
|
1394
|
+
if (dataTransfer.files.length > 1 && !options.multiple) {
|
|
1395
|
+
options.callbacks.dropProcessing(false);
|
|
1396
|
+
options.callbacks.error('tooManyFilesError', "");
|
|
1397
|
+
dz.dropDisabled(false);
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
droppedFiles = [];
|
|
1401
|
+
droppedEntriesCount = 0;
|
|
1402
|
+
droppedEntriesParsedCount = 0;
|
|
1403
|
+
|
|
1404
|
+
if (qq.isFolderDropSupported(dataTransfer)) {
|
|
1405
|
+
items = dataTransfer.items;
|
|
1406
|
+
|
|
1407
|
+
for (i = 0; i < items.length; i+=1) {
|
|
1408
|
+
entry = items[i].webkitGetAsEntry();
|
|
1409
|
+
if (entry) {
|
|
1410
|
+
//due to a bug in Chrome's File System API impl - #149735
|
|
1411
|
+
if (entry.isFile) {
|
|
1412
|
+
droppedFiles.push(items[i].getAsFile());
|
|
1413
|
+
if (i === items.length-1) {
|
|
1414
|
+
maybeUploadDroppedFiles();
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
else {
|
|
1419
|
+
traverseFileTree(entry);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
else {
|
|
1425
|
+
options.callbacks.dropProcessing(false, dataTransfer.files);
|
|
1426
|
+
dz.dropDisabled(false);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function setupDropzone(dropArea){
|
|
1432
|
+
dz = new qq.UploadDropZone({
|
|
1433
|
+
element: dropArea,
|
|
1434
|
+
onEnter: function(e){
|
|
1435
|
+
qq(dropArea).addClass(options.classes.dropActive);
|
|
1436
|
+
e.stopPropagation();
|
|
1437
|
+
},
|
|
1438
|
+
onLeaveNotDescendants: function(e){
|
|
1439
|
+
qq(dropArea).removeClass(options.classes.dropActive);
|
|
1440
|
+
},
|
|
1441
|
+
onDrop: function(e){
|
|
1442
|
+
if (options.hideDropzones) {
|
|
1443
|
+
qq(dropArea).hide();
|
|
1444
|
+
}
|
|
1445
|
+
qq(dropArea).removeClass(options.classes.dropActive);
|
|
1446
|
+
|
|
1447
|
+
handleDataTransfer(e.dataTransfer);
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
disposeSupport.addDisposer(function() {
|
|
1452
|
+
dz.dispose();
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
if (options.hideDropzones) {
|
|
1456
|
+
qq(dropArea).hide();
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
function isFileDrag(dragEvent) {
|
|
1461
|
+
var fileDrag;
|
|
1462
|
+
|
|
1463
|
+
qq.each(dragEvent.dataTransfer.types, function(key, val) {
|
|
1464
|
+
if (val === 'Files') {
|
|
1465
|
+
fileDrag = true;
|
|
1466
|
+
return false;
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
return fileDrag;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function setupDragDrop(){
|
|
1474
|
+
if (options.dropArea) {
|
|
1475
|
+
options.extraDropzones.push(options.dropArea);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
var i, dropzones = options.extraDropzones;
|
|
1479
|
+
|
|
1480
|
+
for (i=0; i < dropzones.length; i+=1){
|
|
1481
|
+
setupDropzone(dropzones[i]);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// IE <= 9 does not support the File API used for drag+drop uploads
|
|
1485
|
+
if (options.dropArea && (!qq.ie() || qq.ie10())) {
|
|
1486
|
+
disposeSupport.attach(document, 'dragenter', function(e) {
|
|
1487
|
+
if (!dz.dropDisabled() && isFileDrag(e)) {
|
|
1488
|
+
if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
options.dropArea.style.display = 'block';
|
|
1493
|
+
for (i=0; i < dropzones.length; i+=1) {
|
|
1494
|
+
dropzones[i].style.display = 'block';
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
disposeSupport.attach(document, 'dragleave', function(e){
|
|
1500
|
+
if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
|
|
1501
|
+
for (i=0; i < dropzones.length; i+=1) {
|
|
1502
|
+
qq(dropzones[i]).hide();
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
disposeSupport.attach(document, 'drop', function(e){
|
|
1507
|
+
if (options.hideDropzones) {
|
|
1508
|
+
for (i=0; i < dropzones.length; i+=1) {
|
|
1509
|
+
qq(dropzones[i]).hide();
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
e.preventDefault();
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
return {
|
|
1517
|
+
setup: function() {
|
|
1518
|
+
setupDragDrop();
|
|
1519
|
+
},
|
|
1520
|
+
|
|
1521
|
+
setupExtraDropzone: function(element) {
|
|
1522
|
+
options.extraDropzones.push(element);
|
|
1523
|
+
setupDropzone(element);
|
|
1524
|
+
},
|
|
1525
|
+
|
|
1526
|
+
removeExtraDropzone: function(element) {
|
|
1527
|
+
var i, dzs = options.extraDropzones;
|
|
1528
|
+
for(i in dzs) {
|
|
1529
|
+
if (dzs[i] === element) {
|
|
1530
|
+
return dzs.splice(i, 1);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
},
|
|
1534
|
+
|
|
1535
|
+
dispose: function() {
|
|
1536
|
+
disposeSupport.dispose();
|
|
1537
|
+
dz.dispose();
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
};
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
qq.UploadDropZone = function(o){
|
|
1544
|
+
"use strict";
|
|
1545
|
+
|
|
1546
|
+
var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
|
|
1547
|
+
|
|
1548
|
+
options = {
|
|
1549
|
+
element: null,
|
|
1550
|
+
onEnter: function(e){},
|
|
1551
|
+
onLeave: function(e){},
|
|
1552
|
+
// is not fired when leaving element by hovering descendants
|
|
1553
|
+
onLeaveNotDescendants: function(e){},
|
|
1554
|
+
onDrop: function(e){}
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
qq.extend(options, o);
|
|
1558
|
+
element = options.element;
|
|
1559
|
+
|
|
1560
|
+
function dragover_should_be_canceled(){
|
|
1561
|
+
return qq.safari() || (qq.firefox() && qq.windows());
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
function disableDropOutside(e){
|
|
1565
|
+
// run only once for all instances
|
|
1566
|
+
if (!dropOutsideDisabled ){
|
|
1567
|
+
|
|
1568
|
+
// for these cases we need to catch onDrop to reset dropArea
|
|
1569
|
+
if (dragover_should_be_canceled){
|
|
1570
|
+
disposeSupport.attach(document, 'dragover', function(e){
|
|
1571
|
+
e.preventDefault();
|
|
1572
|
+
});
|
|
1573
|
+
} else {
|
|
1574
|
+
disposeSupport.attach(document, 'dragover', function(e){
|
|
1575
|
+
if (e.dataTransfer){
|
|
1576
|
+
e.dataTransfer.dropEffect = 'none';
|
|
1577
|
+
e.preventDefault();
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
dropOutsideDisabled = true;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
function isValidFileDrag(e){
|
|
1587
|
+
// e.dataTransfer currently causing IE errors
|
|
1588
|
+
// IE9 does NOT support file API, so drag-and-drop is not possible
|
|
1589
|
+
if (qq.ie() && !qq.ie10()) {
|
|
1590
|
+
return false;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
var effectTest, dt = e.dataTransfer,
|
|
1594
|
+
// do not check dt.types.contains in webkit, because it crashes safari 4
|
|
1595
|
+
isSafari = qq.safari();
|
|
1596
|
+
|
|
1597
|
+
// dt.effectAllowed is none in Safari 5
|
|
1598
|
+
// dt.types.contains check is for firefox
|
|
1599
|
+
effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
|
|
1600
|
+
return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function isOrSetDropDisabled(isDisabled) {
|
|
1604
|
+
if (isDisabled !== undefined) {
|
|
1605
|
+
preventDrop = isDisabled;
|
|
1606
|
+
}
|
|
1607
|
+
return preventDrop;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
function attachEvents(){
|
|
1611
|
+
disposeSupport.attach(element, 'dragover', function(e){
|
|
1612
|
+
if (!isValidFileDrag(e)) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
|
|
1617
|
+
if (effect === 'move' || effect === 'linkMove'){
|
|
1618
|
+
e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
|
|
1619
|
+
} else {
|
|
1620
|
+
e.dataTransfer.dropEffect = 'copy'; // for Chrome
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
e.stopPropagation();
|
|
1624
|
+
e.preventDefault();
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
disposeSupport.attach(element, 'dragenter', function(e){
|
|
1628
|
+
if (!isOrSetDropDisabled()) {
|
|
1629
|
+
if (!isValidFileDrag(e)) {
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
options.onEnter(e);
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
disposeSupport.attach(element, 'dragleave', function(e){
|
|
1637
|
+
if (!isValidFileDrag(e)) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
options.onLeave(e);
|
|
1642
|
+
|
|
1643
|
+
var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
|
|
1644
|
+
// do not fire when moving a mouse over a descendant
|
|
1645
|
+
if (qq(this).contains(relatedTarget)) {
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
options.onLeaveNotDescendants(e);
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
disposeSupport.attach(element, 'drop', function(e){
|
|
1653
|
+
if (!isOrSetDropDisabled()) {
|
|
1654
|
+
if (!isValidFileDrag(e)) {
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
e.preventDefault();
|
|
1659
|
+
options.onDrop(e);
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
disableDropOutside();
|
|
1665
|
+
attachEvents();
|
|
1666
|
+
|
|
1667
|
+
return {
|
|
1668
|
+
dropDisabled: function(isDisabled) {
|
|
1669
|
+
return isOrSetDropDisabled(isDisabled);
|
|
1670
|
+
},
|
|
1671
|
+
|
|
1672
|
+
dispose: function() {
|
|
1673
|
+
disposeSupport.dispose();
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
};
|
|
1677
|
+
/**
|
|
1678
|
+
* Class that creates upload widget with drag-and-drop and file list
|
|
1679
|
+
* @inherits qq.FineUploaderBasic
|
|
1680
|
+
*/
|
|
1681
|
+
qq.FineUploader = function(o){
|
|
1682
|
+
// call parent constructor
|
|
1683
|
+
qq.FineUploaderBasic.apply(this, arguments);
|
|
1684
|
+
|
|
1685
|
+
// additional options
|
|
1686
|
+
qq.extend(this._options, {
|
|
1687
|
+
element: null,
|
|
1688
|
+
listElement: null,
|
|
1689
|
+
dragAndDrop: {
|
|
1690
|
+
extraDropzones: [],
|
|
1691
|
+
hideDropzones: true,
|
|
1692
|
+
disableDefaultDropzone: false
|
|
1693
|
+
},
|
|
1694
|
+
text: {
|
|
1695
|
+
uploadButton: 'Upload a file',
|
|
1696
|
+
cancelButton: 'Cancel',
|
|
1697
|
+
retryButton: 'Retry',
|
|
1698
|
+
failUpload: 'Upload failed',
|
|
1699
|
+
dragZone: 'Drop files here to upload',
|
|
1700
|
+
dropProcessing: 'Processing dropped files...',
|
|
1701
|
+
formatProgress: "{percent}% of {total_size}",
|
|
1702
|
+
waitingForResponse: "Processing..."
|
|
1703
|
+
},
|
|
1704
|
+
template: '<div class="qq-uploader">' +
|
|
1705
|
+
((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '<div class="qq-upload-drop-area"><span>{dragZoneText}</span></div>' : '') +
|
|
1706
|
+
(!this._options.button ? '<div class="qq-upload-button"><div>{uploadButtonText}</div></div>' : '') +
|
|
1707
|
+
'<span class="qq-drop-processing"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></span>' +
|
|
1708
|
+
(!this._options.listElement ? '<ul class="qq-upload-list"></ul>' : '') +
|
|
1709
|
+
'</div>',
|
|
1710
|
+
|
|
1711
|
+
// template for one item in file list
|
|
1712
|
+
fileTemplate: '<li>' +
|
|
1713
|
+
'<div class="qq-progress-bar"></div>' +
|
|
1714
|
+
'<span class="qq-upload-spinner"></span>' +
|
|
1715
|
+
'<span class="qq-upload-finished"></span>' +
|
|
1716
|
+
'<span class="qq-upload-file"></span>' +
|
|
1717
|
+
'<span class="qq-upload-size"></span>' +
|
|
1718
|
+
'<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
|
|
1719
|
+
'<a class="qq-upload-retry" href="#">{retryButtonText}</a>' +
|
|
1720
|
+
'<span class="qq-upload-status-text">{statusText}</span>' +
|
|
1721
|
+
'</li>',
|
|
1722
|
+
classes: {
|
|
1723
|
+
button: 'qq-upload-button',
|
|
1724
|
+
drop: 'qq-upload-drop-area',
|
|
1725
|
+
dropActive: 'qq-upload-drop-area-active',
|
|
1726
|
+
dropDisabled: 'qq-upload-drop-area-disabled',
|
|
1727
|
+
list: 'qq-upload-list',
|
|
1728
|
+
progressBar: 'qq-progress-bar',
|
|
1729
|
+
file: 'qq-upload-file',
|
|
1730
|
+
spinner: 'qq-upload-spinner',
|
|
1731
|
+
finished: 'qq-upload-finished',
|
|
1732
|
+
retrying: 'qq-upload-retrying',
|
|
1733
|
+
retryable: 'qq-upload-retryable',
|
|
1734
|
+
size: 'qq-upload-size',
|
|
1735
|
+
cancel: 'qq-upload-cancel',
|
|
1736
|
+
retry: 'qq-upload-retry',
|
|
1737
|
+
statusText: 'qq-upload-status-text',
|
|
1738
|
+
|
|
1739
|
+
success: 'qq-upload-success',
|
|
1740
|
+
fail: 'qq-upload-fail',
|
|
1741
|
+
|
|
1742
|
+
successIcon: null,
|
|
1743
|
+
failIcon: null,
|
|
1744
|
+
|
|
1745
|
+
dropProcessing: 'qq-drop-processing',
|
|
1746
|
+
dropProcessingSpinner: 'qq-drop-processing-spinner'
|
|
1747
|
+
},
|
|
1748
|
+
failedUploadTextDisplay: {
|
|
1749
|
+
mode: 'default', //default, custom, or none
|
|
1750
|
+
maxChars: 50,
|
|
1751
|
+
responseProperty: 'error',
|
|
1752
|
+
enableTooltip: true
|
|
1753
|
+
},
|
|
1754
|
+
messages: {
|
|
1755
|
+
tooManyFilesError: "You may only drop one file"
|
|
1756
|
+
},
|
|
1757
|
+
retry: {
|
|
1758
|
+
showAutoRetryNote: true,
|
|
1759
|
+
autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
|
|
1760
|
+
showButton: false
|
|
1761
|
+
},
|
|
1762
|
+
showMessage: function(message){
|
|
1763
|
+
setTimeout(function() {
|
|
1764
|
+
alert(message);
|
|
1765
|
+
}, 0);
|
|
1766
|
+
}
|
|
1767
|
+
}, true);
|
|
1768
|
+
|
|
1769
|
+
// overwrite options with user supplied
|
|
1770
|
+
qq.extend(this._options, o, true);
|
|
1771
|
+
this._wrapCallbacks();
|
|
1772
|
+
|
|
1773
|
+
// overwrite the upload button text if any
|
|
1774
|
+
// same for the Cancel button and Fail message text
|
|
1775
|
+
this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
|
|
1776
|
+
this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
|
|
1777
|
+
this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
|
|
1778
|
+
this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
|
|
1779
|
+
this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
|
|
1780
|
+
this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
|
|
1781
|
+
|
|
1782
|
+
this._element = this._options.element;
|
|
1783
|
+
this._element.innerHTML = this._options.template;
|
|
1784
|
+
this._listElement = this._options.listElement || this._find(this._element, 'list');
|
|
1785
|
+
|
|
1786
|
+
this._classes = this._options.classes;
|
|
1787
|
+
|
|
1788
|
+
if (!this._button) {
|
|
1789
|
+
this._button = this._createUploadButton(this._find(this._element, 'button'));
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
this._bindCancelAndRetryEvents();
|
|
1793
|
+
|
|
1794
|
+
this._dnd = this._setupDragAndDrop();
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
// inherit from Basic Uploader
|
|
1798
|
+
qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
|
|
1799
|
+
|
|
1800
|
+
qq.extend(qq.FineUploader.prototype, {
|
|
1801
|
+
clearStoredFiles: function() {
|
|
1802
|
+
qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
|
|
1803
|
+
this._listElement.innerHTML = "";
|
|
1804
|
+
},
|
|
1805
|
+
addExtraDropzone: function(element){
|
|
1806
|
+
this._dnd.setupExtraDropzone(element);
|
|
1807
|
+
},
|
|
1808
|
+
removeExtraDropzone: function(element){
|
|
1809
|
+
return this._dnd.removeExtraDropzone(element);
|
|
1810
|
+
},
|
|
1811
|
+
getItemByFileId: function(id){
|
|
1812
|
+
var item = this._listElement.firstChild;
|
|
1813
|
+
|
|
1814
|
+
// there can't be txt nodes in dynamically created list
|
|
1815
|
+
// and we can use nextSibling
|
|
1816
|
+
while (item){
|
|
1817
|
+
if (item.qqFileId == id) return item;
|
|
1818
|
+
item = item.nextSibling;
|
|
1819
|
+
}
|
|
1820
|
+
},
|
|
1821
|
+
cancel: function(fileId) {
|
|
1822
|
+
qq.FineUploaderBasic.prototype.cancel.apply(this, arguments);
|
|
1823
|
+
var item = this.getItemByFileId(fileId);
|
|
1824
|
+
qq(item).remove();
|
|
1825
|
+
},
|
|
1826
|
+
reset: function() {
|
|
1827
|
+
qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
|
|
1828
|
+
this._element.innerHTML = this._options.template;
|
|
1829
|
+
this._listElement = this._options.listElement || this._find(this._element, 'list');
|
|
1830
|
+
if (!this._options.button) {
|
|
1831
|
+
this._button = this._createUploadButton(this._find(this._element, 'button'));
|
|
1832
|
+
}
|
|
1833
|
+
this._bindCancelAndRetryEvents();
|
|
1834
|
+
this._dnd.dispose();
|
|
1835
|
+
this._dnd = this._setupDragAndDrop();
|
|
1836
|
+
},
|
|
1837
|
+
_setupDragAndDrop: function() {
|
|
1838
|
+
var self = this,
|
|
1839
|
+
dropProcessingEl = this._find(this._element, 'dropProcessing'),
|
|
1840
|
+
dnd, preventSelectFiles, defaultDropAreaEl;
|
|
1841
|
+
|
|
1842
|
+
preventSelectFiles = function(event) {
|
|
1843
|
+
event.preventDefault();
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
if (!this._options.dragAndDrop.disableDefaultDropzone) {
|
|
1847
|
+
defaultDropAreaEl = this._find(this._options.element, 'drop');
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
dnd = new qq.DragAndDrop({
|
|
1851
|
+
dropArea: defaultDropAreaEl,
|
|
1852
|
+
extraDropzones: this._options.dragAndDrop.extraDropzones,
|
|
1853
|
+
hideDropzones: this._options.dragAndDrop.hideDropzones,
|
|
1854
|
+
multiple: this._options.multiple,
|
|
1855
|
+
classes: {
|
|
1856
|
+
dropActive: this._options.classes.dropActive
|
|
1857
|
+
},
|
|
1858
|
+
callbacks: {
|
|
1859
|
+
dropProcessing: function(isProcessing, files) {
|
|
1860
|
+
var input = self._button.getInput();
|
|
1861
|
+
|
|
1862
|
+
if (isProcessing) {
|
|
1863
|
+
qq(dropProcessingEl).css({display: 'block'});
|
|
1864
|
+
qq(input).attach('click', preventSelectFiles);
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
qq(dropProcessingEl).hide();
|
|
1868
|
+
qq(input).detach('click', preventSelectFiles);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (files) {
|
|
1872
|
+
self.addFiles(files);
|
|
1873
|
+
}
|
|
1874
|
+
},
|
|
1875
|
+
error: function(code, filename) {
|
|
1876
|
+
self._error(code, filename);
|
|
1877
|
+
},
|
|
1878
|
+
log: function(message, level) {
|
|
1879
|
+
self.log(message, level);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
|
|
1884
|
+
dnd.setup();
|
|
1885
|
+
|
|
1886
|
+
return dnd;
|
|
1887
|
+
},
|
|
1888
|
+
_leaving_document_out: function(e){
|
|
1889
|
+
return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
|
|
1890
|
+
|| (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
|
|
1891
|
+
},
|
|
1892
|
+
_storeFileForLater: function(id) {
|
|
1893
|
+
qq.FineUploaderBasic.prototype._storeFileForLater.apply(this, arguments);
|
|
1894
|
+
var item = this.getItemByFileId(id);
|
|
1895
|
+
qq(this._find(item, 'spinner')).hide();
|
|
1896
|
+
},
|
|
1897
|
+
/**
|
|
1898
|
+
* Gets one of the elements listed in this._options.classes
|
|
1899
|
+
**/
|
|
1900
|
+
_find: function(parent, type){
|
|
1901
|
+
var element = qq(parent).getByClass(this._options.classes[type])[0];
|
|
1902
|
+
if (!element){
|
|
1903
|
+
throw new Error('element not found ' + type);
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
return element;
|
|
1907
|
+
},
|
|
1908
|
+
_onSubmit: function(id, fileName){
|
|
1909
|
+
qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
|
|
1910
|
+
this._addToList(id, fileName);
|
|
1911
|
+
},
|
|
1912
|
+
// Update the progress bar & percentage as the file is uploaded
|
|
1913
|
+
_onProgress: function(id, fileName, loaded, total){
|
|
1914
|
+
qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
|
|
1915
|
+
|
|
1916
|
+
var item, progressBar, text, percent, cancelLink, size;
|
|
1917
|
+
|
|
1918
|
+
item = this.getItemByFileId(id);
|
|
1919
|
+
progressBar = this._find(item, 'progressBar');
|
|
1920
|
+
percent = Math.round(loaded / total * 100);
|
|
1921
|
+
|
|
1922
|
+
if (loaded === total) {
|
|
1923
|
+
cancelLink = this._find(item, 'cancel');
|
|
1924
|
+
qq(cancelLink).hide();
|
|
1925
|
+
|
|
1926
|
+
qq(progressBar).hide();
|
|
1927
|
+
qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
|
|
1928
|
+
|
|
1929
|
+
// If last byte was sent, just display final size
|
|
1930
|
+
text = this._formatSize(total);
|
|
1931
|
+
}
|
|
1932
|
+
else {
|
|
1933
|
+
// If still uploading, display percentage
|
|
1934
|
+
text = this._formatProgress(loaded, total);
|
|
1935
|
+
|
|
1936
|
+
qq(progressBar).css({display: 'block'});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// Update progress bar element
|
|
1940
|
+
qq(progressBar).css({width: percent + '%'});
|
|
1941
|
+
|
|
1942
|
+
size = this._find(item, 'size');
|
|
1943
|
+
qq(size).css({display: 'inline'});
|
|
1944
|
+
qq(size).setText(text);
|
|
1945
|
+
},
|
|
1946
|
+
_onComplete: function(id, fileName, result, xhr){
|
|
1947
|
+
qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
|
|
1948
|
+
|
|
1949
|
+
var item = this.getItemByFileId(id);
|
|
1950
|
+
|
|
1951
|
+
qq(this._find(item, 'statusText')).clearText();
|
|
1952
|
+
|
|
1953
|
+
qq(item).removeClass(this._classes.retrying);
|
|
1954
|
+
qq(this._find(item, 'progressBar')).hide();
|
|
1955
|
+
|
|
1956
|
+
if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
|
|
1957
|
+
qq(this._find(item, 'cancel')).hide();
|
|
1958
|
+
}
|
|
1959
|
+
qq(this._find(item, 'spinner')).hide();
|
|
1960
|
+
|
|
1961
|
+
if (result.success){
|
|
1962
|
+
qq(item).addClass(this._classes.success);
|
|
1963
|
+
if (this._classes.successIcon) {
|
|
1964
|
+
this._find(item, 'finished').style.display = "inline-block";
|
|
1965
|
+
qq(item).addClass(this._classes.successIcon);
|
|
1966
|
+
}
|
|
1967
|
+
} else {
|
|
1968
|
+
qq(item).addClass(this._classes.fail);
|
|
1969
|
+
if (this._classes.failIcon) {
|
|
1970
|
+
this._find(item, 'finished').style.display = "inline-block";
|
|
1971
|
+
qq(item).addClass(this._classes.failIcon);
|
|
1972
|
+
}
|
|
1973
|
+
if (this._options.retry.showButton && !this._preventRetries[id]) {
|
|
1974
|
+
qq(item).addClass(this._classes.retryable);
|
|
1975
|
+
}
|
|
1976
|
+
this._controlFailureTextDisplay(item, result);
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1979
|
+
_onUpload: function(id, fileName){
|
|
1980
|
+
qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
|
|
1981
|
+
|
|
1982
|
+
var item = this.getItemByFileId(id);
|
|
1983
|
+
this._showSpinner(item);
|
|
1984
|
+
},
|
|
1985
|
+
_onBeforeAutoRetry: function(id) {
|
|
1986
|
+
var item, progressBar, cancelLink, failTextEl, retryNumForDisplay, maxAuto, retryNote;
|
|
1987
|
+
|
|
1988
|
+
qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
|
|
1989
|
+
|
|
1990
|
+
item = this.getItemByFileId(id);
|
|
1991
|
+
progressBar = this._find(item, 'progressBar');
|
|
1992
|
+
|
|
1993
|
+
this._showCancelLink(item);
|
|
1994
|
+
progressBar.style.width = 0;
|
|
1995
|
+
qq(progressBar).hide();
|
|
1996
|
+
|
|
1997
|
+
if (this._options.retry.showAutoRetryNote) {
|
|
1998
|
+
failTextEl = this._find(item, 'statusText');
|
|
1999
|
+
retryNumForDisplay = this._autoRetries[id] + 1;
|
|
2000
|
+
maxAuto = this._options.retry.maxAutoAttempts;
|
|
2001
|
+
|
|
2002
|
+
retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
|
|
2003
|
+
retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
|
|
2004
|
+
|
|
2005
|
+
qq(failTextEl).setText(retryNote);
|
|
2006
|
+
if (retryNumForDisplay === 1) {
|
|
2007
|
+
qq(item).addClass(this._classes.retrying);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
},
|
|
2011
|
+
//return false if we should not attempt the requested retry
|
|
2012
|
+
_onBeforeManualRetry: function(id) {
|
|
2013
|
+
if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
|
|
2014
|
+
var item = this.getItemByFileId(id);
|
|
2015
|
+
this._find(item, 'progressBar').style.width = 0;
|
|
2016
|
+
qq(item).removeClass(this._classes.fail);
|
|
2017
|
+
qq(this._find(item, 'statusText')).clearText();
|
|
2018
|
+
this._showSpinner(item);
|
|
2019
|
+
this._showCancelLink(item);
|
|
2020
|
+
return true;
|
|
2021
|
+
}
|
|
2022
|
+
return false;
|
|
2023
|
+
},
|
|
2024
|
+
_addToList: function(id, fileName){
|
|
2025
|
+
var item = qq.toElement(this._options.fileTemplate);
|
|
2026
|
+
if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
|
|
2027
|
+
var cancelLink = this._find(item, 'cancel');
|
|
2028
|
+
qq(cancelLink).remove();
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
item.qqFileId = id;
|
|
2032
|
+
|
|
2033
|
+
var fileElement = this._find(item, 'file');
|
|
2034
|
+
qq(fileElement).setText(this._options.formatFileName(fileName));
|
|
2035
|
+
qq(this._find(item, 'size')).hide();
|
|
2036
|
+
if (!this._options.multiple) this._clearList();
|
|
2037
|
+
this._listElement.appendChild(item);
|
|
2038
|
+
},
|
|
2039
|
+
_clearList: function(){
|
|
2040
|
+
this._listElement.innerHTML = '';
|
|
2041
|
+
this.clearStoredFiles();
|
|
2042
|
+
},
|
|
2043
|
+
/**
|
|
2044
|
+
* delegate click event for cancel & retry links
|
|
2045
|
+
**/
|
|
2046
|
+
_bindCancelAndRetryEvents: function(){
|
|
2047
|
+
var self = this,
|
|
2048
|
+
list = this._listElement;
|
|
2049
|
+
|
|
2050
|
+
this._disposeSupport.attach(list, 'click', function(e){
|
|
2051
|
+
e = e || window.event;
|
|
2052
|
+
var target = e.target || e.srcElement;
|
|
2053
|
+
|
|
2054
|
+
if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry)){
|
|
2055
|
+
qq.preventDefault(e);
|
|
2056
|
+
|
|
2057
|
+
var item = target.parentNode;
|
|
2058
|
+
while(item.qqFileId == undefined) {
|
|
2059
|
+
item = target = target.parentNode;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
if (qq(target).hasClass(self._classes.cancel)) {
|
|
2063
|
+
self.cancel(item.qqFileId);
|
|
2064
|
+
}
|
|
2065
|
+
else {
|
|
2066
|
+
qq(item).removeClass(self._classes.retryable);
|
|
2067
|
+
self.retry(item.qqFileId);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
});
|
|
2071
|
+
},
|
|
2072
|
+
_formatProgress: function (uploadedSize, totalSize) {
|
|
2073
|
+
var message = this._options.text.formatProgress;
|
|
2074
|
+
function r(name, replacement) { message = message.replace(name, replacement); }
|
|
2075
|
+
|
|
2076
|
+
r('{percent}', Math.round(uploadedSize / totalSize * 100));
|
|
2077
|
+
r('{total_size}', this._formatSize(totalSize));
|
|
2078
|
+
return message;
|
|
2079
|
+
},
|
|
2080
|
+
_controlFailureTextDisplay: function(item, response) {
|
|
2081
|
+
var mode, maxChars, responseProperty, failureReason, shortFailureReason;
|
|
2082
|
+
|
|
2083
|
+
mode = this._options.failedUploadTextDisplay.mode;
|
|
2084
|
+
maxChars = this._options.failedUploadTextDisplay.maxChars;
|
|
2085
|
+
responseProperty = this._options.failedUploadTextDisplay.responseProperty;
|
|
2086
|
+
|
|
2087
|
+
if (mode === 'custom') {
|
|
2088
|
+
failureReason = response[responseProperty];
|
|
2089
|
+
if (failureReason) {
|
|
2090
|
+
if (failureReason.length > maxChars) {
|
|
2091
|
+
shortFailureReason = failureReason.substring(0, maxChars) + '...';
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
else {
|
|
2095
|
+
failureReason = this._options.text.failUpload;
|
|
2096
|
+
this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
|
|
2100
|
+
|
|
2101
|
+
if (this._options.failedUploadTextDisplay.enableTooltip) {
|
|
2102
|
+
this._showTooltip(item, failureReason);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
else if (mode === 'default') {
|
|
2106
|
+
qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
|
|
2107
|
+
}
|
|
2108
|
+
else if (mode !== 'none') {
|
|
2109
|
+
this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
2112
|
+
//TODO turn this into a real tooltip, with click trigger (so it is usable on mobile devices). See case #355 for details.
|
|
2113
|
+
_showTooltip: function(item, text) {
|
|
2114
|
+
item.title = text;
|
|
2115
|
+
},
|
|
2116
|
+
_showSpinner: function(item) {
|
|
2117
|
+
var spinnerEl = this._find(item, 'spinner');
|
|
2118
|
+
spinnerEl.style.display = "inline-block";
|
|
2119
|
+
},
|
|
2120
|
+
_showCancelLink: function(item) {
|
|
2121
|
+
if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
|
|
2122
|
+
var cancelLink = this._find(item, 'cancel');
|
|
2123
|
+
cancelLink.style.display = 'inline';
|
|
2124
|
+
}
|
|
2125
|
+
},
|
|
2126
|
+
_error: function(code, fileName){
|
|
2127
|
+
var message = qq.FineUploaderBasic.prototype._error.apply(this, arguments);
|
|
2128
|
+
this._options.showMessage(message);
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
/**
|
|
2132
|
+
* Class for uploading files, uploading itself is handled by child classes
|
|
2133
|
+
*/
|
|
2134
|
+
/*globals qq*/
|
|
2135
|
+
qq.UploadHandler = function(o) {
|
|
2136
|
+
"use strict";
|
|
2137
|
+
|
|
2138
|
+
var queue = [],
|
|
2139
|
+
options, log, dequeue, handlerImpl;
|
|
2140
|
+
|
|
2141
|
+
// Default options, can be overridden by the user
|
|
2142
|
+
options = {
|
|
2143
|
+
debug: false,
|
|
2144
|
+
forceMultipart: true,
|
|
2145
|
+
paramsInBody: false,
|
|
2146
|
+
paramsStore: {},
|
|
2147
|
+
endpointStore: {},
|
|
2148
|
+
maxConnections: 3, // maximum number of concurrent uploads
|
|
2149
|
+
uuidParamName: 'qquuid',
|
|
2150
|
+
totalFileSizeParamName: 'qqtotalfilesize',
|
|
2151
|
+
chunking: {
|
|
2152
|
+
enabled: false,
|
|
2153
|
+
partSize: 2000000, //bytes
|
|
2154
|
+
paramNames: {
|
|
2155
|
+
partIndex: 'qqpartindex',
|
|
2156
|
+
partByteOffset: 'qqpartbyteoffset',
|
|
2157
|
+
chunkSize: 'qqchunksize',
|
|
2158
|
+
totalParts: 'qqtotalparts',
|
|
2159
|
+
filename: 'qqfilename'
|
|
2160
|
+
}
|
|
2161
|
+
},
|
|
2162
|
+
resume: {
|
|
2163
|
+
enabled: false,
|
|
2164
|
+
id: null,
|
|
2165
|
+
cookiesExpireIn: 7, //days
|
|
2166
|
+
paramNames: {
|
|
2167
|
+
resuming: "qqresume"
|
|
2168
|
+
}
|
|
2169
|
+
},
|
|
2170
|
+
log: function(str, level) {},
|
|
2171
|
+
onProgress: function(id, fileName, loaded, total){},
|
|
2172
|
+
onComplete: function(id, fileName, response, xhr){},
|
|
2173
|
+
onCancel: function(id, fileName){},
|
|
2174
|
+
onUpload: function(id, fileName){},
|
|
2175
|
+
onUploadChunk: function(id, fileName, chunkData){},
|
|
2176
|
+
onAutoRetry: function(id, fileName, response, xhr){},
|
|
2177
|
+
onResume: function(id, fileName, chunkData){}
|
|
2178
|
+
|
|
2179
|
+
};
|
|
2180
|
+
qq.extend(options, o);
|
|
2181
|
+
|
|
2182
|
+
log = options.log;
|
|
2183
|
+
|
|
2184
|
+
/**
|
|
2185
|
+
* Removes element from queue, starts upload of next
|
|
2186
|
+
*/
|
|
2187
|
+
dequeue = function(id) {
|
|
2188
|
+
var i = qq.indexOf(queue, id),
|
|
2189
|
+
max = options.maxConnections,
|
|
2190
|
+
nextId;
|
|
2191
|
+
|
|
2192
|
+
queue.splice(i, 1);
|
|
2193
|
+
|
|
2194
|
+
if (queue.length >= max && i < max){
|
|
2195
|
+
nextId = queue[max-1];
|
|
2196
|
+
handlerImpl.upload(nextId);
|
|
2197
|
+
}
|
|
2198
|
+
};
|
|
2199
|
+
|
|
2200
|
+
if (qq.isXhrUploadSupported()) {
|
|
2201
|
+
handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
|
|
2202
|
+
}
|
|
2203
|
+
else {
|
|
2204
|
+
handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
|
|
2208
|
+
return {
|
|
2209
|
+
/**
|
|
2210
|
+
* Adds file or file input to the queue
|
|
2211
|
+
* @returns id
|
|
2212
|
+
**/
|
|
2213
|
+
add: function(file){
|
|
2214
|
+
return handlerImpl.add(file);
|
|
2215
|
+
},
|
|
2216
|
+
/**
|
|
2217
|
+
* Sends the file identified by id
|
|
2218
|
+
*/
|
|
2219
|
+
upload: function(id){
|
|
2220
|
+
var len = queue.push(id);
|
|
2221
|
+
|
|
2222
|
+
// if too many active uploads, wait...
|
|
2223
|
+
if (len <= options.maxConnections){
|
|
2224
|
+
return handlerImpl.upload(id);
|
|
2225
|
+
}
|
|
2226
|
+
},
|
|
2227
|
+
retry: function(id) {
|
|
2228
|
+
var i = qq.indexOf(queue, id);
|
|
2229
|
+
if (i >= 0) {
|
|
2230
|
+
return handlerImpl.upload(id, true);
|
|
2231
|
+
}
|
|
2232
|
+
else {
|
|
2233
|
+
return this.upload(id);
|
|
2234
|
+
}
|
|
2235
|
+
},
|
|
2236
|
+
/**
|
|
2237
|
+
* Cancels file upload by id
|
|
2238
|
+
*/
|
|
2239
|
+
cancel: function(id){
|
|
2240
|
+
log('Cancelling ' + id);
|
|
2241
|
+
options.paramsStore.remove(id);
|
|
2242
|
+
handlerImpl.cancel(id);
|
|
2243
|
+
dequeue(id);
|
|
2244
|
+
},
|
|
2245
|
+
/**
|
|
2246
|
+
* Cancels all uploads
|
|
2247
|
+
*/
|
|
2248
|
+
cancelAll: function(){
|
|
2249
|
+
qq.each(queue, function(idx, fileId) {
|
|
2250
|
+
this.cancel(fileId);
|
|
2251
|
+
});
|
|
2252
|
+
|
|
2253
|
+
queue = [];
|
|
2254
|
+
},
|
|
2255
|
+
/**
|
|
2256
|
+
* Returns name of the file identified by id
|
|
2257
|
+
*/
|
|
2258
|
+
getName: function(id){
|
|
2259
|
+
return handlerImpl.getName(id);
|
|
2260
|
+
},
|
|
2261
|
+
/**
|
|
2262
|
+
* Returns size of the file identified by id
|
|
2263
|
+
*/
|
|
2264
|
+
getSize: function(id){
|
|
2265
|
+
if (handlerImpl.getSize) {
|
|
2266
|
+
return handlerImpl.getSize(id);
|
|
2267
|
+
}
|
|
2268
|
+
},
|
|
2269
|
+
getFile: function(id) {
|
|
2270
|
+
if (handlerImpl.getFile) {
|
|
2271
|
+
return handlerImpl.getFile(id);
|
|
2272
|
+
}
|
|
2273
|
+
},
|
|
2274
|
+
/**
|
|
2275
|
+
* Returns id of files being uploaded or
|
|
2276
|
+
* waiting for their turn
|
|
2277
|
+
*/
|
|
2278
|
+
getQueue: function(){
|
|
2279
|
+
return queue;
|
|
2280
|
+
},
|
|
2281
|
+
reset: function() {
|
|
2282
|
+
log('Resetting upload handler');
|
|
2283
|
+
queue = [];
|
|
2284
|
+
handlerImpl.reset();
|
|
2285
|
+
},
|
|
2286
|
+
getUuid: function(id) {
|
|
2287
|
+
return handlerImpl.getUuid(id);
|
|
2288
|
+
},
|
|
2289
|
+
/**
|
|
2290
|
+
* Determine if the file exists.
|
|
2291
|
+
*/
|
|
2292
|
+
isValid: function(id) {
|
|
2293
|
+
return handlerImpl.isValid(id);
|
|
2294
|
+
},
|
|
2295
|
+
getResumableFilesData: function() {
|
|
2296
|
+
if (handlerImpl.getResumableFilesData) {
|
|
2297
|
+
return handlerImpl.getResumableFilesData();
|
|
2298
|
+
}
|
|
2299
|
+
return [];
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
};
|
|
2303
|
+
/*globals qq, document, setTimeout*/
|
|
2304
|
+
/*jslint white: true*/
|
|
2305
|
+
qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
|
|
2306
|
+
"use strict";
|
|
2307
|
+
|
|
2308
|
+
var options = o,
|
|
2309
|
+
inputs = [],
|
|
2310
|
+
uuids = [],
|
|
2311
|
+
detachLoadEvents = {},
|
|
2312
|
+
uploadComplete = uploadCompleteCallback,
|
|
2313
|
+
log = logCallback,
|
|
2314
|
+
api;
|
|
2315
|
+
|
|
2316
|
+
function attachLoadEvent(iframe, callback) {
|
|
2317
|
+
/*jslint eqeq: true*/
|
|
2318
|
+
|
|
2319
|
+
detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
|
|
2320
|
+
log('Received response for ' + iframe.id);
|
|
2321
|
+
|
|
2322
|
+
// when we remove iframe from dom
|
|
2323
|
+
// the request stops, but in IE load
|
|
2324
|
+
// event fires
|
|
2325
|
+
if (!iframe.parentNode){
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
try {
|
|
2330
|
+
// fixing Opera 10.53
|
|
2331
|
+
if (iframe.contentDocument &&
|
|
2332
|
+
iframe.contentDocument.body &&
|
|
2333
|
+
iframe.contentDocument.body.innerHTML == "false"){
|
|
2334
|
+
// In Opera event is fired second time
|
|
2335
|
+
// when body.innerHTML changed from false
|
|
2336
|
+
// to server response approx. after 1 sec
|
|
2337
|
+
// when we upload file with iframe
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
catch (error) {
|
|
2342
|
+
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
|
2343
|
+
log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
callback();
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
/**
|
|
2351
|
+
* Returns json object received by iframe from server.
|
|
2352
|
+
*/
|
|
2353
|
+
function getIframeContentJson(iframe) {
|
|
2354
|
+
/*jshint evil: true*/
|
|
2355
|
+
|
|
2356
|
+
var response;
|
|
2357
|
+
|
|
2358
|
+
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
|
2359
|
+
try {
|
|
2360
|
+
// iframe.contentWindow.document - for IE<7
|
|
2361
|
+
var doc = iframe.contentDocument || iframe.contentWindow.document,
|
|
2362
|
+
innerHTML = doc.body.innerHTML;
|
|
2363
|
+
|
|
2364
|
+
log("converting iframe's innerHTML to JSON");
|
|
2365
|
+
log("innerHTML = " + innerHTML);
|
|
2366
|
+
//plain text response may be wrapped in <pre> tag
|
|
2367
|
+
if (innerHTML && innerHTML.match(/^<pre/i)) {
|
|
2368
|
+
innerHTML = doc.body.firstChild.firstChild.nodeValue;
|
|
2369
|
+
}
|
|
2370
|
+
response = eval("(" + innerHTML + ")");
|
|
2371
|
+
} catch(error){
|
|
2372
|
+
log('Error when attempting to parse form upload response (' + error + ")", 'error');
|
|
2373
|
+
response = {success: false};
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
return response;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
/**
|
|
2380
|
+
* Creates iframe with unique name
|
|
2381
|
+
*/
|
|
2382
|
+
function createIframe(id){
|
|
2383
|
+
// We can't use following code as the name attribute
|
|
2384
|
+
// won't be properly registered in IE6, and new window
|
|
2385
|
+
// on form submit will open
|
|
2386
|
+
// var iframe = document.createElement('iframe');
|
|
2387
|
+
// iframe.setAttribute('name', id);
|
|
2388
|
+
|
|
2389
|
+
var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
|
|
2390
|
+
// src="javascript:false;" removes ie6 prompt on https
|
|
2391
|
+
|
|
2392
|
+
iframe.setAttribute('id', id);
|
|
2393
|
+
|
|
2394
|
+
iframe.style.display = 'none';
|
|
2395
|
+
document.body.appendChild(iframe);
|
|
2396
|
+
|
|
2397
|
+
return iframe;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
/**
|
|
2401
|
+
* Creates form, that will be submitted to iframe
|
|
2402
|
+
*/
|
|
2403
|
+
function createForm(id, iframe){
|
|
2404
|
+
var params = options.paramsStore.getParams(id),
|
|
2405
|
+
protocol = options.demoMode ? "GET" : "POST",
|
|
2406
|
+
form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>'),
|
|
2407
|
+
endpoint = options.endpointStore.getEndpoint(id),
|
|
2408
|
+
url = endpoint;
|
|
2409
|
+
|
|
2410
|
+
params[options.uuidParamName] = uuids[id];
|
|
2411
|
+
|
|
2412
|
+
if (!options.paramsInBody) {
|
|
2413
|
+
url = qq.obj2url(params, endpoint);
|
|
2414
|
+
}
|
|
2415
|
+
else {
|
|
2416
|
+
qq.obj2Inputs(params, form);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
form.setAttribute('action', url);
|
|
2420
|
+
form.setAttribute('target', iframe.name);
|
|
2421
|
+
form.style.display = 'none';
|
|
2422
|
+
document.body.appendChild(form);
|
|
2423
|
+
|
|
2424
|
+
return form;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
|
|
2428
|
+
api = {
|
|
2429
|
+
add: function(fileInput) {
|
|
2430
|
+
fileInput.setAttribute('name', options.inputName);
|
|
2431
|
+
|
|
2432
|
+
var id = inputs.push(fileInput) - 1;
|
|
2433
|
+
uuids[id] = qq.getUniqueId();
|
|
2434
|
+
|
|
2435
|
+
// remove file input from DOM
|
|
2436
|
+
if (fileInput.parentNode){
|
|
2437
|
+
qq(fileInput).remove();
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
return id;
|
|
2441
|
+
},
|
|
2442
|
+
getName: function(id) {
|
|
2443
|
+
/*jslint regexp: true*/
|
|
2444
|
+
|
|
2445
|
+
// get input value and remove path to normalize
|
|
2446
|
+
return inputs[id].value.replace(/.*(\/|\\)/, "");
|
|
2447
|
+
},
|
|
2448
|
+
isValid: function(id) {
|
|
2449
|
+
return inputs[id] !== undefined;
|
|
2450
|
+
},
|
|
2451
|
+
reset: function() {
|
|
2452
|
+
qq.UploadHandler.prototype.reset.apply(this, arguments);
|
|
2453
|
+
inputs = [];
|
|
2454
|
+
uuids = [];
|
|
2455
|
+
detachLoadEvents = {};
|
|
2456
|
+
},
|
|
2457
|
+
getUuid: function(id) {
|
|
2458
|
+
return uuids[id];
|
|
2459
|
+
},
|
|
2460
|
+
cancel: function(id) {
|
|
2461
|
+
options.onCancel(id, this.getName(id));
|
|
2462
|
+
|
|
2463
|
+
delete inputs[id];
|
|
2464
|
+
delete uuids[id];
|
|
2465
|
+
delete detachLoadEvents[id];
|
|
2466
|
+
|
|
2467
|
+
var iframe = document.getElementById(id);
|
|
2468
|
+
if (iframe) {
|
|
2469
|
+
// to cancel request set src to something else
|
|
2470
|
+
// we use src="javascript:false;" because it doesn't
|
|
2471
|
+
// trigger ie6 prompt on https
|
|
2472
|
+
iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
|
|
2473
|
+
|
|
2474
|
+
qq(iframe).remove();
|
|
2475
|
+
}
|
|
2476
|
+
},
|
|
2477
|
+
upload: function(id){
|
|
2478
|
+
var input = inputs[id],
|
|
2479
|
+
fileName = api.getName(id),
|
|
2480
|
+
iframe = createIframe(id),
|
|
2481
|
+
form = createForm(id, iframe);
|
|
2482
|
+
|
|
2483
|
+
if (!input){
|
|
2484
|
+
throw new Error('file with passed id was not added, or already uploaded or cancelled');
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
options.onUpload(id, this.getName(id));
|
|
2488
|
+
|
|
2489
|
+
form.appendChild(input);
|
|
2490
|
+
|
|
2491
|
+
attachLoadEvent(iframe, function(){
|
|
2492
|
+
log('iframe loaded');
|
|
2493
|
+
|
|
2494
|
+
var response = getIframeContentJson(iframe);
|
|
2495
|
+
|
|
2496
|
+
// timeout added to fix busy state in FF3.6
|
|
2497
|
+
setTimeout(function(){
|
|
2498
|
+
detachLoadEvents[id]();
|
|
2499
|
+
delete detachLoadEvents[id];
|
|
2500
|
+
qq(iframe).remove();
|
|
2501
|
+
}, 1);
|
|
2502
|
+
|
|
2503
|
+
if (!response.success) {
|
|
2504
|
+
if (options.onAutoRetry(id, fileName, response)) {
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
options.onComplete(id, fileName, response);
|
|
2509
|
+
uploadComplete(id);
|
|
2510
|
+
});
|
|
2511
|
+
|
|
2512
|
+
log('Sending upload request for ' + id);
|
|
2513
|
+
form.submit();
|
|
2514
|
+
qq(form).remove();
|
|
2515
|
+
|
|
2516
|
+
return id;
|
|
2517
|
+
}
|
|
2518
|
+
};
|
|
2519
|
+
|
|
2520
|
+
return api;
|
|
2521
|
+
};
|
|
2522
|
+
/*globals qq, File, XMLHttpRequest, FormData*/
|
|
2523
|
+
qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
|
|
2524
|
+
"use strict";
|
|
2525
|
+
|
|
2526
|
+
var options = o,
|
|
2527
|
+
uploadComplete = uploadCompleteCallback,
|
|
2528
|
+
log = logCallback,
|
|
2529
|
+
fileState = [],
|
|
2530
|
+
cookieItemDelimiter = "|",
|
|
2531
|
+
chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
|
|
2532
|
+
resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
|
|
2533
|
+
resumeId = getResumeId(),
|
|
2534
|
+
multipart = options.forceMultipart || options.paramsInBody,
|
|
2535
|
+
api;
|
|
2536
|
+
|
|
2537
|
+
|
|
2538
|
+
function addChunkingSpecificParams(id, params, chunkData) {
|
|
2539
|
+
var size = api.getSize(id),
|
|
2540
|
+
name = api.getName(id);
|
|
2541
|
+
|
|
2542
|
+
params[options.chunking.paramNames.partIndex] = chunkData.part;
|
|
2543
|
+
params[options.chunking.paramNames.partByteOffset] = chunkData.start;
|
|
2544
|
+
params[options.chunking.paramNames.chunkSize] = chunkData.end - chunkData.start;
|
|
2545
|
+
params[options.chunking.paramNames.totalParts] = chunkData.count;
|
|
2546
|
+
params[options.totalFileSizeParamName] = size;
|
|
2547
|
+
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
|
|
2551
|
+
* or an empty string. So, we will need to include the actual file name as a param in this case.
|
|
2552
|
+
*/
|
|
2553
|
+
if (multipart) {
|
|
2554
|
+
params[options.chunking.paramNames.filename] = name;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
function addResumeSpecificParams(params) {
|
|
2559
|
+
params[options.resume.paramNames.resuming] = true;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
function getChunk(file, startByte, endByte) {
|
|
2563
|
+
if (file.slice) {
|
|
2564
|
+
return file.slice(startByte, endByte);
|
|
2565
|
+
}
|
|
2566
|
+
else if (file.mozSlice) {
|
|
2567
|
+
return file.mozSlice(startByte, endByte);
|
|
2568
|
+
}
|
|
2569
|
+
else if (file.webkitSlice) {
|
|
2570
|
+
return file.webkitSlice(startByte, endByte);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
function getChunkData(id, chunkIndex) {
|
|
2575
|
+
var chunkSize = options.chunking.partSize,
|
|
2576
|
+
fileSize = api.getSize(id),
|
|
2577
|
+
file = fileState[id].file,
|
|
2578
|
+
startBytes = chunkSize * chunkIndex,
|
|
2579
|
+
endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
|
|
2580
|
+
totalChunks = getTotalChunks(id);
|
|
2581
|
+
|
|
2582
|
+
return {
|
|
2583
|
+
part: chunkIndex,
|
|
2584
|
+
start: startBytes,
|
|
2585
|
+
end: endBytes,
|
|
2586
|
+
count: totalChunks,
|
|
2587
|
+
blob: getChunk(file, startBytes, endBytes)
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
function getTotalChunks(id) {
|
|
2592
|
+
var fileSize = api.getSize(id),
|
|
2593
|
+
chunkSize = options.chunking.partSize;
|
|
2594
|
+
|
|
2595
|
+
return Math.ceil(fileSize / chunkSize);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
function createXhr(id) {
|
|
2599
|
+
fileState[id].xhr = new XMLHttpRequest();
|
|
2600
|
+
return fileState[id].xhr;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
|
|
2604
|
+
var formData = new FormData(),
|
|
2605
|
+
protocol = options.demoMode ? "GET" : "POST",
|
|
2606
|
+
endpoint = options.endpointStore.getEndpoint(id),
|
|
2607
|
+
url = endpoint,
|
|
2608
|
+
name = api.getName(id),
|
|
2609
|
+
size = api.getSize(id);
|
|
2610
|
+
|
|
2611
|
+
params[options.uuidParamName] = fileState[id].uuid;
|
|
2612
|
+
|
|
2613
|
+
if (multipart) {
|
|
2614
|
+
params[options.totalFileSizeParamName] = size;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
//build query string
|
|
2618
|
+
if (!options.paramsInBody) {
|
|
2619
|
+
params[options.inputName] = name;
|
|
2620
|
+
url = qq.obj2url(params, endpoint);
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
xhr.open(protocol, url, true);
|
|
2624
|
+
if (multipart) {
|
|
2625
|
+
if (options.paramsInBody) {
|
|
2626
|
+
qq.obj2FormData(params, formData);
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
formData.append(options.inputName, fileOrBlob);
|
|
2630
|
+
return formData;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
return fileOrBlob;
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
function setHeaders(id, xhr) {
|
|
2637
|
+
var extraHeaders = options.customHeaders,
|
|
2638
|
+
name = api.getName(id),
|
|
2639
|
+
file = fileState[id].file;
|
|
2640
|
+
|
|
2641
|
+
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
2642
|
+
xhr.setRequestHeader("Cache-Control", "no-cache");
|
|
2643
|
+
|
|
2644
|
+
if (!multipart) {
|
|
2645
|
+
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
|
2646
|
+
//NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
|
|
2647
|
+
xhr.setRequestHeader("X-Mime-Type", file.type);
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
qq.each(extraHeaders, function(name, val) {
|
|
2651
|
+
xhr.setRequestHeader(name, val);
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
function handleCompletedFile(id, response, xhr) {
|
|
2656
|
+
var name = api.getName(id),
|
|
2657
|
+
size = api.getSize(id);
|
|
2658
|
+
|
|
2659
|
+
fileState[id].attemptingResume = false;
|
|
2660
|
+
|
|
2661
|
+
options.onProgress(id, name, size, size);
|
|
2662
|
+
|
|
2663
|
+
options.onComplete(id, name, response, xhr);
|
|
2664
|
+
delete fileState[id].xhr;
|
|
2665
|
+
uploadComplete(id);
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
function uploadNextChunk(id) {
|
|
2669
|
+
var chunkData = getChunkData(id, fileState[id].remainingChunkIdxs[0]),
|
|
2670
|
+
xhr = createXhr(id),
|
|
2671
|
+
size = api.getSize(id),
|
|
2672
|
+
name = api.getName(id),
|
|
2673
|
+
toSend, params;
|
|
2674
|
+
|
|
2675
|
+
if (fileState[id].loaded === undefined) {
|
|
2676
|
+
fileState[id].loaded = 0;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
persistChunkData(id, chunkData);
|
|
2680
|
+
|
|
2681
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
|
|
2682
|
+
|
|
2683
|
+
xhr.upload.onprogress = function(e) {
|
|
2684
|
+
if (e.lengthComputable) {
|
|
2685
|
+
if (fileState[id].loaded < size) {
|
|
2686
|
+
var totalLoaded = e.loaded + fileState[id].loaded;
|
|
2687
|
+
options.onProgress(id, name, totalLoaded, size);
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2691
|
+
|
|
2692
|
+
options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
|
|
2693
|
+
|
|
2694
|
+
params = options.paramsStore.getParams(id);
|
|
2695
|
+
addChunkingSpecificParams(id, params, chunkData);
|
|
2696
|
+
|
|
2697
|
+
if (fileState[id].attemptingResume) {
|
|
2698
|
+
addResumeSpecificParams(params);
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
|
|
2702
|
+
setHeaders(id, xhr);
|
|
2703
|
+
|
|
2704
|
+
log('Sending chunked upload request for ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
|
|
2705
|
+
xhr.send(toSend);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
|
|
2709
|
+
function handleSuccessfullyCompletedChunk(id, response, xhr) {
|
|
2710
|
+
var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
|
|
2711
|
+
chunkData = getChunkData(id, chunkIdx);
|
|
2712
|
+
|
|
2713
|
+
fileState[id].attemptingResume = false;
|
|
2714
|
+
fileState[id].loaded += chunkData.end - chunkData.start;
|
|
2715
|
+
|
|
2716
|
+
if (fileState[id].remainingChunkIdxs.length > 0) {
|
|
2717
|
+
uploadNextChunk(id);
|
|
2718
|
+
}
|
|
2719
|
+
else {
|
|
2720
|
+
deletePersistedChunkData(id);
|
|
2721
|
+
handleCompletedFile(id, response, xhr);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
function isErrorResponse(xhr, response) {
|
|
2726
|
+
return xhr.status !== 200 || !response.success || response.reset;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
function parseResponse(xhr) {
|
|
2730
|
+
var response;
|
|
2731
|
+
|
|
2732
|
+
try {
|
|
2733
|
+
response = qq.parseJson(xhr.responseText);
|
|
2734
|
+
}
|
|
2735
|
+
catch(error) {
|
|
2736
|
+
log('Error when attempting to parse xhr response text (' + error + ')', 'error');
|
|
2737
|
+
response = {};
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
return response;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
function handleResetResponse(id) {
|
|
2744
|
+
log('Server has ordered chunking effort to be restarted on next attempt for file ID ' + id, 'error');
|
|
2745
|
+
|
|
2746
|
+
if (resumeEnabled) {
|
|
2747
|
+
deletePersistedChunkData(id);
|
|
2748
|
+
}
|
|
2749
|
+
fileState[id].remainingChunkIdxs = [];
|
|
2750
|
+
delete fileState[id].loaded;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
function handleResetResponseOnResumeAttempt(id) {
|
|
2754
|
+
fileState[id].attemptingResume = false;
|
|
2755
|
+
log("Server has declared that it cannot handle resume for file ID " + id + " - starting from the first chunk", 'error');
|
|
2756
|
+
api.upload(id, true);
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
function handleNonResetErrorResponse(id, response, xhr) {
|
|
2760
|
+
var name = api.getName(id);
|
|
2761
|
+
|
|
2762
|
+
if (options.onAutoRetry(id, name, response, xhr)) {
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
else {
|
|
2766
|
+
handleCompletedFile(id, response, xhr);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
function onComplete(id, xhr) {
|
|
2771
|
+
var response;
|
|
2772
|
+
|
|
2773
|
+
// the request was aborted/cancelled
|
|
2774
|
+
if (!fileState[id]) {
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
log("xhr - server response received for " + id);
|
|
2779
|
+
log("responseText = " + xhr.responseText);
|
|
2780
|
+
response = parseResponse(xhr);
|
|
2781
|
+
|
|
2782
|
+
if (isErrorResponse(xhr, response)) {
|
|
2783
|
+
if (response.reset) {
|
|
2784
|
+
handleResetResponse(id);
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
if (fileState[id].attemptingResume && response.reset) {
|
|
2788
|
+
handleResetResponseOnResumeAttempt(id);
|
|
2789
|
+
}
|
|
2790
|
+
else {
|
|
2791
|
+
handleNonResetErrorResponse(id, response, xhr);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
else if (chunkFiles) {
|
|
2795
|
+
handleSuccessfullyCompletedChunk(id, response, xhr);
|
|
2796
|
+
}
|
|
2797
|
+
else {
|
|
2798
|
+
handleCompletedFile(id, response, xhr);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
function getChunkDataForCallback(chunkData) {
|
|
2803
|
+
return {
|
|
2804
|
+
partIndex: chunkData.part,
|
|
2805
|
+
startByte: chunkData.start + 1,
|
|
2806
|
+
endByte: chunkData.end,
|
|
2807
|
+
totalParts: chunkData.count
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
function getReadyStateChangeHandler(id, xhr) {
|
|
2812
|
+
return function() {
|
|
2813
|
+
if (xhr.readyState === 4) {
|
|
2814
|
+
onComplete(id, xhr);
|
|
2815
|
+
}
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
function persistChunkData(id, chunkData) {
|
|
2820
|
+
var fileUuid = api.getUuid(id),
|
|
2821
|
+
cookieName = getChunkDataCookieName(id),
|
|
2822
|
+
cookieValue = fileUuid + cookieItemDelimiter + chunkData.part,
|
|
2823
|
+
cookieExpDays = options.resume.cookiesExpireIn;
|
|
2824
|
+
|
|
2825
|
+
qq.setCookie(cookieName, cookieValue, cookieExpDays);
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
function deletePersistedChunkData(id) {
|
|
2829
|
+
var cookieName = getChunkDataCookieName(id);
|
|
2830
|
+
|
|
2831
|
+
qq.deleteCookie(cookieName);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
function getPersistedChunkData(id) {
|
|
2835
|
+
var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
|
|
2836
|
+
delimiterIndex, uuid, partIndex;
|
|
2837
|
+
|
|
2838
|
+
if (chunkCookieValue) {
|
|
2839
|
+
delimiterIndex = chunkCookieValue.indexOf(cookieItemDelimiter);
|
|
2840
|
+
uuid = chunkCookieValue.substr(0, delimiterIndex);
|
|
2841
|
+
partIndex = parseInt(chunkCookieValue.substr(delimiterIndex + 1, chunkCookieValue.length - delimiterIndex), 10);
|
|
2842
|
+
|
|
2843
|
+
return {
|
|
2844
|
+
uuid: uuid,
|
|
2845
|
+
part: partIndex
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
function getChunkDataCookieName(id) {
|
|
2851
|
+
var filename = api.getName(id),
|
|
2852
|
+
fileSize = api.getSize(id),
|
|
2853
|
+
maxChunkSize = options.chunking.partSize,
|
|
2854
|
+
cookieName;
|
|
2855
|
+
|
|
2856
|
+
cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
|
|
2857
|
+
|
|
2858
|
+
if (resumeId !== undefined) {
|
|
2859
|
+
cookieName += cookieItemDelimiter + resumeId;
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
return cookieName;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
function getResumeId() {
|
|
2866
|
+
if (options.resume.id !== null &&
|
|
2867
|
+
options.resume.id !== undefined &&
|
|
2868
|
+
!qq.isFunction(options.resume.id) &&
|
|
2869
|
+
!qq.isObject(options.resume.id)) {
|
|
2870
|
+
|
|
2871
|
+
return options.resume.id;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
function handleFileChunkingUpload(id, retry) {
|
|
2876
|
+
var name = api.getName(id),
|
|
2877
|
+
firstChunkIndex = 0,
|
|
2878
|
+
persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
|
|
2879
|
+
|
|
2880
|
+
if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
|
|
2881
|
+
fileState[id].remainingChunkIdxs = [];
|
|
2882
|
+
|
|
2883
|
+
if (resumeEnabled && !retry) {
|
|
2884
|
+
persistedChunkInfoForResume = getPersistedChunkData(id);
|
|
2885
|
+
if (persistedChunkInfoForResume) {
|
|
2886
|
+
firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
|
|
2887
|
+
if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
|
|
2888
|
+
firstChunkIndex = persistedChunkInfoForResume.part;
|
|
2889
|
+
fileState[id].uuid = persistedChunkInfoForResume.uuid;
|
|
2890
|
+
fileState[id].loaded = firstChunkDataForResume.start;
|
|
2891
|
+
fileState[id].attemptingResume = true;
|
|
2892
|
+
log('Resuming ' + name + " at partition index " + firstChunkIndex);
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
|
|
2898
|
+
fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
uploadNextChunk(id);
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
function handleStandardFileUpload(id) {
|
|
2906
|
+
var file = fileState[id].file,
|
|
2907
|
+
name = api.getName(id),
|
|
2908
|
+
xhr, params, toSend;
|
|
2909
|
+
|
|
2910
|
+
fileState[id].loaded = 0;
|
|
2911
|
+
|
|
2912
|
+
xhr = createXhr(id);
|
|
2913
|
+
|
|
2914
|
+
xhr.upload.onprogress = function(e){
|
|
2915
|
+
if (e.lengthComputable){
|
|
2916
|
+
fileState[id].loaded = e.loaded;
|
|
2917
|
+
options.onProgress(id, name, e.loaded, e.total);
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
|
|
2921
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
|
|
2922
|
+
|
|
2923
|
+
params = options.paramsStore.getParams(id);
|
|
2924
|
+
toSend = setParamsAndGetEntityToSend(params, xhr, file, id);
|
|
2925
|
+
setHeaders(id, xhr);
|
|
2926
|
+
|
|
2927
|
+
log('Sending upload request for ' + id);
|
|
2928
|
+
xhr.send(toSend);
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
|
|
2932
|
+
api = {
|
|
2933
|
+
/**
|
|
2934
|
+
* Adds file to the queue
|
|
2935
|
+
* Returns id to use with upload, cancel
|
|
2936
|
+
**/
|
|
2937
|
+
add: function(file){
|
|
2938
|
+
if (!(file instanceof File)){
|
|
2939
|
+
throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
|
|
2943
|
+
var id = fileState.push({file: file}) - 1;
|
|
2944
|
+
fileState[id].uuid = qq.getUniqueId();
|
|
2945
|
+
|
|
2946
|
+
return id;
|
|
2947
|
+
},
|
|
2948
|
+
getName: function(id){
|
|
2949
|
+
var file = fileState[id].file;
|
|
2950
|
+
// fix missing name in Safari 4
|
|
2951
|
+
//NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
|
|
2952
|
+
return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
|
2953
|
+
},
|
|
2954
|
+
getSize: function(id){
|
|
2955
|
+
/*jshint eqnull: true*/
|
|
2956
|
+
var file = fileState[id].file;
|
|
2957
|
+
return file.fileSize != null ? file.fileSize : file.size;
|
|
2958
|
+
},
|
|
2959
|
+
getFile: function(id) {
|
|
2960
|
+
if (fileState[id]) {
|
|
2961
|
+
return fileState[id].file;
|
|
2962
|
+
}
|
|
2963
|
+
},
|
|
2964
|
+
/**
|
|
2965
|
+
* Returns uploaded bytes for file identified by id
|
|
2966
|
+
*/
|
|
2967
|
+
getLoaded: function(id){
|
|
2968
|
+
return fileState[id].loaded || 0;
|
|
2969
|
+
},
|
|
2970
|
+
isValid: function(id) {
|
|
2971
|
+
return fileState[id] !== undefined;
|
|
2972
|
+
},
|
|
2973
|
+
reset: function() {
|
|
2974
|
+
fileState = [];
|
|
2975
|
+
},
|
|
2976
|
+
getUuid: function(id) {
|
|
2977
|
+
return fileState[id].uuid;
|
|
2978
|
+
},
|
|
2979
|
+
/**
|
|
2980
|
+
* Sends the file identified by id to the server
|
|
2981
|
+
*/
|
|
2982
|
+
upload: function(id, retry){
|
|
2983
|
+
var name = this.getName(id);
|
|
2984
|
+
|
|
2985
|
+
options.onUpload(id, name);
|
|
2986
|
+
|
|
2987
|
+
if (chunkFiles) {
|
|
2988
|
+
handleFileChunkingUpload(id, retry);
|
|
2989
|
+
}
|
|
2990
|
+
else {
|
|
2991
|
+
handleStandardFileUpload(id);
|
|
2992
|
+
}
|
|
2993
|
+
},
|
|
2994
|
+
cancel: function(id){
|
|
2995
|
+
options.onCancel(id, this.getName(id));
|
|
2996
|
+
|
|
2997
|
+
if (fileState[id].xhr){
|
|
2998
|
+
fileState[id].xhr.abort();
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
if (resumeEnabled) {
|
|
3002
|
+
deletePersistedChunkData(id);
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
delete fileState[id];
|
|
3006
|
+
},
|
|
3007
|
+
getResumableFilesData: function() {
|
|
3008
|
+
var matchingCookieNames = [],
|
|
3009
|
+
resumableFilesData = [];
|
|
3010
|
+
|
|
3011
|
+
if (chunkFiles && resumeEnabled) {
|
|
3012
|
+
if (resumeId === undefined) {
|
|
3013
|
+
matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
|
|
3014
|
+
cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
|
|
3015
|
+
}
|
|
3016
|
+
else {
|
|
3017
|
+
matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
|
|
3018
|
+
cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
|
|
3019
|
+
cookieItemDelimiter + resumeId + "="));
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
qq.each(matchingCookieNames, function(idx, cookieName) {
|
|
3023
|
+
var cookiesNameParts = cookieName.split(cookieItemDelimiter);
|
|
3024
|
+
var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
|
|
3025
|
+
|
|
3026
|
+
resumableFilesData.push({
|
|
3027
|
+
name: decodeURIComponent(cookiesNameParts[1]),
|
|
3028
|
+
size: cookiesNameParts[2],
|
|
3029
|
+
uuid: cookieValueParts[0],
|
|
3030
|
+
partIdx: cookieValueParts[1]
|
|
3031
|
+
});
|
|
3032
|
+
});
|
|
3033
|
+
|
|
3034
|
+
return resumableFilesData;
|
|
3035
|
+
}
|
|
3036
|
+
return [];
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
|
|
3040
|
+
return api;
|
|
3041
|
+
};
|
|
3042
|
+
/*globals jQuery, qq*/
|
|
3043
|
+
(function($) {
|
|
3044
|
+
"use strict";
|
|
3045
|
+
var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
|
|
3046
|
+
delegateCommand;
|
|
3047
|
+
|
|
3048
|
+
pluginOptions = ['uploaderType'];
|
|
3049
|
+
|
|
3050
|
+
init = function (options) {
|
|
3051
|
+
if (options) {
|
|
3052
|
+
var xformedOpts = transformVariables(options);
|
|
3053
|
+
addCallbacks(xformedOpts);
|
|
3054
|
+
|
|
3055
|
+
if (pluginOption('uploaderType') === 'basic') {
|
|
3056
|
+
uploader(new qq.FineUploaderBasic(xformedOpts));
|
|
3057
|
+
}
|
|
3058
|
+
else {
|
|
3059
|
+
uploader(new qq.FineUploader(xformedOpts));
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
return $el;
|
|
3064
|
+
};
|
|
3065
|
+
|
|
3066
|
+
dataStore = function(key, val) {
|
|
3067
|
+
var data = $el.data('fineuploader');
|
|
3068
|
+
|
|
3069
|
+
if (val) {
|
|
3070
|
+
if (data === undefined) {
|
|
3071
|
+
data = {};
|
|
3072
|
+
}
|
|
3073
|
+
data[key] = val;
|
|
3074
|
+
$el.data('fineuploader', data);
|
|
3075
|
+
}
|
|
3076
|
+
else {
|
|
3077
|
+
if (data === undefined) {
|
|
3078
|
+
return null;
|
|
3079
|
+
}
|
|
3080
|
+
return data[key];
|
|
3081
|
+
}
|
|
3082
|
+
};
|
|
3083
|
+
|
|
3084
|
+
//the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
|
|
3085
|
+
// tied to this instance of the plug-in
|
|
3086
|
+
uploader = function(instanceToStore) {
|
|
3087
|
+
return dataStore('uploader', instanceToStore);
|
|
3088
|
+
};
|
|
3089
|
+
|
|
3090
|
+
pluginOption = function(option, optionVal) {
|
|
3091
|
+
return dataStore(option, optionVal);
|
|
3092
|
+
};
|
|
3093
|
+
|
|
3094
|
+
//implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
|
|
3095
|
+
// return the result of executing the bound handler back to Fine Uploader
|
|
3096
|
+
addCallbacks = function(transformedOpts) {
|
|
3097
|
+
var callbacks = transformedOpts.callbacks = {};
|
|
3098
|
+
|
|
3099
|
+
$.each(new qq.FineUploaderBasic()._options.callbacks, function(prop, func) {
|
|
3100
|
+
var name, $callbackEl;
|
|
3101
|
+
|
|
3102
|
+
name = /^on(\w+)/.exec(prop)[1];
|
|
3103
|
+
name = name.substring(0, 1).toLowerCase() + name.substring(1);
|
|
3104
|
+
$callbackEl = $el;
|
|
3105
|
+
|
|
3106
|
+
callbacks[prop] = function() {
|
|
3107
|
+
var args = Array.prototype.slice.call(arguments);
|
|
3108
|
+
return $callbackEl.triggerHandler(name, args);
|
|
3109
|
+
};
|
|
3110
|
+
});
|
|
3111
|
+
};
|
|
3112
|
+
|
|
3113
|
+
//transform jQuery objects into HTMLElements, and pass along all other option properties
|
|
3114
|
+
transformVariables = function(source, dest) {
|
|
3115
|
+
var xformed, arrayVals;
|
|
3116
|
+
|
|
3117
|
+
if (dest === undefined) {
|
|
3118
|
+
if (source.uploaderType !== 'basic') {
|
|
3119
|
+
xformed = { element : $el[0] };
|
|
3120
|
+
}
|
|
3121
|
+
else {
|
|
3122
|
+
xformed = {};
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
else {
|
|
3126
|
+
xformed = dest;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
$.each(source, function(prop, val) {
|
|
3130
|
+
if ($.inArray(prop, pluginOptions) >= 0) {
|
|
3131
|
+
pluginOption(prop, val);
|
|
3132
|
+
}
|
|
3133
|
+
else if (val instanceof $) {
|
|
3134
|
+
xformed[prop] = val[0];
|
|
3135
|
+
}
|
|
3136
|
+
else if ($.isPlainObject(val)) {
|
|
3137
|
+
xformed[prop] = {};
|
|
3138
|
+
transformVariables(val, xformed[prop]);
|
|
3139
|
+
}
|
|
3140
|
+
else if ($.isArray(val)) {
|
|
3141
|
+
arrayVals = [];
|
|
3142
|
+
$.each(val, function(idx, arrayVal) {
|
|
3143
|
+
if (arrayVal instanceof $) {
|
|
3144
|
+
$.merge(arrayVals, arrayVal);
|
|
3145
|
+
}
|
|
3146
|
+
else {
|
|
3147
|
+
arrayVals.push(arrayVal);
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
xformed[prop] = arrayVals;
|
|
3151
|
+
}
|
|
3152
|
+
else {
|
|
3153
|
+
xformed[prop] = val;
|
|
3154
|
+
}
|
|
3155
|
+
});
|
|
3156
|
+
|
|
3157
|
+
if (dest === undefined) {
|
|
3158
|
+
return xformed;
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
|
|
3162
|
+
isValidCommand = function(command) {
|
|
3163
|
+
return $.type(command) === "string" &&
|
|
3164
|
+
!command.match(/^_/) && //enforce private methods convention
|
|
3165
|
+
uploader()[command] !== undefined;
|
|
3166
|
+
};
|
|
3167
|
+
|
|
3168
|
+
//assuming we have already verified that this is a valid command, call the associated function in the underlying
|
|
3169
|
+
// Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
|
|
3170
|
+
delegateCommand = function(command) {
|
|
3171
|
+
var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
|
|
3172
|
+
|
|
3173
|
+
transformVariables(origArgs, xformedArgs);
|
|
3174
|
+
|
|
3175
|
+
return uploader()[command].apply(uploader(), xformedArgs);
|
|
3176
|
+
};
|
|
3177
|
+
|
|
3178
|
+
$.fn.fineUploader = function(optionsOrCommand) {
|
|
3179
|
+
var self = this, selfArgs = arguments, retVals = [];
|
|
3180
|
+
|
|
3181
|
+
this.each(function(index, el) {
|
|
3182
|
+
$el = $(el);
|
|
3183
|
+
|
|
3184
|
+
if (uploader() && isValidCommand(optionsOrCommand)) {
|
|
3185
|
+
retVals.push(delegateCommand.apply(self, selfArgs));
|
|
3186
|
+
|
|
3187
|
+
if (self.length === 1) {
|
|
3188
|
+
return false;
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
|
|
3192
|
+
init.apply(self, selfArgs);
|
|
3193
|
+
}
|
|
3194
|
+
else {
|
|
3195
|
+
$.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
|
|
3196
|
+
}
|
|
3197
|
+
});
|
|
3198
|
+
|
|
3199
|
+
if (retVals.length === 1) {
|
|
3200
|
+
return retVals[0];
|
|
3201
|
+
}
|
|
3202
|
+
else if (retVals.length > 1) {
|
|
3203
|
+
return retVals;
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
return this;
|
|
3207
|
+
};
|
|
3208
|
+
|
|
3209
|
+
}(jQuery));
|