fineuploader-rails 3.0 → 3.3
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.
Binary file
|
@@ -1,6 +1,4 @@
|
|
1
1
|
/**
|
2
|
-
* Fine Uploader - version 3.0
|
3
|
-
*
|
4
2
|
* http://github.com/Valums-File-Uploader/file-uploader
|
5
3
|
*
|
6
4
|
* Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
|
@@ -10,8 +8,7 @@
|
|
10
8
|
*
|
11
9
|
* Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
|
12
10
|
*/
|
13
|
-
|
14
|
-
var qq = qq || {};
|
11
|
+
/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
|
15
12
|
var qq = function(element) {
|
16
13
|
"use strict";
|
17
14
|
|
@@ -44,13 +41,14 @@ var qq = function(element) {
|
|
44
41
|
|
45
42
|
contains: function(descendant) {
|
46
43
|
// compareposition returns false in this case
|
47
|
-
if (element
|
44
|
+
if (element === descendant) {
|
48
45
|
return true;
|
49
46
|
}
|
50
47
|
|
51
48
|
if (element.contains){
|
52
49
|
return element.contains(descendant);
|
53
50
|
} else {
|
51
|
+
/*jslint bitwise: true*/
|
54
52
|
return !!(descendant.compareDocumentPosition(element) & 8);
|
55
53
|
}
|
56
54
|
},
|
@@ -73,8 +71,8 @@ var qq = function(element) {
|
|
73
71
|
* Fixes opacity in IE6-8.
|
74
72
|
*/
|
75
73
|
css: function(styles) {
|
76
|
-
if (styles.opacity
|
77
|
-
if (typeof element.style.opacity
|
74
|
+
if (styles.opacity !== null){
|
75
|
+
if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
|
78
76
|
styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
|
79
77
|
}
|
80
78
|
}
|
@@ -102,19 +100,20 @@ var qq = function(element) {
|
|
102
100
|
},
|
103
101
|
|
104
102
|
getByClass: function(className) {
|
103
|
+
var candidates,
|
104
|
+
result = [];
|
105
|
+
|
105
106
|
if (element.querySelectorAll){
|
106
107
|
return element.querySelectorAll('.' + className);
|
107
108
|
}
|
108
109
|
|
109
|
-
|
110
|
-
var candidates = element.getElementsByTagName("*");
|
111
|
-
var len = candidates.length;
|
110
|
+
candidates = element.getElementsByTagName("*");
|
112
111
|
|
113
|
-
|
114
|
-
if (qq(
|
115
|
-
result.push(
|
112
|
+
qq.each(candidates, function(idx, val) {
|
113
|
+
if (qq(val).hasClass(className)){
|
114
|
+
result.push(val);
|
116
115
|
}
|
117
|
-
}
|
116
|
+
});
|
118
117
|
return result;
|
119
118
|
},
|
120
119
|
|
@@ -123,7 +122,7 @@ var qq = function(element) {
|
|
123
122
|
child = element.firstChild;
|
124
123
|
|
125
124
|
while (child){
|
126
|
-
if (child.nodeType
|
125
|
+
if (child.nodeType === 1){
|
127
126
|
children.push(child);
|
128
127
|
}
|
129
128
|
child = child.nextSibling;
|
@@ -145,6 +144,8 @@ var qq = function(element) {
|
|
145
144
|
};
|
146
145
|
|
147
146
|
qq.log = function(message, level) {
|
147
|
+
"use strict";
|
148
|
+
|
148
149
|
if (window.console) {
|
149
150
|
if (!level || level === 'info') {
|
150
151
|
window.console.log(message);
|
@@ -166,22 +167,84 @@ qq.isObject = function(variable) {
|
|
166
167
|
return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
|
167
168
|
};
|
168
169
|
|
169
|
-
qq.
|
170
|
+
qq.isFunction = function(variable) {
|
170
171
|
"use strict";
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
172
|
+
return typeof(variable) === "function";
|
173
|
+
};
|
174
|
+
|
175
|
+
qq.trimStr = function(string) {
|
176
|
+
if (String.prototype.trim) {
|
177
|
+
return string.trim();
|
178
|
+
}
|
179
|
+
|
180
|
+
return string.replace(/^\s+|\s+$/g,'');
|
181
|
+
};
|
182
|
+
|
183
|
+
qq.isFileOrInput = function(maybeFileOrInput) {
|
184
|
+
"use strict";
|
185
|
+
if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
|
186
|
+
return true;
|
187
|
+
}
|
188
|
+
else if (window.HTMLInputElement) {
|
189
|
+
if (maybeFileOrInput instanceof HTMLInputElement) {
|
190
|
+
if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
|
191
|
+
return true;
|
179
192
|
}
|
180
|
-
|
181
|
-
|
193
|
+
}
|
194
|
+
}
|
195
|
+
else if (maybeFileOrInput.tagName) {
|
196
|
+
if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
|
197
|
+
if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
|
198
|
+
return true;
|
182
199
|
}
|
183
200
|
}
|
184
201
|
}
|
202
|
+
|
203
|
+
return false;
|
204
|
+
};
|
205
|
+
|
206
|
+
qq.isBlob = function(maybeBlob) {
|
207
|
+
"use strict";
|
208
|
+
return window.Blob && maybeBlob instanceof Blob;
|
209
|
+
};
|
210
|
+
|
211
|
+
qq.isXhrUploadSupported = function() {
|
212
|
+
"use strict";
|
213
|
+
var input = document.createElement('input');
|
214
|
+
input.type = 'file';
|
215
|
+
|
216
|
+
return (
|
217
|
+
input.multiple !== undefined &&
|
218
|
+
typeof File !== "undefined" &&
|
219
|
+
typeof FormData !== "undefined" &&
|
220
|
+
typeof (new XMLHttpRequest()).upload !== "undefined" );
|
221
|
+
};
|
222
|
+
|
223
|
+
qq.isFolderDropSupported = function(dataTransfer) {
|
224
|
+
"use strict";
|
225
|
+
return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
|
226
|
+
};
|
227
|
+
|
228
|
+
qq.isFileChunkingSupported = function() {
|
229
|
+
"use strict";
|
230
|
+
return !qq.android() && //android's impl of Blob.slice is broken
|
231
|
+
qq.isXhrUploadSupported() &&
|
232
|
+
(File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
|
233
|
+
};
|
234
|
+
|
235
|
+
qq.extend = function (first, second, extendNested) {
|
236
|
+
"use strict";
|
237
|
+
qq.each(second, function(prop, val) {
|
238
|
+
if (extendNested && qq.isObject(val)) {
|
239
|
+
if (first[prop] === undefined) {
|
240
|
+
first[prop] = {};
|
241
|
+
}
|
242
|
+
qq.extend(first[prop], val, true);
|
243
|
+
}
|
244
|
+
else {
|
245
|
+
first[prop] = val;
|
246
|
+
}
|
247
|
+
});
|
185
248
|
};
|
186
249
|
|
187
250
|
/**
|
@@ -189,40 +252,75 @@ qq.extend = function (first, second, extendNested) {
|
|
189
252
|
* @param {Number} [from] The index at which to begin the search
|
190
253
|
*/
|
191
254
|
qq.indexOf = function(arr, elt, from){
|
192
|
-
|
255
|
+
"use strict";
|
256
|
+
|
257
|
+
if (arr.indexOf) {
|
258
|
+
return arr.indexOf(elt, from);
|
259
|
+
}
|
193
260
|
|
194
261
|
from = from || 0;
|
195
262
|
var len = arr.length;
|
196
263
|
|
197
|
-
if (from < 0)
|
264
|
+
if (from < 0) {
|
265
|
+
from += len;
|
266
|
+
}
|
198
267
|
|
199
|
-
for (; from < len; from
|
200
|
-
if (from
|
268
|
+
for (; from < len; from+=1){
|
269
|
+
if (arr.hasOwnProperty(from) && arr[from] === elt){
|
201
270
|
return from;
|
202
271
|
}
|
203
272
|
}
|
204
273
|
return -1;
|
205
274
|
};
|
206
275
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
276
|
+
//this is a version 4 UUID
|
277
|
+
qq.getUniqueId = function(){
|
278
|
+
"use strict";
|
279
|
+
|
280
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
281
|
+
/*jslint eqeq: true, bitwise: true*/
|
282
|
+
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
283
|
+
return v.toString(16);
|
284
|
+
});
|
285
|
+
};
|
211
286
|
|
212
287
|
//
|
213
288
|
// Browsers and platforms detection
|
214
289
|
|
215
|
-
qq.ie = function(){
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
qq.
|
220
|
-
|
290
|
+
qq.ie = function(){
|
291
|
+
"use strict";
|
292
|
+
return navigator.userAgent.indexOf('MSIE') !== -1;
|
293
|
+
};
|
294
|
+
qq.ie10 = function(){
|
295
|
+
"use strict";
|
296
|
+
return navigator.userAgent.indexOf('MSIE 10') !== -1;
|
297
|
+
};
|
298
|
+
qq.safari = function(){
|
299
|
+
"use strict";
|
300
|
+
return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
|
301
|
+
};
|
302
|
+
qq.chrome = function(){
|
303
|
+
"use strict";
|
304
|
+
return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
|
305
|
+
};
|
306
|
+
qq.firefox = function(){
|
307
|
+
"use strict";
|
308
|
+
return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
|
309
|
+
};
|
310
|
+
qq.windows = function(){
|
311
|
+
"use strict";
|
312
|
+
return navigator.platform === "Win32";
|
313
|
+
};
|
314
|
+
qq.android = function(){
|
315
|
+
"use strict";
|
316
|
+
return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
|
317
|
+
};
|
221
318
|
|
222
319
|
//
|
223
320
|
// Events
|
224
321
|
|
225
322
|
qq.preventDefault = function(e){
|
323
|
+
"use strict";
|
226
324
|
if (e.preventDefault){
|
227
325
|
e.preventDefault();
|
228
326
|
} else{
|
@@ -235,6 +333,7 @@ qq.preventDefault = function(e){
|
|
235
333
|
* Uses innerHTML to create an element
|
236
334
|
*/
|
237
335
|
qq.toElement = (function(){
|
336
|
+
"use strict";
|
238
337
|
var div = document.createElement('div');
|
239
338
|
return function(html){
|
240
339
|
div.innerHTML = html;
|
@@ -242,7 +341,23 @@ qq.toElement = (function(){
|
|
242
341
|
div.removeChild(element);
|
243
342
|
return element;
|
244
343
|
};
|
245
|
-
}
|
344
|
+
}());
|
345
|
+
|
346
|
+
//key and value are passed to callback for each item in the object or array
|
347
|
+
qq.each = function(obj, callback) {
|
348
|
+
"use strict";
|
349
|
+
var key, retVal;
|
350
|
+
if (obj) {
|
351
|
+
for (key in obj) {
|
352
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
353
|
+
retVal = callback(key, obj[key]);
|
354
|
+
if (retVal === false) {
|
355
|
+
break;
|
356
|
+
}
|
357
|
+
}
|
358
|
+
}
|
359
|
+
}
|
360
|
+
};
|
246
361
|
|
247
362
|
/**
|
248
363
|
* obj2url() takes a json-object as argument and generates
|
@@ -261,15 +376,18 @@ qq.toElement = (function(){
|
|
261
376
|
* @return String encoded querystring
|
262
377
|
*/
|
263
378
|
qq.obj2url = function(obj, temp, prefixDone){
|
264
|
-
|
265
|
-
|
266
|
-
|
379
|
+
"use strict";
|
380
|
+
/*jshint laxbreak: true*/
|
381
|
+
var i, len,
|
382
|
+
uristrings = [],
|
383
|
+
prefix = '&',
|
384
|
+
add = function(nextObj, i){
|
267
385
|
var nextTemp = temp
|
268
386
|
? (/\[\]$/.test(temp)) // prevent double-encoding
|
269
387
|
? temp
|
270
388
|
: temp+'['+i+']'
|
271
389
|
: i;
|
272
|
-
if ((nextTemp
|
390
|
+
if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
|
273
391
|
uristrings.push(
|
274
392
|
(typeof nextObj === 'object')
|
275
393
|
? qq.obj2url(nextObj, nextTemp, true)
|
@@ -284,15 +402,17 @@ qq.obj2url = function(obj, temp, prefixDone){
|
|
284
402
|
prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
|
285
403
|
uristrings.push(temp);
|
286
404
|
uristrings.push(qq.obj2url(obj));
|
287
|
-
} else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj
|
405
|
+
} else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
|
288
406
|
// we wont use a for-in-loop on an array (performance)
|
289
|
-
for (
|
407
|
+
for (i = -1, len = obj.length; i < len; i+=1){
|
290
408
|
add(obj[i], i);
|
291
409
|
}
|
292
|
-
} else if ((typeof obj
|
410
|
+
} else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
|
293
411
|
// for anything else but a scalar, we will use for-in-loop
|
294
|
-
for (
|
295
|
-
|
412
|
+
for (i in obj){
|
413
|
+
if (obj.hasOwnProperty(i)) {
|
414
|
+
add(obj[i], i);
|
415
|
+
}
|
296
416
|
}
|
297
417
|
} else {
|
298
418
|
uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
|
@@ -307,29 +427,155 @@ qq.obj2url = function(obj, temp, prefixDone){
|
|
307
427
|
}
|
308
428
|
};
|
309
429
|
|
430
|
+
qq.obj2FormData = function(obj, formData, arrayKeyName) {
|
431
|
+
"use strict";
|
432
|
+
if (!formData) {
|
433
|
+
formData = new FormData();
|
434
|
+
}
|
435
|
+
|
436
|
+
qq.each(obj, function(key, val) {
|
437
|
+
key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
|
438
|
+
|
439
|
+
if (qq.isObject(val)) {
|
440
|
+
qq.obj2FormData(val, formData, key);
|
441
|
+
}
|
442
|
+
else if (qq.isFunction(val)) {
|
443
|
+
formData.append(key, val());
|
444
|
+
}
|
445
|
+
else {
|
446
|
+
formData.append(key, val);
|
447
|
+
}
|
448
|
+
});
|
449
|
+
|
450
|
+
return formData;
|
451
|
+
};
|
452
|
+
|
453
|
+
qq.obj2Inputs = function(obj, form) {
|
454
|
+
"use strict";
|
455
|
+
var input;
|
456
|
+
|
457
|
+
if (!form) {
|
458
|
+
form = document.createElement('form');
|
459
|
+
}
|
460
|
+
|
461
|
+
qq.obj2FormData(obj, {
|
462
|
+
append: function(key, val) {
|
463
|
+
input = document.createElement('input');
|
464
|
+
input.setAttribute('name', key);
|
465
|
+
input.setAttribute('value', val);
|
466
|
+
form.appendChild(input);
|
467
|
+
}
|
468
|
+
});
|
469
|
+
|
470
|
+
return form;
|
471
|
+
};
|
472
|
+
|
473
|
+
qq.setCookie = function(name, value, days) {
|
474
|
+
var date = new Date(),
|
475
|
+
expires = "";
|
476
|
+
|
477
|
+
if (days) {
|
478
|
+
date.setTime(date.getTime()+(days*24*60*60*1000));
|
479
|
+
expires = "; expires="+date.toGMTString();
|
480
|
+
}
|
481
|
+
|
482
|
+
document.cookie = name+"="+value+expires+"; path=/";
|
483
|
+
};
|
484
|
+
|
485
|
+
qq.getCookie = function(name) {
|
486
|
+
var nameEQ = name + "=",
|
487
|
+
ca = document.cookie.split(';'),
|
488
|
+
c;
|
489
|
+
|
490
|
+
for(var i=0;i < ca.length;i++) {
|
491
|
+
c = ca[i];
|
492
|
+
while (c.charAt(0)==' ') {
|
493
|
+
c = c.substring(1,c.length);
|
494
|
+
}
|
495
|
+
if (c.indexOf(nameEQ) === 0) {
|
496
|
+
return c.substring(nameEQ.length,c.length);
|
497
|
+
}
|
498
|
+
}
|
499
|
+
};
|
500
|
+
|
501
|
+
qq.getCookieNames = function(regexp) {
|
502
|
+
var cookies = document.cookie.split(';'),
|
503
|
+
cookieNames = [];
|
504
|
+
|
505
|
+
qq.each(cookies, function(idx, cookie) {
|
506
|
+
cookie = qq.trimStr(cookie);
|
507
|
+
|
508
|
+
var equalsIdx = cookie.indexOf("=");
|
509
|
+
|
510
|
+
if (cookie.match(regexp)) {
|
511
|
+
cookieNames.push(cookie.substr(0, equalsIdx));
|
512
|
+
}
|
513
|
+
});
|
514
|
+
|
515
|
+
return cookieNames;
|
516
|
+
};
|
517
|
+
|
518
|
+
qq.deleteCookie = function(name) {
|
519
|
+
qq.setCookie(name, "", -1);
|
520
|
+
};
|
521
|
+
|
522
|
+
qq.areCookiesEnabled = function() {
|
523
|
+
var randNum = Math.random() * 100000,
|
524
|
+
name = "qqCookieTest:" + randNum;
|
525
|
+
qq.setCookie(name, 1);
|
526
|
+
|
527
|
+
if (qq.getCookie(name)) {
|
528
|
+
qq.deleteCookie(name);
|
529
|
+
return true;
|
530
|
+
}
|
531
|
+
return false;
|
532
|
+
};
|
533
|
+
|
534
|
+
/**
|
535
|
+
* Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
|
536
|
+
* implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
|
537
|
+
*/
|
538
|
+
qq.parseJson = function(json) {
|
539
|
+
/*jshint evil: true*/
|
540
|
+
if (window.JSON && qq.isFunction(JSON.parse)) {
|
541
|
+
return JSON.parse(json);
|
542
|
+
} else {
|
543
|
+
return eval("(" + json + ")");
|
544
|
+
}
|
545
|
+
};
|
546
|
+
|
310
547
|
/**
|
311
548
|
* A generic module which supports object disposing in dispose() method.
|
312
549
|
* */
|
313
|
-
qq.DisposeSupport = {
|
314
|
-
|
550
|
+
qq.DisposeSupport = function() {
|
551
|
+
"use strict";
|
552
|
+
var disposers = [];
|
315
553
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
554
|
+
return {
|
555
|
+
/** Run all registered disposers */
|
556
|
+
dispose: function() {
|
557
|
+
var disposer;
|
558
|
+
do {
|
559
|
+
disposer = disposers.shift();
|
560
|
+
if (disposer) {
|
561
|
+
disposer();
|
562
|
+
}
|
563
|
+
}
|
564
|
+
while (disposer);
|
565
|
+
},
|
323
566
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
567
|
+
/** Attach event handler and register de-attacher as a disposer */
|
568
|
+
attach: function() {
|
569
|
+
var args = arguments;
|
570
|
+
/*jslint undef:true*/
|
571
|
+
this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
|
572
|
+
},
|
328
573
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
574
|
+
/** Add disposer to the collection */
|
575
|
+
addDisposer: function(disposeFunction) {
|
576
|
+
disposers.push(disposeFunction);
|
577
|
+
}
|
578
|
+
};
|
333
579
|
};
|
334
580
|
qq.UploadButton = function(o){
|
335
581
|
this._options = {
|
@@ -345,7 +591,7 @@ qq.UploadButton = function(o){
|
|
345
591
|
};
|
346
592
|
|
347
593
|
qq.extend(this._options, o);
|
348
|
-
|
594
|
+
this._disposeSupport = new qq.DisposeSupport();
|
349
595
|
|
350
596
|
this._element = this._options.element;
|
351
597
|
|
@@ -406,20 +652,20 @@ qq.UploadButton.prototype = {
|
|
406
652
|
this._element.appendChild(input);
|
407
653
|
|
408
654
|
var self = this;
|
409
|
-
this.
|
655
|
+
this._disposeSupport.attach(input, 'change', function(){
|
410
656
|
self._options.onChange(input);
|
411
657
|
});
|
412
658
|
|
413
|
-
this.
|
659
|
+
this._disposeSupport.attach(input, 'mouseover', function(){
|
414
660
|
qq(self._element).addClass(self._options.hoverClass);
|
415
661
|
});
|
416
|
-
this.
|
662
|
+
this._disposeSupport.attach(input, 'mouseout', function(){
|
417
663
|
qq(self._element).removeClass(self._options.hoverClass);
|
418
664
|
});
|
419
|
-
this.
|
665
|
+
this._disposeSupport.attach(input, 'focus', function(){
|
420
666
|
qq(self._element).addClass(self._options.focusClass);
|
421
667
|
});
|
422
|
-
this.
|
668
|
+
this._disposeSupport.attach(input, 'blur', function(){
|
423
669
|
qq(self._element).removeClass(self._options.focusClass);
|
424
670
|
});
|
425
671
|
|
@@ -445,9 +691,12 @@ qq.FineUploaderBasic = function(o){
|
|
445
691
|
request: {
|
446
692
|
endpoint: '/server/upload',
|
447
693
|
params: {},
|
694
|
+
paramsInBody: true,
|
448
695
|
customHeaders: {},
|
449
|
-
forceMultipart:
|
450
|
-
inputName: 'qqfile'
|
696
|
+
forceMultipart: true,
|
697
|
+
inputName: 'qqfile',
|
698
|
+
uuidName: 'qquuid',
|
699
|
+
totalFileSizeName: 'qqtotalfilesize'
|
451
700
|
},
|
452
701
|
validation: {
|
453
702
|
allowedExtensions: [],
|
@@ -456,15 +705,21 @@ qq.FineUploaderBasic = function(o){
|
|
456
705
|
stopOnFirstInvalidFile: true
|
457
706
|
},
|
458
707
|
callbacks: {
|
459
|
-
onSubmit: function(id,
|
460
|
-
onComplete: function(id,
|
461
|
-
onCancel: function(id,
|
462
|
-
onUpload: function(id,
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
708
|
+
onSubmit: function(id, name){},
|
709
|
+
onComplete: function(id, name, responseJSON){},
|
710
|
+
onCancel: function(id, name){},
|
711
|
+
onUpload: function(id, name){},
|
712
|
+
onUploadChunk: function(id, name, chunkData){},
|
713
|
+
onResume: function(id, fileName, chunkData){},
|
714
|
+
onProgress: function(id, name, loaded, total){},
|
715
|
+
onError: function(id, name, reason) {},
|
716
|
+
onAutoRetry: function(id, name, attemptNumber) {},
|
717
|
+
onManualRetry: function(id, name) {},
|
718
|
+
onValidateBatch: function(fileOrBlobData) {},
|
719
|
+
onValidate: function(fileOrBlobData) {},
|
720
|
+
onSubmitDelete: function(id) {},
|
721
|
+
onDelete: function(id){},
|
722
|
+
onDeleteComplete: function(id, xhr, isError){}
|
468
723
|
},
|
469
724
|
messages: {
|
470
725
|
typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
|
@@ -479,23 +734,79 @@ qq.FineUploaderBasic = function(o){
|
|
479
734
|
maxAutoAttempts: 3,
|
480
735
|
autoAttemptDelay: 5,
|
481
736
|
preventRetryResponseProperty: 'preventRetry'
|
737
|
+
},
|
738
|
+
classes: {
|
739
|
+
buttonHover: 'qq-upload-button-hover',
|
740
|
+
buttonFocus: 'qq-upload-button-focus'
|
741
|
+
},
|
742
|
+
chunking: {
|
743
|
+
enabled: false,
|
744
|
+
partSize: 2000000,
|
745
|
+
paramNames: {
|
746
|
+
partIndex: 'qqpartindex',
|
747
|
+
partByteOffset: 'qqpartbyteoffset',
|
748
|
+
chunkSize: 'qqchunksize',
|
749
|
+
totalFileSize: 'qqtotalfilesize',
|
750
|
+
totalParts: 'qqtotalparts',
|
751
|
+
filename: 'qqfilename'
|
752
|
+
}
|
753
|
+
},
|
754
|
+
resume: {
|
755
|
+
enabled: false,
|
756
|
+
id: null,
|
757
|
+
cookiesExpireIn: 7, //days
|
758
|
+
paramNames: {
|
759
|
+
resuming: "qqresume"
|
760
|
+
}
|
761
|
+
},
|
762
|
+
formatFileName: function(fileOrBlobName) {
|
763
|
+
if (fileOrBlobName.length > 33) {
|
764
|
+
fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
|
765
|
+
}
|
766
|
+
return fileOrBlobName;
|
767
|
+
},
|
768
|
+
text: {
|
769
|
+
sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
|
770
|
+
},
|
771
|
+
deleteFile : {
|
772
|
+
enabled: false,
|
773
|
+
endpoint: '/server/upload',
|
774
|
+
customHeaders: {},
|
775
|
+
params: {}
|
776
|
+
},
|
777
|
+
cors: {
|
778
|
+
expected: false,
|
779
|
+
sendCredentials: false
|
780
|
+
},
|
781
|
+
blobs: {
|
782
|
+
defaultName: 'Misc data',
|
783
|
+
paramNames: {
|
784
|
+
name: 'qqblobname'
|
785
|
+
}
|
482
786
|
}
|
483
787
|
};
|
484
788
|
|
485
789
|
qq.extend(this._options, o, true);
|
486
790
|
this._wrapCallbacks();
|
487
|
-
|
791
|
+
this._disposeSupport = new qq.DisposeSupport();
|
488
792
|
|
489
793
|
// number of files being uploaded
|
490
|
-
this._filesInProgress =
|
794
|
+
this._filesInProgress = [];
|
491
795
|
|
492
|
-
this.
|
796
|
+
this._storedIds = [];
|
493
797
|
|
494
798
|
this._autoRetries = [];
|
495
799
|
this._retryTimeouts = [];
|
496
800
|
this._preventRetries = [];
|
497
801
|
|
802
|
+
this._paramsStore = this._createParamsStore("request");
|
803
|
+
this._deleteFileParamsStore = this._createParamsStore("deleteFile");
|
804
|
+
|
805
|
+
this._endpointStore = this._createEndpointStore("request");
|
806
|
+
this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
|
807
|
+
|
498
808
|
this._handler = this._createUploadHandler();
|
809
|
+
this._deleteHandler = this._createDeleteHandler();
|
499
810
|
|
500
811
|
if (this._options.button){
|
501
812
|
this._button = this._createUploadButton(this._options.button);
|
@@ -514,21 +825,48 @@ qq.FineUploaderBasic.prototype = {
|
|
514
825
|
|
515
826
|
}
|
516
827
|
},
|
517
|
-
setParams: function(params){
|
518
|
-
|
828
|
+
setParams: function(params, id) {
|
829
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
830
|
+
if (id == null) {
|
831
|
+
this._options.request.params = params;
|
832
|
+
}
|
833
|
+
else {
|
834
|
+
this._paramsStore.setParams(params, id);
|
835
|
+
}
|
836
|
+
},
|
837
|
+
setDeleteFileParams: function(params, id) {
|
838
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
839
|
+
if (id == null) {
|
840
|
+
this._options.deleteFile.params = params;
|
841
|
+
}
|
842
|
+
else {
|
843
|
+
this._deleteFileParamsStore.setParams(params, id);
|
844
|
+
}
|
845
|
+
},
|
846
|
+
setEndpoint: function(endpoint, id) {
|
847
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
848
|
+
if (id == null) {
|
849
|
+
this._options.request.endpoint = endpoint;
|
850
|
+
}
|
851
|
+
else {
|
852
|
+
this._endpointStore.setEndpoint(endpoint, id);
|
853
|
+
}
|
519
854
|
},
|
520
855
|
getInProgress: function(){
|
521
|
-
return this._filesInProgress;
|
856
|
+
return this._filesInProgress.length;
|
522
857
|
},
|
523
858
|
uploadStoredFiles: function(){
|
524
859
|
"use strict";
|
525
|
-
|
526
|
-
|
527
|
-
|
860
|
+
var idToUpload;
|
861
|
+
|
862
|
+
while(this._storedIds.length) {
|
863
|
+
idToUpload = this._storedIds.shift();
|
864
|
+
this._filesInProgress.push(idToUpload);
|
865
|
+
this._handler.upload(idToUpload);
|
528
866
|
}
|
529
867
|
},
|
530
868
|
clearStoredFiles: function(){
|
531
|
-
this.
|
869
|
+
this._storedIds = [];
|
532
870
|
},
|
533
871
|
retry: function(id) {
|
534
872
|
if (this._onBeforeManualRetry(id)) {
|
@@ -539,79 +877,179 @@ qq.FineUploaderBasic.prototype = {
|
|
539
877
|
return false;
|
540
878
|
}
|
541
879
|
},
|
542
|
-
cancel: function(
|
543
|
-
this._handler.cancel(
|
880
|
+
cancel: function(id) {
|
881
|
+
this._handler.cancel(id);
|
882
|
+
},
|
883
|
+
cancelAll: function() {
|
884
|
+
var storedIdsCopy = [],
|
885
|
+
self = this;
|
886
|
+
|
887
|
+
qq.extend(storedIdsCopy, this._storedIds);
|
888
|
+
qq.each(storedIdsCopy, function(idx, storedFileId) {
|
889
|
+
self.cancel(storedFileId);
|
890
|
+
});
|
891
|
+
|
892
|
+
this._handler.cancelAll();
|
544
893
|
},
|
545
894
|
reset: function() {
|
546
895
|
this.log("Resetting uploader...");
|
547
896
|
this._handler.reset();
|
548
|
-
this._filesInProgress =
|
549
|
-
this.
|
897
|
+
this._filesInProgress = [];
|
898
|
+
this._storedIds = [];
|
550
899
|
this._autoRetries = [];
|
551
900
|
this._retryTimeouts = [];
|
552
901
|
this._preventRetries = [];
|
553
902
|
this._button.reset();
|
903
|
+
this._paramsStore.reset();
|
904
|
+
this._endpointStore.reset();
|
905
|
+
},
|
906
|
+
addFiles: function(filesBlobDataOrInputs) {
|
907
|
+
var self = this,
|
908
|
+
verifiedFilesOrInputs = [],
|
909
|
+
index, fileOrInput;
|
910
|
+
|
911
|
+
if (filesBlobDataOrInputs) {
|
912
|
+
if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
|
913
|
+
filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
|
914
|
+
}
|
915
|
+
|
916
|
+
for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
|
917
|
+
fileOrInput = filesBlobDataOrInputs[index];
|
918
|
+
|
919
|
+
if (qq.isFileOrInput(fileOrInput)) {
|
920
|
+
verifiedFilesOrInputs.push(fileOrInput);
|
921
|
+
}
|
922
|
+
else {
|
923
|
+
self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
|
924
|
+
}
|
925
|
+
}
|
926
|
+
|
927
|
+
this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
|
928
|
+
this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
|
929
|
+
}
|
930
|
+
},
|
931
|
+
addBlobs: function(blobDataOrArray) {
|
932
|
+
if (blobDataOrArray) {
|
933
|
+
var blobDataArray = [].concat(blobDataOrArray),
|
934
|
+
verifiedBlobDataList = [],
|
935
|
+
self = this;
|
936
|
+
|
937
|
+
qq.each(blobDataArray, function(idx, blobData) {
|
938
|
+
if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
|
939
|
+
verifiedBlobDataList.push({
|
940
|
+
blob: blobData,
|
941
|
+
name: self._options.blobs.defaultName
|
942
|
+
});
|
943
|
+
}
|
944
|
+
else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
|
945
|
+
verifiedBlobDataList.push(blobData);
|
946
|
+
}
|
947
|
+
else {
|
948
|
+
self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
|
949
|
+
}
|
950
|
+
});
|
951
|
+
|
952
|
+
this._uploadFileOrBlobDataList(verifiedBlobDataList);
|
953
|
+
}
|
954
|
+
else {
|
955
|
+
this.log("undefined or non-array parameter passed into addBlobs", "error");
|
956
|
+
}
|
957
|
+
},
|
958
|
+
getUuid: function(id) {
|
959
|
+
return this._handler.getUuid(id);
|
960
|
+
},
|
961
|
+
getResumableFilesData: function() {
|
962
|
+
return this._handler.getResumableFilesData();
|
963
|
+
},
|
964
|
+
getSize: function(id) {
|
965
|
+
return this._handler.getSize(id);
|
966
|
+
},
|
967
|
+
getFile: function(fileOrBlobId) {
|
968
|
+
return this._handler.getFile(fileOrBlobId);
|
969
|
+
},
|
970
|
+
deleteFile: function(id) {
|
971
|
+
this._onSubmitDelete(id);
|
972
|
+
},
|
973
|
+
setDeleteFileEndpoint: function(endpoint, id) {
|
974
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
975
|
+
if (id == null) {
|
976
|
+
this._options.deleteFile.endpoint = endpoint;
|
977
|
+
}
|
978
|
+
else {
|
979
|
+
this._deleteFileEndpointStore.setEndpoint(endpoint, id);
|
980
|
+
}
|
554
981
|
},
|
555
982
|
_createUploadButton: function(element){
|
556
983
|
var self = this;
|
557
984
|
|
558
985
|
var button = new qq.UploadButton({
|
559
986
|
element: element,
|
560
|
-
multiple: this._options.multiple && qq.
|
987
|
+
multiple: this._options.multiple && qq.isXhrUploadSupported(),
|
561
988
|
acceptFiles: this._options.validation.acceptFiles,
|
562
989
|
onChange: function(input){
|
563
990
|
self._onInputChange(input);
|
564
|
-
}
|
991
|
+
},
|
992
|
+
hoverClass: this._options.classes.buttonHover,
|
993
|
+
focusClass: this._options.classes.buttonFocus
|
565
994
|
});
|
566
995
|
|
567
|
-
this.addDisposer(function() { button.dispose(); });
|
996
|
+
this._disposeSupport.addDisposer(function() { button.dispose(); });
|
568
997
|
return button;
|
569
998
|
},
|
570
999
|
_createUploadHandler: function(){
|
571
|
-
var self = this
|
572
|
-
handlerClass;
|
573
|
-
|
574
|
-
if(qq.UploadHandlerXhr.isSupported()){
|
575
|
-
handlerClass = 'UploadHandlerXhr';
|
576
|
-
} else {
|
577
|
-
handlerClass = 'UploadHandlerForm';
|
578
|
-
}
|
1000
|
+
var self = this;
|
579
1001
|
|
580
|
-
|
1002
|
+
return new qq.UploadHandler({
|
581
1003
|
debug: this._options.debug,
|
582
|
-
endpoint: this._options.request.endpoint,
|
583
1004
|
forceMultipart: this._options.request.forceMultipart,
|
584
1005
|
maxConnections: this._options.maxConnections,
|
585
1006
|
customHeaders: this._options.request.customHeaders,
|
586
1007
|
inputName: this._options.request.inputName,
|
1008
|
+
uuidParamName: this._options.request.uuidName,
|
1009
|
+
totalFileSizeParamName: this._options.request.totalFileSizeName,
|
1010
|
+
cors: this._options.cors,
|
587
1011
|
demoMode: this._options.demoMode,
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
1012
|
+
paramsInBody: this._options.request.paramsInBody,
|
1013
|
+
paramsStore: this._paramsStore,
|
1014
|
+
endpointStore: this._endpointStore,
|
1015
|
+
chunking: this._options.chunking,
|
1016
|
+
resume: this._options.resume,
|
1017
|
+
blobs: this._options.blobs,
|
1018
|
+
log: function(str, level) {
|
1019
|
+
self.log(str, level);
|
1020
|
+
},
|
1021
|
+
onProgress: function(id, name, loaded, total){
|
1022
|
+
self._onProgress(id, name, loaded, total);
|
1023
|
+
self._options.callbacks.onProgress(id, name, loaded, total);
|
592
1024
|
},
|
593
|
-
onComplete: function(id,
|
594
|
-
self._onComplete(id,
|
595
|
-
self._options.callbacks.onComplete(id,
|
1025
|
+
onComplete: function(id, name, result, xhr){
|
1026
|
+
self._onComplete(id, name, result, xhr);
|
1027
|
+
self._options.callbacks.onComplete(id, name, result);
|
596
1028
|
},
|
597
|
-
onCancel: function(id,
|
598
|
-
self._onCancel(id,
|
599
|
-
self._options.callbacks.onCancel(id,
|
1029
|
+
onCancel: function(id, name){
|
1030
|
+
self._onCancel(id, name);
|
1031
|
+
self._options.callbacks.onCancel(id, name);
|
600
1032
|
},
|
601
|
-
onUpload: function(id,
|
602
|
-
self._onUpload(id,
|
603
|
-
self._options.callbacks.onUpload(id,
|
1033
|
+
onUpload: function(id, name){
|
1034
|
+
self._onUpload(id, name);
|
1035
|
+
self._options.callbacks.onUpload(id, name);
|
604
1036
|
},
|
605
|
-
|
1037
|
+
onUploadChunk: function(id, name, chunkData){
|
1038
|
+
self._options.callbacks.onUploadChunk(id, name, chunkData);
|
1039
|
+
},
|
1040
|
+
onResume: function(id, name, chunkData) {
|
1041
|
+
return self._options.callbacks.onResume(id, name, chunkData);
|
1042
|
+
},
|
1043
|
+
onAutoRetry: function(id, name, responseJSON, xhr) {
|
606
1044
|
self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
|
607
1045
|
|
608
|
-
if (self._shouldAutoRetry(id,
|
609
|
-
self._maybeParseAndSendUploadError(id,
|
610
|
-
self._options.callbacks.onAutoRetry(id,
|
611
|
-
self._onBeforeAutoRetry(id,
|
1046
|
+
if (self._shouldAutoRetry(id, name, responseJSON)) {
|
1047
|
+
self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
|
1048
|
+
self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
|
1049
|
+
self._onBeforeAutoRetry(id, name);
|
612
1050
|
|
613
1051
|
self._retryTimeouts[id] = setTimeout(function() {
|
614
|
-
self._onAutoRetry(id,
|
1052
|
+
self._onAutoRetry(id, name, responseJSON)
|
615
1053
|
}, self._options.retry.autoAttemptDelay * 1000);
|
616
1054
|
|
617
1055
|
return true;
|
@@ -621,14 +1059,36 @@ qq.FineUploaderBasic.prototype = {
|
|
621
1059
|
}
|
622
1060
|
}
|
623
1061
|
});
|
1062
|
+
},
|
1063
|
+
_createDeleteHandler: function() {
|
1064
|
+
var self = this;
|
624
1065
|
|
625
|
-
return
|
1066
|
+
return new qq.DeleteFileAjaxRequestor({
|
1067
|
+
maxConnections: this._options.maxConnections,
|
1068
|
+
customHeaders: this._options.deleteFile.customHeaders,
|
1069
|
+
paramsStore: this._deleteFileParamsStore,
|
1070
|
+
endpointStore: this._deleteFileEndpointStore,
|
1071
|
+
demoMode: this._options.demoMode,
|
1072
|
+
cors: this._options.cors,
|
1073
|
+
log: function(str, level) {
|
1074
|
+
self.log(str, level);
|
1075
|
+
},
|
1076
|
+
onDelete: function(id) {
|
1077
|
+
self._onDelete(id);
|
1078
|
+
self._options.callbacks.onDelete(id);
|
1079
|
+
},
|
1080
|
+
onDeleteComplete: function(id, xhr, isError) {
|
1081
|
+
self._onDeleteComplete(id, xhr, isError);
|
1082
|
+
self._options.callbacks.onDeleteComplete(id, xhr, isError);
|
1083
|
+
}
|
1084
|
+
|
1085
|
+
});
|
626
1086
|
},
|
627
1087
|
_preventLeaveInProgress: function(){
|
628
1088
|
var self = this;
|
629
1089
|
|
630
|
-
this.
|
631
|
-
if (!self._filesInProgress){return;}
|
1090
|
+
this._disposeSupport.attach(window, 'beforeunload', function(e){
|
1091
|
+
if (!self._filesInProgress.length){return;}
|
632
1092
|
|
633
1093
|
var e = e || window.event;
|
634
1094
|
// for ie, ff
|
@@ -637,49 +1097,82 @@ qq.FineUploaderBasic.prototype = {
|
|
637
1097
|
return self._options.messages.onLeave;
|
638
1098
|
});
|
639
1099
|
},
|
640
|
-
_onSubmit: function(id,
|
1100
|
+
_onSubmit: function(id, name){
|
641
1101
|
if (this._options.autoUpload) {
|
642
|
-
this._filesInProgress
|
1102
|
+
this._filesInProgress.push(id);
|
643
1103
|
}
|
644
1104
|
},
|
645
|
-
_onProgress: function(id,
|
1105
|
+
_onProgress: function(id, name, loaded, total){
|
646
1106
|
},
|
647
|
-
_onComplete: function(id,
|
648
|
-
this.
|
649
|
-
this._maybeParseAndSendUploadError(id,
|
1107
|
+
_onComplete: function(id, name, result, xhr){
|
1108
|
+
this._removeFromFilesInProgress(id);
|
1109
|
+
this._maybeParseAndSendUploadError(id, name, result, xhr);
|
650
1110
|
},
|
651
|
-
_onCancel: function(id,
|
1111
|
+
_onCancel: function(id, name){
|
1112
|
+
this._removeFromFilesInProgress(id);
|
1113
|
+
|
652
1114
|
clearTimeout(this._retryTimeouts[id]);
|
653
1115
|
|
654
|
-
var
|
655
|
-
if (this._options.autoUpload
|
656
|
-
this.
|
1116
|
+
var storedItemIndex = qq.indexOf(this._storedIds, id);
|
1117
|
+
if (!this._options.autoUpload && storedItemIndex >= 0) {
|
1118
|
+
this._storedIds.splice(storedItemIndex, 1);
|
1119
|
+
}
|
1120
|
+
},
|
1121
|
+
_isDeletePossible: function() {
|
1122
|
+
return (this._options.deleteFile.enabled &&
|
1123
|
+
(!this._options.cors.expected ||
|
1124
|
+
(this._options.cors.expected && (qq.ie10() || !qq.ie()))
|
1125
|
+
)
|
1126
|
+
);
|
1127
|
+
},
|
1128
|
+
_onSubmitDelete: function(id) {
|
1129
|
+
if (this._isDeletePossible()) {
|
1130
|
+
if (this._options.callbacks.onSubmitDelete(id)) {
|
1131
|
+
this._deleteHandler.sendDelete(id, this.getUuid(id));
|
1132
|
+
}
|
1133
|
+
}
|
1134
|
+
else {
|
1135
|
+
this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
|
1136
|
+
"due to CORS on a user agent that does not support pre-flighting.", "warn");
|
1137
|
+
return false;
|
1138
|
+
}
|
1139
|
+
},
|
1140
|
+
_onDelete: function(fileId) {},
|
1141
|
+
_onDeleteComplete: function(id, xhr, isError) {
|
1142
|
+
var name = this._handler.getName(id);
|
1143
|
+
|
1144
|
+
if (isError) {
|
1145
|
+
this.log("Delete request for '" + name + "' has failed.", "error");
|
1146
|
+
this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status);
|
657
1147
|
}
|
658
|
-
else
|
659
|
-
this.
|
1148
|
+
else {
|
1149
|
+
this.log("Delete request for '" + name + "' has succeeded.");
|
660
1150
|
}
|
661
1151
|
},
|
662
|
-
|
1152
|
+
_removeFromFilesInProgress: function(id) {
|
1153
|
+
var index = qq.indexOf(this._filesInProgress, id);
|
1154
|
+
if (index >= 0) {
|
1155
|
+
this._filesInProgress.splice(index, 1);
|
1156
|
+
}
|
663
1157
|
},
|
1158
|
+
_onUpload: function(id, name){},
|
664
1159
|
_onInputChange: function(input){
|
665
|
-
if (
|
666
|
-
this.
|
1160
|
+
if (qq.isXhrUploadSupported()){
|
1161
|
+
this.addFiles(input.files);
|
667
1162
|
} else {
|
668
|
-
|
669
|
-
this._uploadFile(input);
|
670
|
-
}
|
1163
|
+
this.addFiles(input);
|
671
1164
|
}
|
672
1165
|
this._button.reset();
|
673
1166
|
},
|
674
|
-
_onBeforeAutoRetry: function(id,
|
675
|
-
this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " +
|
1167
|
+
_onBeforeAutoRetry: function(id, name) {
|
1168
|
+
this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
|
676
1169
|
},
|
677
|
-
_onAutoRetry: function(id,
|
678
|
-
this.log("Retrying " +
|
1170
|
+
_onAutoRetry: function(id, name, responseJSON) {
|
1171
|
+
this.log("Retrying " + name + "...");
|
679
1172
|
this._autoRetries[id]++;
|
680
1173
|
this._handler.retry(id);
|
681
1174
|
},
|
682
|
-
_shouldAutoRetry: function(id,
|
1175
|
+
_shouldAutoRetry: function(id, name, responseJSON) {
|
683
1176
|
if (!this._preventRetries[id] && this._options.retry.enableAuto) {
|
684
1177
|
if (this._autoRetries[id] === undefined) {
|
685
1178
|
this._autoRetries[id] = 0;
|
@@ -704,7 +1197,7 @@ qq.FineUploaderBasic.prototype = {
|
|
704
1197
|
}
|
705
1198
|
|
706
1199
|
this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
|
707
|
-
this._filesInProgress
|
1200
|
+
this._filesInProgress.push(id);
|
708
1201
|
return true;
|
709
1202
|
}
|
710
1203
|
else {
|
@@ -712,31 +1205,29 @@ qq.FineUploaderBasic.prototype = {
|
|
712
1205
|
return false;
|
713
1206
|
}
|
714
1207
|
},
|
715
|
-
_maybeParseAndSendUploadError: function(id,
|
1208
|
+
_maybeParseAndSendUploadError: function(id, name, response, xhr) {
|
716
1209
|
//assuming no one will actually set the response code to something other than 200 and still set 'success' to true
|
717
1210
|
if (!response.success){
|
718
1211
|
if (xhr && xhr.status !== 200 && !response.error) {
|
719
|
-
this._options.callbacks.onError(id,
|
1212
|
+
this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status);
|
720
1213
|
}
|
721
1214
|
else {
|
722
1215
|
var errorReason = response.error ? response.error : "Upload failure reason unknown";
|
723
|
-
this._options.callbacks.onError(id,
|
1216
|
+
this._options.callbacks.onError(id, name, errorReason);
|
724
1217
|
}
|
725
1218
|
}
|
726
1219
|
},
|
727
|
-
|
1220
|
+
_uploadFileOrBlobDataList: function(fileOrBlobDataList){
|
728
1221
|
var validationDescriptors, index, batchInvalid;
|
729
1222
|
|
730
|
-
validationDescriptors = this._getValidationDescriptors(
|
731
|
-
|
732
|
-
batchInvalid = this._options.callbacks.onValidate(validationDescriptors) === false;
|
733
|
-
}
|
1223
|
+
validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList);
|
1224
|
+
batchInvalid = this._options.callbacks.onValidateBatch(validationDescriptors) === false;
|
734
1225
|
|
735
1226
|
if (!batchInvalid) {
|
736
|
-
if (
|
737
|
-
for (index = 0; index <
|
738
|
-
if (this.
|
739
|
-
this.
|
1227
|
+
if (fileOrBlobDataList.length > 0) {
|
1228
|
+
for (index = 0; index < fileOrBlobDataList.length; index++){
|
1229
|
+
if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
|
1230
|
+
this._upload(fileOrBlobDataList[index]);
|
740
1231
|
} else {
|
741
1232
|
if (this._options.validation.stopOnFirstInvalidFile){
|
742
1233
|
return;
|
@@ -749,35 +1240,35 @@ qq.FineUploaderBasic.prototype = {
|
|
749
1240
|
}
|
750
1241
|
}
|
751
1242
|
},
|
752
|
-
|
753
|
-
var id = this._handler.add(
|
754
|
-
var
|
1243
|
+
_upload: function(blobOrFileContainer){
|
1244
|
+
var id = this._handler.add(blobOrFileContainer);
|
1245
|
+
var name = this._handler.getName(id);
|
755
1246
|
|
756
|
-
if (this._options.callbacks.onSubmit(id,
|
757
|
-
this._onSubmit(id,
|
1247
|
+
if (this._options.callbacks.onSubmit(id, name) !== false){
|
1248
|
+
this._onSubmit(id, name);
|
758
1249
|
if (this._options.autoUpload) {
|
759
|
-
this._handler.upload(id
|
1250
|
+
this._handler.upload(id);
|
760
1251
|
}
|
761
1252
|
else {
|
762
|
-
this.
|
1253
|
+
this._storeForLater(id);
|
763
1254
|
}
|
764
1255
|
}
|
765
1256
|
},
|
766
|
-
|
767
|
-
this.
|
1257
|
+
_storeForLater: function(id) {
|
1258
|
+
this._storedIds.push(id);
|
768
1259
|
},
|
769
|
-
|
1260
|
+
_validateFileOrBlobData: function(fileOrBlobData){
|
770
1261
|
var validationDescriptor, name, size;
|
771
1262
|
|
772
|
-
validationDescriptor = this._getValidationDescriptor(
|
1263
|
+
validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
|
773
1264
|
name = validationDescriptor.name;
|
774
1265
|
size = validationDescriptor.size;
|
775
1266
|
|
776
|
-
if (this._options.callbacks.onValidate(
|
1267
|
+
if (this._options.callbacks.onValidate(validationDescriptor) === false) {
|
777
1268
|
return false;
|
778
1269
|
}
|
779
1270
|
|
780
|
-
if (!this._isAllowedExtension(name)){
|
1271
|
+
if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
|
781
1272
|
this._error('typeError', name);
|
782
1273
|
return false;
|
783
1274
|
|
@@ -799,49 +1290,49 @@ qq.FineUploaderBasic.prototype = {
|
|
799
1290
|
|
800
1291
|
return true;
|
801
1292
|
},
|
802
|
-
_error: function(code,
|
1293
|
+
_error: function(code, name){
|
803
1294
|
var message = this._options.messages[code];
|
804
1295
|
function r(name, replacement){ message = message.replace(name, replacement); }
|
805
1296
|
|
806
|
-
var extensions = this._options.validation.allowedExtensions.join(', ');
|
1297
|
+
var extensions = this._options.validation.allowedExtensions.join(', ').toLowerCase();
|
807
1298
|
|
808
|
-
r('{file}', this.
|
1299
|
+
r('{file}', this._options.formatFileName(name));
|
809
1300
|
r('{extensions}', extensions);
|
810
1301
|
r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
|
811
1302
|
r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
|
812
1303
|
|
813
|
-
this._options.callbacks.onError(null,
|
1304
|
+
this._options.callbacks.onError(null, name, message);
|
814
1305
|
|
815
1306
|
return message;
|
816
1307
|
},
|
817
|
-
_formatFileName: function(name){
|
818
|
-
if (name.length > 33){
|
819
|
-
name = name.slice(0, 19) + '...' + name.slice(-13);
|
820
|
-
}
|
821
|
-
return name;
|
822
|
-
},
|
823
1308
|
_isAllowedExtension: function(fileName){
|
824
|
-
var
|
825
|
-
|
826
|
-
: '';
|
827
|
-
var allowed = this._options.validation.allowedExtensions;
|
828
|
-
|
829
|
-
if (!allowed.length){return true;}
|
1309
|
+
var allowed = this._options.validation.allowedExtensions,
|
1310
|
+
valid = false;
|
830
1311
|
|
831
|
-
|
832
|
-
|
1312
|
+
if (!allowed.length) {
|
1313
|
+
return true;
|
833
1314
|
}
|
834
1315
|
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
1316
|
+
qq.each(allowed, function(idx, allowedExt) {
|
1317
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
1318
|
+
var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
|
1319
|
+
|
1320
|
+
if (fileName.match(extRegex) != null) {
|
1321
|
+
valid = true;
|
1322
|
+
return false;
|
1323
|
+
}
|
1324
|
+
});
|
1325
|
+
|
1326
|
+
return valid;
|
1327
|
+
},
|
1328
|
+
_formatSize: function(bytes){
|
1329
|
+
var i = -1;
|
1330
|
+
do {
|
840
1331
|
bytes = bytes / 1024;
|
841
1332
|
i++;
|
842
1333
|
} while (bytes > 99);
|
843
1334
|
|
844
|
-
return Math.max(bytes, 0.1).toFixed(1) + [
|
1335
|
+
return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
|
845
1336
|
},
|
846
1337
|
_wrapCallbacks: function() {
|
847
1338
|
var self, safeCallback;
|
@@ -853,49 +1344,61 @@ qq.FineUploaderBasic.prototype = {
|
|
853
1344
|
return callback.apply(self, args);
|
854
1345
|
}
|
855
1346
|
catch (exception) {
|
856
|
-
self.log("Caught exception in '" + name + "' callback - " + exception, 'error');
|
1347
|
+
self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
|
857
1348
|
}
|
858
1349
|
}
|
859
1350
|
|
860
1351
|
for (var prop in this._options.callbacks) {
|
861
1352
|
(function() {
|
862
|
-
var
|
863
|
-
|
864
|
-
|
1353
|
+
var callbackName, callbackFunc;
|
1354
|
+
callbackName = prop;
|
1355
|
+
callbackFunc = self._options.callbacks[callbackName];
|
1356
|
+
self._options.callbacks[callbackName] = function() {
|
1357
|
+
return safeCallback(callbackName, callbackFunc, arguments);
|
865
1358
|
}
|
866
1359
|
}());
|
867
1360
|
}
|
868
1361
|
},
|
869
|
-
|
1362
|
+
_parseFileOrBlobDataName: function(fileOrBlobData) {
|
870
1363
|
var name;
|
871
1364
|
|
872
|
-
if (
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
1365
|
+
if (qq.isFileOrInput(fileOrBlobData)) {
|
1366
|
+
if (fileOrBlobData.value) {
|
1367
|
+
// it is a file input
|
1368
|
+
// get input value and remove path to normalize
|
1369
|
+
name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
|
1370
|
+
} else {
|
1371
|
+
// fix missing properties in Safari 4 and firefox 11.0a2
|
1372
|
+
name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
|
1373
|
+
}
|
1374
|
+
}
|
1375
|
+
else {
|
1376
|
+
name = fileOrBlobData.name;
|
879
1377
|
}
|
880
1378
|
|
881
1379
|
return name;
|
882
1380
|
},
|
883
|
-
|
1381
|
+
_parseFileOrBlobDataSize: function(fileOrBlobData) {
|
884
1382
|
var size;
|
885
1383
|
|
886
|
-
if (
|
887
|
-
|
888
|
-
|
1384
|
+
if (qq.isFileOrInput(fileOrBlobData)) {
|
1385
|
+
if (!fileOrBlobData.value){
|
1386
|
+
// fix missing properties in Safari 4 and firefox 11.0a2
|
1387
|
+
size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
|
1388
|
+
}
|
1389
|
+
}
|
1390
|
+
else {
|
1391
|
+
size = fileOrBlobData.blob.size;
|
889
1392
|
}
|
890
1393
|
|
891
1394
|
return size;
|
892
1395
|
},
|
893
|
-
_getValidationDescriptor: function(
|
1396
|
+
_getValidationDescriptor: function(fileOrBlobData) {
|
894
1397
|
var name, size, fileDescriptor;
|
895
1398
|
|
896
1399
|
fileDescriptor = {};
|
897
|
-
name = this.
|
898
|
-
size = this.
|
1400
|
+
name = this._parseFileOrBlobDataName(fileOrBlobData);
|
1401
|
+
size = this._parseFileOrBlobDataSize(fileOrBlobData);
|
899
1402
|
|
900
1403
|
fileDescriptor.name = name;
|
901
1404
|
if (size) {
|
@@ -905,16 +1408,433 @@ qq.FineUploaderBasic.prototype = {
|
|
905
1408
|
return fileDescriptor;
|
906
1409
|
},
|
907
1410
|
_getValidationDescriptors: function(files) {
|
908
|
-
var
|
1411
|
+
var self = this,
|
1412
|
+
fileDescriptors = [];
|
1413
|
+
|
1414
|
+
qq.each(files, function(idx, file) {
|
1415
|
+
fileDescriptors.push(self._getValidationDescriptor(file));
|
1416
|
+
});
|
1417
|
+
|
1418
|
+
return fileDescriptors;
|
1419
|
+
},
|
1420
|
+
_createParamsStore: function(type) {
|
1421
|
+
var paramsStore = {},
|
1422
|
+
self = this;
|
1423
|
+
|
1424
|
+
return {
|
1425
|
+
setParams: function(params, id) {
|
1426
|
+
var paramsCopy = {};
|
1427
|
+
qq.extend(paramsCopy, params);
|
1428
|
+
paramsStore[id] = paramsCopy;
|
1429
|
+
},
|
1430
|
+
|
1431
|
+
getParams: function(id) {
|
1432
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
1433
|
+
var paramsCopy = {};
|
1434
|
+
|
1435
|
+
if (id != null && paramsStore[id]) {
|
1436
|
+
qq.extend(paramsCopy, paramsStore[id]);
|
1437
|
+
}
|
1438
|
+
else {
|
1439
|
+
qq.extend(paramsCopy, self._options[type].params);
|
1440
|
+
}
|
1441
|
+
|
1442
|
+
return paramsCopy;
|
1443
|
+
},
|
1444
|
+
|
1445
|
+
remove: function(fileId) {
|
1446
|
+
return delete paramsStore[fileId];
|
1447
|
+
},
|
1448
|
+
|
1449
|
+
reset: function() {
|
1450
|
+
paramsStore = {};
|
1451
|
+
}
|
1452
|
+
};
|
1453
|
+
},
|
1454
|
+
_createEndpointStore: function(type) {
|
1455
|
+
var endpointStore = {},
|
1456
|
+
self = this;
|
1457
|
+
|
1458
|
+
return {
|
1459
|
+
setEndpoint: function(endpoint, id) {
|
1460
|
+
endpointStore[id] = endpoint;
|
1461
|
+
},
|
1462
|
+
|
1463
|
+
getEndpoint: function(id) {
|
1464
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
1465
|
+
if (id != null && endpointStore[id]) {
|
1466
|
+
return endpointStore[id];
|
1467
|
+
}
|
1468
|
+
|
1469
|
+
return self._options[type].endpoint;
|
1470
|
+
},
|
1471
|
+
|
1472
|
+
remove: function(fileId) {
|
1473
|
+
return delete endpointStore[fileId];
|
1474
|
+
},
|
909
1475
|
|
910
|
-
|
1476
|
+
reset: function() {
|
1477
|
+
endpointStore = {};
|
1478
|
+
}
|
1479
|
+
};
|
1480
|
+
}
|
1481
|
+
};
|
1482
|
+
/*globals qq, document*/
|
1483
|
+
qq.DragAndDrop = function(o) {
|
1484
|
+
"use strict";
|
1485
|
+
|
1486
|
+
var options, dz, dirPending,
|
1487
|
+
droppedFiles = [],
|
1488
|
+
droppedEntriesCount = 0,
|
1489
|
+
droppedEntriesParsedCount = 0,
|
1490
|
+
disposeSupport = new qq.DisposeSupport();
|
911
1491
|
|
912
|
-
|
913
|
-
|
1492
|
+
options = {
|
1493
|
+
dropArea: null,
|
1494
|
+
extraDropzones: [],
|
1495
|
+
hideDropzones: true,
|
1496
|
+
multiple: true,
|
1497
|
+
classes: {
|
1498
|
+
dropActive: null
|
1499
|
+
},
|
1500
|
+
callbacks: {
|
1501
|
+
dropProcessing: function(isProcessing, files) {},
|
1502
|
+
error: function(code, filename) {},
|
1503
|
+
log: function(message, level) {}
|
914
1504
|
}
|
1505
|
+
};
|
915
1506
|
|
916
|
-
|
1507
|
+
qq.extend(options, o);
|
1508
|
+
|
1509
|
+
function maybeUploadDroppedFiles() {
|
1510
|
+
if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
|
1511
|
+
options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
|
1512
|
+
dz.dropDisabled(false);
|
1513
|
+
options.callbacks.dropProcessing(false, droppedFiles);
|
1514
|
+
}
|
1515
|
+
}
|
1516
|
+
function addDroppedFile(file) {
|
1517
|
+
droppedFiles.push(file);
|
1518
|
+
droppedEntriesParsedCount+=1;
|
1519
|
+
maybeUploadDroppedFiles();
|
1520
|
+
}
|
1521
|
+
|
1522
|
+
function traverseFileTree(entry) {
|
1523
|
+
var dirReader, i;
|
1524
|
+
|
1525
|
+
droppedEntriesCount+=1;
|
1526
|
+
|
1527
|
+
if (entry.isFile) {
|
1528
|
+
entry.file(function(file) {
|
1529
|
+
addDroppedFile(file);
|
1530
|
+
});
|
1531
|
+
}
|
1532
|
+
else if (entry.isDirectory) {
|
1533
|
+
dirPending = true;
|
1534
|
+
dirReader = entry.createReader();
|
1535
|
+
dirReader.readEntries(function(entries) {
|
1536
|
+
droppedEntriesParsedCount+=1;
|
1537
|
+
for (i = 0; i < entries.length; i+=1) {
|
1538
|
+
traverseFileTree(entries[i]);
|
1539
|
+
}
|
1540
|
+
|
1541
|
+
dirPending = false;
|
1542
|
+
|
1543
|
+
if (!entries.length) {
|
1544
|
+
maybeUploadDroppedFiles();
|
1545
|
+
}
|
1546
|
+
});
|
1547
|
+
}
|
1548
|
+
}
|
1549
|
+
|
1550
|
+
function handleDataTransfer(dataTransfer) {
|
1551
|
+
var i, items, entry;
|
1552
|
+
|
1553
|
+
options.callbacks.dropProcessing(true);
|
1554
|
+
dz.dropDisabled(true);
|
1555
|
+
|
1556
|
+
if (dataTransfer.files.length > 1 && !options.multiple) {
|
1557
|
+
options.callbacks.dropProcessing(false);
|
1558
|
+
options.callbacks.error('tooManyFilesError', "");
|
1559
|
+
dz.dropDisabled(false);
|
1560
|
+
}
|
1561
|
+
else {
|
1562
|
+
droppedFiles = [];
|
1563
|
+
droppedEntriesCount = 0;
|
1564
|
+
droppedEntriesParsedCount = 0;
|
1565
|
+
|
1566
|
+
if (qq.isFolderDropSupported(dataTransfer)) {
|
1567
|
+
items = dataTransfer.items;
|
1568
|
+
|
1569
|
+
for (i = 0; i < items.length; i+=1) {
|
1570
|
+
entry = items[i].webkitGetAsEntry();
|
1571
|
+
if (entry) {
|
1572
|
+
//due to a bug in Chrome's File System API impl - #149735
|
1573
|
+
if (entry.isFile) {
|
1574
|
+
droppedFiles.push(items[i].getAsFile());
|
1575
|
+
if (i === items.length-1) {
|
1576
|
+
maybeUploadDroppedFiles();
|
1577
|
+
}
|
1578
|
+
}
|
1579
|
+
|
1580
|
+
else {
|
1581
|
+
traverseFileTree(entry);
|
1582
|
+
}
|
1583
|
+
}
|
1584
|
+
}
|
1585
|
+
}
|
1586
|
+
else {
|
1587
|
+
options.callbacks.dropProcessing(false, dataTransfer.files);
|
1588
|
+
dz.dropDisabled(false);
|
1589
|
+
}
|
1590
|
+
}
|
1591
|
+
}
|
1592
|
+
|
1593
|
+
function setupDropzone(dropArea){
|
1594
|
+
dz = new qq.UploadDropZone({
|
1595
|
+
element: dropArea,
|
1596
|
+
onEnter: function(e){
|
1597
|
+
qq(dropArea).addClass(options.classes.dropActive);
|
1598
|
+
e.stopPropagation();
|
1599
|
+
},
|
1600
|
+
onLeaveNotDescendants: function(e){
|
1601
|
+
qq(dropArea).removeClass(options.classes.dropActive);
|
1602
|
+
},
|
1603
|
+
onDrop: function(e){
|
1604
|
+
if (options.hideDropzones) {
|
1605
|
+
qq(dropArea).hide();
|
1606
|
+
}
|
1607
|
+
qq(dropArea).removeClass(options.classes.dropActive);
|
1608
|
+
|
1609
|
+
handleDataTransfer(e.dataTransfer);
|
1610
|
+
}
|
1611
|
+
});
|
1612
|
+
|
1613
|
+
disposeSupport.addDisposer(function() {
|
1614
|
+
dz.dispose();
|
1615
|
+
});
|
1616
|
+
|
1617
|
+
if (options.hideDropzones) {
|
1618
|
+
qq(dropArea).hide();
|
1619
|
+
}
|
1620
|
+
}
|
1621
|
+
|
1622
|
+
function isFileDrag(dragEvent) {
|
1623
|
+
var fileDrag;
|
1624
|
+
|
1625
|
+
qq.each(dragEvent.dataTransfer.types, function(key, val) {
|
1626
|
+
if (val === 'Files') {
|
1627
|
+
fileDrag = true;
|
1628
|
+
return false;
|
1629
|
+
}
|
1630
|
+
});
|
1631
|
+
|
1632
|
+
return fileDrag;
|
1633
|
+
}
|
1634
|
+
|
1635
|
+
function setupDragDrop(){
|
1636
|
+
if (options.dropArea) {
|
1637
|
+
options.extraDropzones.push(options.dropArea);
|
1638
|
+
}
|
1639
|
+
|
1640
|
+
var i, dropzones = options.extraDropzones;
|
1641
|
+
|
1642
|
+
for (i=0; i < dropzones.length; i+=1){
|
1643
|
+
setupDropzone(dropzones[i]);
|
1644
|
+
}
|
1645
|
+
|
1646
|
+
// IE <= 9 does not support the File API used for drag+drop uploads
|
1647
|
+
if (options.dropArea && (!qq.ie() || qq.ie10())) {
|
1648
|
+
disposeSupport.attach(document, 'dragenter', function(e) {
|
1649
|
+
if (!dz.dropDisabled() && isFileDrag(e)) {
|
1650
|
+
if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
|
1651
|
+
return;
|
1652
|
+
}
|
1653
|
+
|
1654
|
+
options.dropArea.style.display = 'block';
|
1655
|
+
for (i=0; i < dropzones.length; i+=1) {
|
1656
|
+
dropzones[i].style.display = 'block';
|
1657
|
+
}
|
1658
|
+
}
|
1659
|
+
});
|
1660
|
+
}
|
1661
|
+
disposeSupport.attach(document, 'dragleave', function(e){
|
1662
|
+
if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
|
1663
|
+
for (i=0; i < dropzones.length; i+=1) {
|
1664
|
+
qq(dropzones[i]).hide();
|
1665
|
+
}
|
1666
|
+
}
|
1667
|
+
});
|
1668
|
+
disposeSupport.attach(document, 'drop', function(e){
|
1669
|
+
if (options.hideDropzones) {
|
1670
|
+
for (i=0; i < dropzones.length; i+=1) {
|
1671
|
+
qq(dropzones[i]).hide();
|
1672
|
+
}
|
1673
|
+
}
|
1674
|
+
e.preventDefault();
|
1675
|
+
});
|
1676
|
+
}
|
1677
|
+
|
1678
|
+
return {
|
1679
|
+
setup: function() {
|
1680
|
+
setupDragDrop();
|
1681
|
+
},
|
1682
|
+
|
1683
|
+
setupExtraDropzone: function(element) {
|
1684
|
+
options.extraDropzones.push(element);
|
1685
|
+
setupDropzone(element);
|
1686
|
+
},
|
1687
|
+
|
1688
|
+
removeExtraDropzone: function(element) {
|
1689
|
+
var i, dzs = options.extraDropzones;
|
1690
|
+
for(i in dzs) {
|
1691
|
+
if (dzs[i] === element) {
|
1692
|
+
return dzs.splice(i, 1);
|
1693
|
+
}
|
1694
|
+
}
|
1695
|
+
},
|
1696
|
+
|
1697
|
+
dispose: function() {
|
1698
|
+
disposeSupport.dispose();
|
1699
|
+
dz.dispose();
|
1700
|
+
}
|
1701
|
+
};
|
1702
|
+
};
|
1703
|
+
|
1704
|
+
|
1705
|
+
qq.UploadDropZone = function(o){
|
1706
|
+
"use strict";
|
1707
|
+
|
1708
|
+
var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
|
1709
|
+
|
1710
|
+
options = {
|
1711
|
+
element: null,
|
1712
|
+
onEnter: function(e){},
|
1713
|
+
onLeave: function(e){},
|
1714
|
+
// is not fired when leaving element by hovering descendants
|
1715
|
+
onLeaveNotDescendants: function(e){},
|
1716
|
+
onDrop: function(e){}
|
1717
|
+
};
|
1718
|
+
|
1719
|
+
qq.extend(options, o);
|
1720
|
+
element = options.element;
|
1721
|
+
|
1722
|
+
function dragover_should_be_canceled(){
|
1723
|
+
return qq.safari() || (qq.firefox() && qq.windows());
|
1724
|
+
}
|
1725
|
+
|
1726
|
+
function disableDropOutside(e){
|
1727
|
+
// run only once for all instances
|
1728
|
+
if (!dropOutsideDisabled ){
|
1729
|
+
|
1730
|
+
// for these cases we need to catch onDrop to reset dropArea
|
1731
|
+
if (dragover_should_be_canceled){
|
1732
|
+
disposeSupport.attach(document, 'dragover', function(e){
|
1733
|
+
e.preventDefault();
|
1734
|
+
});
|
1735
|
+
} else {
|
1736
|
+
disposeSupport.attach(document, 'dragover', function(e){
|
1737
|
+
if (e.dataTransfer){
|
1738
|
+
e.dataTransfer.dropEffect = 'none';
|
1739
|
+
e.preventDefault();
|
1740
|
+
}
|
1741
|
+
});
|
1742
|
+
}
|
1743
|
+
|
1744
|
+
dropOutsideDisabled = true;
|
1745
|
+
}
|
1746
|
+
}
|
1747
|
+
|
1748
|
+
function isValidFileDrag(e){
|
1749
|
+
// e.dataTransfer currently causing IE errors
|
1750
|
+
// IE9 does NOT support file API, so drag-and-drop is not possible
|
1751
|
+
if (qq.ie() && !qq.ie10()) {
|
1752
|
+
return false;
|
1753
|
+
}
|
1754
|
+
|
1755
|
+
var effectTest, dt = e.dataTransfer,
|
1756
|
+
// do not check dt.types.contains in webkit, because it crashes safari 4
|
1757
|
+
isSafari = qq.safari();
|
1758
|
+
|
1759
|
+
// dt.effectAllowed is none in Safari 5
|
1760
|
+
// dt.types.contains check is for firefox
|
1761
|
+
effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
|
1762
|
+
return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
|
1763
|
+
}
|
1764
|
+
|
1765
|
+
function isOrSetDropDisabled(isDisabled) {
|
1766
|
+
if (isDisabled !== undefined) {
|
1767
|
+
preventDrop = isDisabled;
|
1768
|
+
}
|
1769
|
+
return preventDrop;
|
1770
|
+
}
|
1771
|
+
|
1772
|
+
function attachEvents(){
|
1773
|
+
disposeSupport.attach(element, 'dragover', function(e){
|
1774
|
+
if (!isValidFileDrag(e)) {
|
1775
|
+
return;
|
1776
|
+
}
|
1777
|
+
|
1778
|
+
var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
|
1779
|
+
if (effect === 'move' || effect === 'linkMove'){
|
1780
|
+
e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
|
1781
|
+
} else {
|
1782
|
+
e.dataTransfer.dropEffect = 'copy'; // for Chrome
|
1783
|
+
}
|
1784
|
+
|
1785
|
+
e.stopPropagation();
|
1786
|
+
e.preventDefault();
|
1787
|
+
});
|
1788
|
+
|
1789
|
+
disposeSupport.attach(element, 'dragenter', function(e){
|
1790
|
+
if (!isOrSetDropDisabled()) {
|
1791
|
+
if (!isValidFileDrag(e)) {
|
1792
|
+
return;
|
1793
|
+
}
|
1794
|
+
options.onEnter(e);
|
1795
|
+
}
|
1796
|
+
});
|
1797
|
+
|
1798
|
+
disposeSupport.attach(element, 'dragleave', function(e){
|
1799
|
+
if (!isValidFileDrag(e)) {
|
1800
|
+
return;
|
1801
|
+
}
|
1802
|
+
|
1803
|
+
options.onLeave(e);
|
1804
|
+
|
1805
|
+
var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
|
1806
|
+
// do not fire when moving a mouse over a descendant
|
1807
|
+
if (qq(this).contains(relatedTarget)) {
|
1808
|
+
return;
|
1809
|
+
}
|
1810
|
+
|
1811
|
+
options.onLeaveNotDescendants(e);
|
1812
|
+
});
|
1813
|
+
|
1814
|
+
disposeSupport.attach(element, 'drop', function(e){
|
1815
|
+
if (!isOrSetDropDisabled()) {
|
1816
|
+
if (!isValidFileDrag(e)) {
|
1817
|
+
return;
|
1818
|
+
}
|
1819
|
+
|
1820
|
+
e.preventDefault();
|
1821
|
+
options.onDrop(e);
|
1822
|
+
}
|
1823
|
+
});
|
917
1824
|
}
|
1825
|
+
|
1826
|
+
disableDropOutside();
|
1827
|
+
attachEvents();
|
1828
|
+
|
1829
|
+
return {
|
1830
|
+
dropDisabled: function(isDisabled) {
|
1831
|
+
return isOrSetDropDisabled(isDisabled);
|
1832
|
+
},
|
1833
|
+
|
1834
|
+
dispose: function() {
|
1835
|
+
disposeSupport.dispose();
|
1836
|
+
}
|
1837
|
+
};
|
918
1838
|
};
|
919
1839
|
/**
|
920
1840
|
* Class that creates upload widget with drag-and-drop and file list
|
@@ -937,14 +1857,17 @@ qq.FineUploader = function(o){
|
|
937
1857
|
uploadButton: 'Upload a file',
|
938
1858
|
cancelButton: 'Cancel',
|
939
1859
|
retryButton: 'Retry',
|
1860
|
+
deleteButton: 'Delete',
|
940
1861
|
failUpload: 'Upload failed',
|
941
1862
|
dragZone: 'Drop files here to upload',
|
1863
|
+
dropProcessing: 'Processing dropped files...',
|
942
1864
|
formatProgress: "{percent}% of {total_size}",
|
943
1865
|
waitingForResponse: "Processing..."
|
944
1866
|
},
|
945
1867
|
template: '<div class="qq-uploader">' +
|
946
1868
|
((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '<div class="qq-upload-drop-area"><span>{dragZoneText}</span></div>' : '') +
|
947
1869
|
(!this._options.button ? '<div class="qq-upload-button"><div>{uploadButtonText}</div></div>' : '') +
|
1870
|
+
'<span class="qq-drop-processing"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></span>' +
|
948
1871
|
(!this._options.listElement ? '<ul class="qq-upload-list"></ul>' : '') +
|
949
1872
|
'</div>',
|
950
1873
|
|
@@ -957,10 +1880,10 @@ qq.FineUploader = function(o){
|
|
957
1880
|
'<span class="qq-upload-size"></span>' +
|
958
1881
|
'<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
|
959
1882
|
'<a class="qq-upload-retry" href="#">{retryButtonText}</a>' +
|
1883
|
+
'<a class="qq-upload-delete" href="#">{deleteButtonText}</a>' +
|
960
1884
|
'<span class="qq-upload-status-text">{statusText}</span>' +
|
961
1885
|
'</li>',
|
962
1886
|
classes: {
|
963
|
-
// used to get elements from templates
|
964
1887
|
button: 'qq-upload-button',
|
965
1888
|
drop: 'qq-upload-drop-area',
|
966
1889
|
dropActive: 'qq-upload-drop-area-active',
|
@@ -974,16 +1897,18 @@ qq.FineUploader = function(o){
|
|
974
1897
|
retryable: 'qq-upload-retryable',
|
975
1898
|
size: 'qq-upload-size',
|
976
1899
|
cancel: 'qq-upload-cancel',
|
1900
|
+
deleteButton: 'qq-upload-delete',
|
977
1901
|
retry: 'qq-upload-retry',
|
978
1902
|
statusText: 'qq-upload-status-text',
|
979
1903
|
|
980
|
-
// added to list item <li> when upload completes
|
981
|
-
// used in css to hide progress spinner
|
982
1904
|
success: 'qq-upload-success',
|
983
1905
|
fail: 'qq-upload-fail',
|
984
1906
|
|
985
1907
|
successIcon: null,
|
986
|
-
failIcon: null
|
1908
|
+
failIcon: null,
|
1909
|
+
|
1910
|
+
dropProcessing: 'qq-drop-processing',
|
1911
|
+
dropProcessingSpinner: 'qq-drop-processing-spinner'
|
987
1912
|
},
|
988
1913
|
failedUploadTextDisplay: {
|
989
1914
|
mode: 'default', //default, custom, or none
|
@@ -999,8 +1924,31 @@ qq.FineUploader = function(o){
|
|
999
1924
|
autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
|
1000
1925
|
showButton: false
|
1001
1926
|
},
|
1927
|
+
deleteFile: {
|
1928
|
+
forceConfirm: false,
|
1929
|
+
confirmMessage: "Are you sure you want to delete {filename}?",
|
1930
|
+
deletingStatusText: "Deleting...",
|
1931
|
+
deletingFailedText: "Delete failed"
|
1932
|
+
|
1933
|
+
},
|
1934
|
+
display: {
|
1935
|
+
fileSizeOnSubmit: false
|
1936
|
+
},
|
1002
1937
|
showMessage: function(message){
|
1003
|
-
|
1938
|
+
setTimeout(function() {
|
1939
|
+
alert(message);
|
1940
|
+
}, 0);
|
1941
|
+
},
|
1942
|
+
showConfirm: function(message, okCallback, cancelCallback) {
|
1943
|
+
setTimeout(function() {
|
1944
|
+
var result = confirm(message);
|
1945
|
+
if (result) {
|
1946
|
+
okCallback();
|
1947
|
+
}
|
1948
|
+
else if (cancelCallback) {
|
1949
|
+
cancelCallback();
|
1950
|
+
}
|
1951
|
+
}, 0);
|
1004
1952
|
}
|
1005
1953
|
}, true);
|
1006
1954
|
|
@@ -1012,8 +1960,10 @@ qq.FineUploader = function(o){
|
|
1012
1960
|
// same for the Cancel button and Fail message text
|
1013
1961
|
this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
|
1014
1962
|
this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
|
1963
|
+
this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
|
1015
1964
|
this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
|
1016
1965
|
this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
|
1966
|
+
this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
|
1017
1967
|
this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
|
1018
1968
|
|
1019
1969
|
this._element = this._options.element;
|
@@ -1027,7 +1977,8 @@ qq.FineUploader = function(o){
|
|
1027
1977
|
}
|
1028
1978
|
|
1029
1979
|
this._bindCancelAndRetryEvents();
|
1030
|
-
|
1980
|
+
|
1981
|
+
this._dnd = this._setupDragAndDrop();
|
1031
1982
|
};
|
1032
1983
|
|
1033
1984
|
// inherit from Basic Uploader
|
@@ -1039,11 +1990,10 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1039
1990
|
this._listElement.innerHTML = "";
|
1040
1991
|
},
|
1041
1992
|
addExtraDropzone: function(element){
|
1042
|
-
this.
|
1993
|
+
this._dnd.setupExtraDropzone(element);
|
1043
1994
|
},
|
1044
1995
|
removeExtraDropzone: function(element){
|
1045
|
-
|
1046
|
-
for(var i in dzs) if (dzs[i] === element) return this._options.dragAndDrop.extraDropzones.splice(i,1);
|
1996
|
+
return this._dnd.removeExtraDropzone(element);
|
1047
1997
|
},
|
1048
1998
|
getItemByFileId: function(id){
|
1049
1999
|
var item = this._listElement.firstChild;
|
@@ -1063,119 +2013,93 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1063
2013
|
this._button = this._createUploadButton(this._find(this._element, 'button'));
|
1064
2014
|
}
|
1065
2015
|
this._bindCancelAndRetryEvents();
|
1066
|
-
this.
|
2016
|
+
this._dnd.dispose();
|
2017
|
+
this._dnd = this._setupDragAndDrop();
|
1067
2018
|
},
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
2019
|
+
_removeFileItem: function(fileId) {
|
2020
|
+
var item = this.getItemByFileId(fileId);
|
2021
|
+
qq(item).remove();
|
1071
2022
|
},
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
},
|
1077
|
-
/**
|
1078
|
-
* Gets one of the elements listed in this._options.classes
|
1079
|
-
**/
|
1080
|
-
_find: function(parent, type){
|
1081
|
-
var element = qq(parent).getByClass(this._options.classes[type])[0];
|
1082
|
-
if (!element){
|
1083
|
-
throw new Error('element not found ' + type);
|
1084
|
-
}
|
2023
|
+
_setupDragAndDrop: function() {
|
2024
|
+
var self = this,
|
2025
|
+
dropProcessingEl = this._find(this._element, 'dropProcessing'),
|
2026
|
+
dnd, preventSelectFiles, defaultDropAreaEl;
|
1085
2027
|
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
this._options.dragAndDrop.extraDropzones.push(element);
|
1090
|
-
this._setupDropzone(element);
|
1091
|
-
},
|
1092
|
-
_setupDropzone: function(dropArea){
|
1093
|
-
var self = this;
|
2028
|
+
preventSelectFiles = function(event) {
|
2029
|
+
event.preventDefault();
|
2030
|
+
};
|
1094
2031
|
|
1095
|
-
|
1096
|
-
element
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
2032
|
+
if (!this._options.dragAndDrop.disableDefaultDropzone) {
|
2033
|
+
defaultDropAreaEl = this._find(this._options.element, 'drop');
|
2034
|
+
}
|
2035
|
+
|
2036
|
+
dnd = new qq.DragAndDrop({
|
2037
|
+
dropArea: defaultDropAreaEl,
|
2038
|
+
extraDropzones: this._options.dragAndDrop.extraDropzones,
|
2039
|
+
hideDropzones: this._options.dragAndDrop.hideDropzones,
|
2040
|
+
multiple: this._options.multiple,
|
2041
|
+
classes: {
|
2042
|
+
dropActive: this._options.classes.dropActive
|
1106
2043
|
},
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
}
|
2044
|
+
callbacks: {
|
2045
|
+
dropProcessing: function(isProcessing, files) {
|
2046
|
+
var input = self._button.getInput();
|
1111
2047
|
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
2048
|
+
if (isProcessing) {
|
2049
|
+
qq(dropProcessingEl).css({display: 'block'});
|
2050
|
+
qq(input).attach('click', preventSelectFiles);
|
2051
|
+
}
|
2052
|
+
else {
|
2053
|
+
qq(dropProcessingEl).hide();
|
2054
|
+
qq(input).detach('click', preventSelectFiles);
|
2055
|
+
}
|
2056
|
+
|
2057
|
+
if (files) {
|
2058
|
+
self.addFiles(files);
|
2059
|
+
}
|
2060
|
+
},
|
2061
|
+
error: function(code, filename) {
|
2062
|
+
self._error(code, filename);
|
2063
|
+
},
|
2064
|
+
log: function(message, level) {
|
2065
|
+
self.log(message, level);
|
1118
2066
|
}
|
1119
2067
|
}
|
1120
2068
|
});
|
1121
2069
|
|
1122
|
-
|
2070
|
+
dnd.setup();
|
1123
2071
|
|
1124
|
-
|
1125
|
-
qq(dropArea).hide();
|
1126
|
-
}
|
2072
|
+
return dnd;
|
1127
2073
|
},
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
2074
|
+
_leaving_document_out: function(e){
|
2075
|
+
return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
|
2076
|
+
|| (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
|
2077
|
+
},
|
2078
|
+
_storeForLater: function(id) {
|
2079
|
+
qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
|
2080
|
+
var item = this.getItemByFileId(id);
|
2081
|
+
qq(this._find(item, 'spinner')).hide();
|
2082
|
+
},
|
2083
|
+
/**
|
2084
|
+
* Gets one of the elements listed in this._options.classes
|
2085
|
+
**/
|
2086
|
+
_find: function(parent, type){
|
2087
|
+
var element = qq(parent).getByClass(this._options.classes[type])[0];
|
2088
|
+
if (!element){
|
2089
|
+
throw new Error('element not found ' + type);
|
1142
2090
|
}
|
1143
2091
|
|
1144
|
-
|
1145
|
-
if (!this._options.dragAndDrop.disableDefaultDropzone && (!qq.ie() || qq.ie10())) {
|
1146
|
-
this._attach(document, 'dragenter', function(e){
|
1147
|
-
if (qq(dropArea).hasClass(self._classes.dropDisabled)) return;
|
1148
|
-
|
1149
|
-
dropArea.style.display = 'block';
|
1150
|
-
for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'block'; }
|
1151
|
-
|
1152
|
-
});
|
1153
|
-
}
|
1154
|
-
this._attach(document, 'dragleave', function(e){
|
1155
|
-
if (self._options.dragAndDrop.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
|
1156
|
-
for (i=0; i < dropzones.length; i++) {
|
1157
|
-
qq(dropzones[i]).hide();
|
1158
|
-
}
|
1159
|
-
}
|
1160
|
-
});
|
1161
|
-
qq(document).attach('drop', function(e){
|
1162
|
-
if (self._options.dragAndDrop.hideDropzones) {
|
1163
|
-
for (i=0; i < dropzones.length; i++) {
|
1164
|
-
qq(dropzones[i]).hide();
|
1165
|
-
}
|
1166
|
-
}
|
1167
|
-
e.preventDefault();
|
1168
|
-
});
|
2092
|
+
return element;
|
1169
2093
|
},
|
1170
|
-
_onSubmit: function(id,
|
2094
|
+
_onSubmit: function(id, name){
|
1171
2095
|
qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
|
1172
|
-
this._addToList(id,
|
2096
|
+
this._addToList(id, name);
|
1173
2097
|
},
|
1174
2098
|
// Update the progress bar & percentage as the file is uploaded
|
1175
|
-
_onProgress: function(id,
|
2099
|
+
_onProgress: function(id, name, loaded, total){
|
1176
2100
|
qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
|
1177
2101
|
|
1178
|
-
var item, progressBar,
|
2102
|
+
var item, progressBar, percent, cancelLink;
|
1179
2103
|
|
1180
2104
|
item = this.getItemByFileId(id);
|
1181
2105
|
progressBar = this._find(item, 'progressBar');
|
@@ -1188,24 +2112,20 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1188
2112
|
qq(progressBar).hide();
|
1189
2113
|
qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
|
1190
2114
|
|
1191
|
-
// If last byte was sent,
|
1192
|
-
|
2115
|
+
// If last byte was sent, display total file size
|
2116
|
+
this._displayFileSize(id);
|
1193
2117
|
}
|
1194
2118
|
else {
|
1195
|
-
// If still uploading, display percentage
|
1196
|
-
|
2119
|
+
// If still uploading, display percentage - total size is actually the total request(s) size
|
2120
|
+
this._displayFileSize(id, loaded, total);
|
1197
2121
|
|
1198
2122
|
qq(progressBar).css({display: 'block'});
|
1199
2123
|
}
|
1200
2124
|
|
1201
2125
|
// Update progress bar element
|
1202
2126
|
qq(progressBar).css({width: percent + '%'});
|
1203
|
-
|
1204
|
-
size = this._find(item, 'size');
|
1205
|
-
qq(size).css({display: 'inline'});
|
1206
|
-
qq(size).setText(text);
|
1207
2127
|
},
|
1208
|
-
_onComplete: function(id,
|
2128
|
+
_onComplete: function(id, name, result, xhr){
|
1209
2129
|
qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
|
1210
2130
|
|
1211
2131
|
var item = this.getItemByFileId(id);
|
@@ -1215,12 +2135,16 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1215
2135
|
qq(item).removeClass(this._classes.retrying);
|
1216
2136
|
qq(this._find(item, 'progressBar')).hide();
|
1217
2137
|
|
1218
|
-
if (!this._options.disableCancelForFormUploads || qq.
|
2138
|
+
if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
|
1219
2139
|
qq(this._find(item, 'cancel')).hide();
|
1220
2140
|
}
|
1221
2141
|
qq(this._find(item, 'spinner')).hide();
|
1222
2142
|
|
1223
|
-
if (result.success){
|
2143
|
+
if (result.success) {
|
2144
|
+
if (this._isDeletePossible()) {
|
2145
|
+
this._showDeleteLink(id);
|
2146
|
+
}
|
2147
|
+
|
1224
2148
|
qq(item).addClass(this._classes.success);
|
1225
2149
|
if (this._classes.successIcon) {
|
1226
2150
|
this._find(item, 'finished').style.display = "inline-block";
|
@@ -1238,14 +2162,17 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1238
2162
|
this._controlFailureTextDisplay(item, result);
|
1239
2163
|
}
|
1240
2164
|
},
|
1241
|
-
_onUpload: function(id,
|
2165
|
+
_onUpload: function(id, name){
|
1242
2166
|
qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
|
1243
2167
|
|
1244
|
-
|
1245
|
-
|
2168
|
+
this._showSpinner(id);
|
2169
|
+
},
|
2170
|
+
_onCancel: function(id, name) {
|
2171
|
+
qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
|
2172
|
+
this._removeFileItem(id);
|
1246
2173
|
},
|
1247
2174
|
_onBeforeAutoRetry: function(id) {
|
1248
|
-
var item, progressBar,
|
2175
|
+
var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
|
1249
2176
|
|
1250
2177
|
qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
|
1251
2178
|
|
@@ -1276,15 +2203,69 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1276
2203
|
var item = this.getItemByFileId(id);
|
1277
2204
|
this._find(item, 'progressBar').style.width = 0;
|
1278
2205
|
qq(item).removeClass(this._classes.fail);
|
1279
|
-
this.
|
2206
|
+
qq(this._find(item, 'statusText')).clearText();
|
2207
|
+
this._showSpinner(id);
|
1280
2208
|
this._showCancelLink(item);
|
1281
2209
|
return true;
|
1282
2210
|
}
|
1283
2211
|
return false;
|
1284
2212
|
},
|
1285
|
-
|
2213
|
+
_onSubmitDelete: function(id) {
|
2214
|
+
if (this._isDeletePossible()) {
|
2215
|
+
if (this._options.callbacks.onSubmitDelete(id) !== false) {
|
2216
|
+
if (this._options.deleteFile.forceConfirm) {
|
2217
|
+
this._showDeleteConfirm(id);
|
2218
|
+
}
|
2219
|
+
else {
|
2220
|
+
this._sendDeleteRequest(id);
|
2221
|
+
}
|
2222
|
+
}
|
2223
|
+
}
|
2224
|
+
else {
|
2225
|
+
this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
|
2226
|
+
return false;
|
2227
|
+
}
|
2228
|
+
},
|
2229
|
+
_onDeleteComplete: function(id, xhr, isError) {
|
2230
|
+
qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
|
2231
|
+
|
2232
|
+
var item = this.getItemByFileId(id),
|
2233
|
+
spinnerEl = this._find(item, 'spinner'),
|
2234
|
+
statusTextEl = this._find(item, 'statusText');
|
2235
|
+
|
2236
|
+
qq(spinnerEl).hide();
|
2237
|
+
|
2238
|
+
if (isError) {
|
2239
|
+
qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
|
2240
|
+
this._showDeleteLink(id);
|
2241
|
+
}
|
2242
|
+
else {
|
2243
|
+
this._removeFileItem(id);
|
2244
|
+
}
|
2245
|
+
},
|
2246
|
+
_sendDeleteRequest: function(id) {
|
2247
|
+
var item = this.getItemByFileId(id),
|
2248
|
+
deleteLink = this._find(item, 'deleteButton'),
|
2249
|
+
statusTextEl = this._find(item, 'statusText');
|
2250
|
+
|
2251
|
+
qq(deleteLink).hide();
|
2252
|
+
this._showSpinner(id);
|
2253
|
+
qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
|
2254
|
+
this._deleteHandler.sendDelete(id, this.getUuid(id));
|
2255
|
+
},
|
2256
|
+
_showDeleteConfirm: function(id) {
|
2257
|
+
var fileName = this._handler.getName(id),
|
2258
|
+
confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
|
2259
|
+
uuid = this.getUuid(id),
|
2260
|
+
self = this;
|
2261
|
+
|
2262
|
+
this._options.showConfirm(confirmMessage, function() {
|
2263
|
+
self._sendDeleteRequest(id);
|
2264
|
+
});
|
2265
|
+
},
|
2266
|
+
_addToList: function(id, name){
|
1286
2267
|
var item = qq.toElement(this._options.fileTemplate);
|
1287
|
-
if (this._options.disableCancelForFormUploads && !qq.
|
2268
|
+
if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
|
1288
2269
|
var cancelLink = this._find(item, 'cancel');
|
1289
2270
|
qq(cancelLink).remove();
|
1290
2271
|
}
|
@@ -1292,15 +2273,36 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1292
2273
|
item.qqFileId = id;
|
1293
2274
|
|
1294
2275
|
var fileElement = this._find(item, 'file');
|
1295
|
-
qq(fileElement).setText(this.
|
2276
|
+
qq(fileElement).setText(this._options.formatFileName(name));
|
1296
2277
|
qq(this._find(item, 'size')).hide();
|
1297
|
-
if (!this._options.multiple)
|
2278
|
+
if (!this._options.multiple) {
|
2279
|
+
this._handler.cancelAll();
|
2280
|
+
this._clearList();
|
2281
|
+
}
|
2282
|
+
|
1298
2283
|
this._listElement.appendChild(item);
|
2284
|
+
|
2285
|
+
if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
|
2286
|
+
this._displayFileSize(id);
|
2287
|
+
}
|
1299
2288
|
},
|
1300
2289
|
_clearList: function(){
|
1301
2290
|
this._listElement.innerHTML = '';
|
1302
2291
|
this.clearStoredFiles();
|
1303
2292
|
},
|
2293
|
+
_displayFileSize: function(id, loadedSize, totalSize) {
|
2294
|
+
var item = this.getItemByFileId(id),
|
2295
|
+
size = this.getSize(id),
|
2296
|
+
sizeForDisplay = this._formatSize(size),
|
2297
|
+
sizeEl = this._find(item, 'size');
|
2298
|
+
|
2299
|
+
if (loadedSize !== undefined && totalSize !== undefined) {
|
2300
|
+
sizeForDisplay = this._formatProgress(loadedSize, totalSize);
|
2301
|
+
}
|
2302
|
+
|
2303
|
+
qq(sizeEl).css({display: 'inline'});
|
2304
|
+
qq(sizeEl).setText(sizeForDisplay);
|
2305
|
+
},
|
1304
2306
|
/**
|
1305
2307
|
* delegate click event for cancel & retry links
|
1306
2308
|
**/
|
@@ -1308,21 +2310,23 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1308
2310
|
var self = this,
|
1309
2311
|
list = this._listElement;
|
1310
2312
|
|
1311
|
-
this.
|
2313
|
+
this._disposeSupport.attach(list, 'click', function(e){
|
1312
2314
|
e = e || window.event;
|
1313
2315
|
var target = e.target || e.srcElement;
|
1314
2316
|
|
1315
|
-
if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry)){
|
2317
|
+
if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
|
1316
2318
|
qq.preventDefault(e);
|
1317
2319
|
|
1318
2320
|
var item = target.parentNode;
|
1319
|
-
while(item.qqFileId
|
2321
|
+
while(item.qqFileId === undefined) {
|
1320
2322
|
item = target = target.parentNode;
|
1321
2323
|
}
|
1322
2324
|
|
1323
|
-
if (qq(target).hasClass(self._classes.
|
2325
|
+
if (qq(target).hasClass(self._classes.deleteButton)) {
|
2326
|
+
self.deleteFile(item.qqFileId);
|
2327
|
+
}
|
2328
|
+
else if (qq(target).hasClass(self._classes.cancel)) {
|
1324
2329
|
self.cancel(item.qqFileId);
|
1325
|
-
qq(item).remove();
|
1326
2330
|
}
|
1327
2331
|
else {
|
1328
2332
|
qq(item).removeClass(self._classes.retryable);
|
@@ -1371,405 +2375,620 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1371
2375
|
this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
|
1372
2376
|
}
|
1373
2377
|
},
|
1374
|
-
//TODO turn this into a real tooltip, with click trigger (so it is usable on mobile devices). See case #355 for details.
|
1375
2378
|
_showTooltip: function(item, text) {
|
1376
2379
|
item.title = text;
|
1377
2380
|
},
|
1378
|
-
_showSpinner: function(
|
1379
|
-
var
|
2381
|
+
_showSpinner: function(id) {
|
2382
|
+
var item = this.getItemByFileId(id),
|
2383
|
+
spinnerEl = this._find(item, 'spinner');
|
2384
|
+
|
1380
2385
|
spinnerEl.style.display = "inline-block";
|
1381
2386
|
},
|
1382
2387
|
_showCancelLink: function(item) {
|
1383
|
-
if (!this._options.disableCancelForFormUploads || qq.
|
2388
|
+
if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
|
1384
2389
|
var cancelLink = this._find(item, 'cancel');
|
1385
|
-
|
2390
|
+
|
2391
|
+
qq(cancelLink).css({display: 'inline'});
|
1386
2392
|
}
|
1387
2393
|
},
|
1388
|
-
|
2394
|
+
_showDeleteLink: function(id) {
|
2395
|
+
var item = this.getItemByFileId(id),
|
2396
|
+
deleteLink = this._find(item, 'deleteButton');
|
2397
|
+
|
2398
|
+
qq(deleteLink).css({display: 'inline'});
|
2399
|
+
},
|
2400
|
+
_error: function(code, name){
|
1389
2401
|
var message = qq.FineUploaderBasic.prototype._error.apply(this, arguments);
|
1390
2402
|
this._options.showMessage(message);
|
1391
2403
|
}
|
1392
2404
|
});
|
2405
|
+
/** Generic class for sending non-upload ajax requests and handling the associated responses **/
|
2406
|
+
//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
|
2407
|
+
/*globals qq, XMLHttpRequest*/
|
2408
|
+
qq.AjaxRequestor = function(o) {
|
2409
|
+
"use strict";
|
1393
2410
|
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
2411
|
+
var log, shouldParamsBeInQueryString,
|
2412
|
+
queue = [],
|
2413
|
+
requestState = [],
|
2414
|
+
options = {
|
2415
|
+
method: 'POST',
|
2416
|
+
maxConnections: 3,
|
2417
|
+
customHeaders: {},
|
2418
|
+
endpointStore: {},
|
2419
|
+
paramsStore: {},
|
2420
|
+
successfulResponseCodes: [200],
|
2421
|
+
demoMode: false,
|
2422
|
+
cors: {
|
2423
|
+
expected: false,
|
2424
|
+
sendCredentials: false
|
2425
|
+
},
|
2426
|
+
log: function(str, level) {},
|
2427
|
+
onSend: function(id) {},
|
2428
|
+
onComplete: function(id, xhr, isError) {},
|
2429
|
+
onCancel: function(id) {}
|
2430
|
+
};
|
1405
2431
|
|
1406
|
-
|
2432
|
+
qq.extend(options, o);
|
2433
|
+
log = options.log;
|
2434
|
+
shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
|
1407
2435
|
|
1408
|
-
this._disableDropOutside();
|
1409
|
-
this._attachEvents();
|
1410
|
-
};
|
1411
2436
|
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
2437
|
+
/**
|
2438
|
+
* Removes element from queue, sends next request
|
2439
|
+
*/
|
2440
|
+
function dequeue(id) {
|
2441
|
+
var i = qq.indexOf(queue, id),
|
2442
|
+
max = options.maxConnections,
|
2443
|
+
nextId;
|
1419
2444
|
|
1420
|
-
|
1421
|
-
|
1422
|
-
qq(document).attach('dragover', function(e){
|
1423
|
-
e.preventDefault();
|
1424
|
-
});
|
1425
|
-
} else {
|
1426
|
-
qq(document).attach('dragover', function(e){
|
1427
|
-
if (e.dataTransfer){
|
1428
|
-
e.dataTransfer.dropEffect = 'none';
|
1429
|
-
e.preventDefault();
|
1430
|
-
}
|
1431
|
-
});
|
1432
|
-
}
|
2445
|
+
delete requestState[id];
|
2446
|
+
queue.splice(i, 1);
|
1433
2447
|
|
1434
|
-
|
2448
|
+
if (queue.length >= max && i < max){
|
2449
|
+
nextId = queue[max-1];
|
2450
|
+
sendRequest(nextId);
|
1435
2451
|
}
|
1436
|
-
}
|
1437
|
-
_attachEvents: function(){
|
1438
|
-
var self = this;
|
1439
|
-
|
1440
|
-
self._attach(self._element, 'dragover', function(e){
|
1441
|
-
if (!self._isValidFileDrag(e)) return;
|
2452
|
+
}
|
1442
2453
|
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
e.dataTransfer.dropEffect = 'copy'; // for Chrome
|
1448
|
-
}
|
2454
|
+
function onComplete(id) {
|
2455
|
+
var xhr = requestState[id].xhr,
|
2456
|
+
method = getMethod(),
|
2457
|
+
isError = false;
|
1449
2458
|
|
1450
|
-
|
1451
|
-
e.preventDefault();
|
1452
|
-
});
|
2459
|
+
dequeue(id);
|
1453
2460
|
|
1454
|
-
|
1455
|
-
|
2461
|
+
if (!isResponseSuccessful(xhr.status)) {
|
2462
|
+
isError = true;
|
2463
|
+
log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
|
2464
|
+
}
|
1456
2465
|
|
1457
|
-
|
1458
|
-
|
2466
|
+
options.onComplete(id, xhr, isError);
|
2467
|
+
}
|
1459
2468
|
|
1460
|
-
|
1461
|
-
|
2469
|
+
function sendRequest(id) {
|
2470
|
+
var xhr = new XMLHttpRequest(),
|
2471
|
+
method = getMethod(),
|
2472
|
+
params = {},
|
2473
|
+
url;
|
1462
2474
|
|
1463
|
-
|
2475
|
+
options.onSend(id);
|
1464
2476
|
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
2477
|
+
if (options.paramsStore.getParams) {
|
2478
|
+
params = options.paramsStore.getParams(id);
|
2479
|
+
}
|
1468
2480
|
|
1469
|
-
|
1470
|
-
});
|
2481
|
+
url = createUrl(id, params);
|
1471
2482
|
|
1472
|
-
|
1473
|
-
|
2483
|
+
requestState[id].xhr = xhr;
|
2484
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id);
|
2485
|
+
xhr.open(method, url, true);
|
1474
2486
|
|
1475
|
-
|
1476
|
-
|
1477
|
-
}
|
1478
|
-
},
|
1479
|
-
_isValidFileDrag: function(e){
|
1480
|
-
// e.dataTransfer currently causing IE errors
|
1481
|
-
// IE9 does NOT support file API, so drag-and-drop is not possible
|
1482
|
-
if (qq.ie() && !qq.ie10()) return false;
|
2487
|
+
if (options.cors.expected && options.cors.sendCredentials) {
|
2488
|
+
xhr.withCredentials = true;
|
2489
|
+
}
|
1483
2490
|
|
1484
|
-
|
1485
|
-
// do not check dt.types.contains in webkit, because it crashes safari 4
|
1486
|
-
isSafari = qq.safari();
|
2491
|
+
setHeaders(id);
|
1487
2492
|
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
2493
|
+
log('Sending ' + method + " request for " + id);
|
2494
|
+
if (!shouldParamsBeInQueryString && params) {
|
2495
|
+
xhr.send(qq.obj2url(params, ""));
|
2496
|
+
}
|
2497
|
+
else {
|
2498
|
+
xhr.send();
|
2499
|
+
}
|
1492
2500
|
}
|
1493
|
-
};
|
1494
|
-
/**
|
1495
|
-
* Class for uploading files, uploading itself is handled by child classes
|
1496
|
-
*/
|
1497
|
-
qq.UploadHandlerAbstract = function(o){
|
1498
|
-
// Default options, can be overridden by the user
|
1499
|
-
this._options = {
|
1500
|
-
debug: false,
|
1501
|
-
endpoint: '/upload.php',
|
1502
|
-
// maximum number of concurrent uploads
|
1503
|
-
maxConnections: 999,
|
1504
|
-
log: function(str, level) {},
|
1505
|
-
onProgress: function(id, fileName, loaded, total){},
|
1506
|
-
onComplete: function(id, fileName, response, xhr){},
|
1507
|
-
onCancel: function(id, fileName){},
|
1508
|
-
onUpload: function(id, fileName, xhr){},
|
1509
|
-
onAutoRetry: function(id, fileName, response, xhr){}
|
1510
|
-
|
1511
|
-
};
|
1512
|
-
qq.extend(this._options, o);
|
1513
|
-
|
1514
|
-
this._queue = [];
|
1515
|
-
// params for files in queue
|
1516
|
-
this._params = [];
|
1517
|
-
|
1518
|
-
this.log = this._options.log;
|
1519
|
-
};
|
1520
|
-
qq.UploadHandlerAbstract.prototype = {
|
1521
|
-
/**
|
1522
|
-
* Adds file or file input to the queue
|
1523
|
-
* @returns id
|
1524
|
-
**/
|
1525
|
-
add: function(file){},
|
1526
|
-
/**
|
1527
|
-
* Sends the file identified by id and additional query params to the server
|
1528
|
-
*/
|
1529
|
-
upload: function(id, params){
|
1530
|
-
var len = this._queue.push(id);
|
1531
2501
|
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
2502
|
+
function createUrl(id, params) {
|
2503
|
+
var endpoint = options.endpointStore.getEndpoint(id),
|
2504
|
+
addToPath = requestState[id].addToPath;
|
1535
2505
|
|
1536
|
-
|
1537
|
-
|
1538
|
-
this._upload(id, this._params[id]);
|
2506
|
+
if (addToPath !== undefined) {
|
2507
|
+
endpoint += "/" + addToPath;
|
1539
2508
|
}
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
if (i >= 0) {
|
1544
|
-
this._upload(id, this._params[id]);
|
2509
|
+
|
2510
|
+
if (shouldParamsBeInQueryString && params) {
|
2511
|
+
return qq.obj2url(params, endpoint);
|
1545
2512
|
}
|
1546
2513
|
else {
|
1547
|
-
|
1548
|
-
}
|
1549
|
-
},
|
1550
|
-
/**
|
1551
|
-
* Cancels file upload by id
|
1552
|
-
*/
|
1553
|
-
cancel: function(id){
|
1554
|
-
this.log('Cancelling ' + id);
|
1555
|
-
this._cancel(id);
|
1556
|
-
this._dequeue(id);
|
1557
|
-
},
|
1558
|
-
/**
|
1559
|
-
* Cancells all uploads
|
1560
|
-
*/
|
1561
|
-
cancelAll: function(){
|
1562
|
-
for (var i=0; i<this._queue.length; i++){
|
1563
|
-
this._cancel(this._queue[i]);
|
2514
|
+
return endpoint;
|
1564
2515
|
}
|
1565
|
-
|
1566
|
-
},
|
1567
|
-
/**
|
1568
|
-
* Returns name of the file identified by id
|
1569
|
-
*/
|
1570
|
-
getName: function(id){},
|
1571
|
-
/**
|
1572
|
-
* Returns size of the file identified by id
|
1573
|
-
*/
|
1574
|
-
getSize: function(id){},
|
1575
|
-
/**
|
1576
|
-
* Returns id of files being uploaded or
|
1577
|
-
* waiting for their turn
|
1578
|
-
*/
|
1579
|
-
getQueue: function(){
|
1580
|
-
return this._queue;
|
1581
|
-
},
|
1582
|
-
reset: function() {
|
1583
|
-
this.log('Resetting upload handler');
|
1584
|
-
this._queue = [];
|
1585
|
-
this._params = [];
|
1586
|
-
},
|
1587
|
-
/**
|
1588
|
-
* Actual upload method
|
1589
|
-
*/
|
1590
|
-
_upload: function(id){},
|
1591
|
-
/**
|
1592
|
-
* Actual cancel method
|
1593
|
-
*/
|
1594
|
-
_cancel: function(id){},
|
1595
|
-
/**
|
1596
|
-
* Removes element from queue, starts upload of next
|
1597
|
-
*/
|
1598
|
-
_dequeue: function(id){
|
1599
|
-
var i = qq.indexOf(this._queue, id);
|
1600
|
-
this._queue.splice(i, 1);
|
1601
|
-
|
1602
|
-
var max = this._options.maxConnections;
|
2516
|
+
}
|
1603
2517
|
|
1604
|
-
|
1605
|
-
|
1606
|
-
this._upload(nextId, this._params[nextId]);
|
1607
|
-
}
|
1608
|
-
},
|
1609
|
-
/**
|
1610
|
-
* Determine if the file exists.
|
1611
|
-
*/
|
1612
|
-
isValid: function(id) {}
|
1613
|
-
};
|
1614
|
-
/**
|
1615
|
-
* Class for uploading files using form and iframe
|
1616
|
-
* @inherits qq.UploadHandlerAbstract
|
1617
|
-
*/
|
1618
|
-
qq.UploadHandlerForm = function(o){
|
1619
|
-
qq.UploadHandlerAbstract.apply(this, arguments);
|
2518
|
+
function getReadyStateChangeHandler(id) {
|
2519
|
+
var xhr = requestState[id].xhr;
|
1620
2520
|
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
2521
|
+
return function() {
|
2522
|
+
if (xhr.readyState === 4) {
|
2523
|
+
onComplete(id, xhr);
|
2524
|
+
}
|
2525
|
+
};
|
2526
|
+
}
|
1626
2527
|
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
|
2528
|
+
function setHeaders(id) {
|
2529
|
+
var xhr = requestState[id].xhr,
|
2530
|
+
customHeaders = options.customHeaders;
|
1631
2531
|
|
1632
|
-
|
2532
|
+
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
2533
|
+
xhr.setRequestHeader("Cache-Control", "no-cache");
|
1633
2534
|
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
2535
|
+
qq.each(customHeaders, function(name, val) {
|
2536
|
+
xhr.setRequestHeader(name, val);
|
2537
|
+
});
|
2538
|
+
}
|
1638
2539
|
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
// get input value and remove path to normalize
|
1643
|
-
return this._inputs[id].value.replace(/.*(\/|\\)/, "");
|
1644
|
-
},
|
1645
|
-
isValid: function(id) {
|
1646
|
-
return this._inputs[id] !== undefined;
|
1647
|
-
},
|
1648
|
-
reset: function() {
|
1649
|
-
qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments);
|
1650
|
-
this._inputs = {};
|
1651
|
-
this._detach_load_events = {};
|
1652
|
-
},
|
1653
|
-
_cancel: function(id){
|
1654
|
-
this._options.onCancel(id, this.getName(id));
|
2540
|
+
function cancelRequest(id) {
|
2541
|
+
var xhr = requestState[id].xhr,
|
2542
|
+
method = getMethod();
|
1655
2543
|
|
1656
|
-
|
1657
|
-
|
2544
|
+
if (xhr) {
|
2545
|
+
xhr.onreadystatechange = null;
|
2546
|
+
xhr.abort();
|
2547
|
+
dequeue(id);
|
1658
2548
|
|
1659
|
-
|
1660
|
-
|
1661
|
-
// to cancel request set src to something else
|
1662
|
-
// we use src="javascript:false;" because it doesn't
|
1663
|
-
// trigger ie6 prompt on https
|
1664
|
-
iframe.setAttribute('src', 'javascript:false;');
|
2549
|
+
log('Cancelled ' + method + " for " + id);
|
2550
|
+
options.onCancel(id);
|
1665
2551
|
|
1666
|
-
|
2552
|
+
return true;
|
1667
2553
|
}
|
1668
|
-
},
|
1669
|
-
_upload: function(id, params){
|
1670
|
-
this._options.onUpload(id, this.getName(id), false);
|
1671
|
-
var input = this._inputs[id];
|
1672
2554
|
|
1673
|
-
|
1674
|
-
|
1675
|
-
}
|
2555
|
+
return false;
|
2556
|
+
}
|
1676
2557
|
|
1677
|
-
|
1678
|
-
|
2558
|
+
function isResponseSuccessful(responseCode) {
|
2559
|
+
return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
|
2560
|
+
}
|
1679
2561
|
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
2562
|
+
function getMethod() {
|
2563
|
+
if (options.demoMode) {
|
2564
|
+
return "GET";
|
2565
|
+
}
|
1683
2566
|
|
1684
|
-
|
1685
|
-
|
1686
|
-
self.log('iframe loaded');
|
2567
|
+
return options.method;
|
2568
|
+
}
|
1687
2569
|
|
1688
|
-
var response = self._getIframeContentJSON(iframe);
|
1689
2570
|
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
}, 1);
|
2571
|
+
return {
|
2572
|
+
send: function(id, addToPath) {
|
2573
|
+
requestState[id] = {
|
2574
|
+
addToPath: addToPath
|
2575
|
+
};
|
1696
2576
|
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
2577
|
+
var len = queue.push(id);
|
2578
|
+
|
2579
|
+
// if too many active connections, wait...
|
2580
|
+
if (len <= options.maxConnections){
|
2581
|
+
sendRequest(id);
|
1701
2582
|
}
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
2583
|
+
},
|
2584
|
+
cancel: function(id) {
|
2585
|
+
return cancelRequest(id);
|
2586
|
+
}
|
2587
|
+
};
|
2588
|
+
};
|
2589
|
+
/** Generic class for sending non-upload ajax requests and handling the associated responses **/
|
2590
|
+
/*globals qq, XMLHttpRequest*/
|
2591
|
+
qq.DeleteFileAjaxRequestor = function(o) {
|
2592
|
+
"use strict";
|
1705
2593
|
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
2594
|
+
var requestor,
|
2595
|
+
options = {
|
2596
|
+
endpointStore: {},
|
2597
|
+
maxConnections: 3,
|
2598
|
+
customHeaders: {},
|
2599
|
+
paramsStore: {},
|
2600
|
+
demoMode: false,
|
2601
|
+
cors: {
|
2602
|
+
expected: false,
|
2603
|
+
sendCredentials: false
|
2604
|
+
},
|
2605
|
+
log: function(str, level) {},
|
2606
|
+
onDelete: function(id) {},
|
2607
|
+
onDeleteComplete: function(id, xhr, isError) {}
|
2608
|
+
};
|
1709
2609
|
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
2610
|
+
qq.extend(options, o);
|
2611
|
+
|
2612
|
+
requestor = new qq.AjaxRequestor({
|
2613
|
+
method: 'DELETE',
|
2614
|
+
endpointStore: options.endpointStore,
|
2615
|
+
paramsStore: options.paramsStore,
|
2616
|
+
maxConnections: options.maxConnections,
|
2617
|
+
customHeaders: options.customHeaders,
|
2618
|
+
successfulResponseCodes: [200, 202, 204],
|
2619
|
+
demoMode: options.demoMode,
|
2620
|
+
log: options.log,
|
2621
|
+
onSend: options.onDelete,
|
2622
|
+
onComplete: options.onDeleteComplete
|
2623
|
+
});
|
1716
2624
|
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
2625
|
+
|
2626
|
+
return {
|
2627
|
+
sendDelete: function(id, uuid) {
|
2628
|
+
requestor.send(id, uuid);
|
2629
|
+
options.log("Submitted delete file request for " + id);
|
2630
|
+
}
|
2631
|
+
};
|
2632
|
+
};
|
2633
|
+
qq.WindowReceiveMessage = function(o) {
|
2634
|
+
var options = {
|
2635
|
+
log: function(message, level) {}
|
2636
|
+
},
|
2637
|
+
callbackWrapperDetachers = {};
|
2638
|
+
|
2639
|
+
qq.extend(options, o);
|
2640
|
+
|
2641
|
+
return {
|
2642
|
+
receiveMessage : function(id, callback) {
|
2643
|
+
var onMessageCallbackWrapper = function(event) {
|
2644
|
+
callback(event.data);
|
2645
|
+
};
|
2646
|
+
|
2647
|
+
if (window.postMessage) {
|
2648
|
+
callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
|
2649
|
+
}
|
2650
|
+
else {
|
2651
|
+
log("iframe message passing not supported in this browser!", "error");
|
1722
2652
|
}
|
2653
|
+
},
|
1723
2654
|
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
// In Opera event is fired second time
|
1730
|
-
// when body.innerHTML changed from false
|
1731
|
-
// to server response approx. after 1 sec
|
1732
|
-
// when we upload file with iframe
|
1733
|
-
return;
|
2655
|
+
stopReceivingMessages : function(id) {
|
2656
|
+
if (window.postMessage) {
|
2657
|
+
var detacher = callbackWrapperDetachers[id];
|
2658
|
+
if (detacher) {
|
2659
|
+
detacher();
|
1734
2660
|
}
|
1735
2661
|
}
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
2662
|
+
}
|
2663
|
+
};
|
2664
|
+
};
|
2665
|
+
/**
|
2666
|
+
* Class for uploading files, uploading itself is handled by child classes
|
2667
|
+
*/
|
2668
|
+
/*globals qq*/
|
2669
|
+
qq.UploadHandler = function(o) {
|
2670
|
+
"use strict";
|
2671
|
+
|
2672
|
+
var queue = [],
|
2673
|
+
options, log, dequeue, handlerImpl;
|
2674
|
+
|
2675
|
+
// Default options, can be overridden by the user
|
2676
|
+
options = {
|
2677
|
+
debug: false,
|
2678
|
+
forceMultipart: true,
|
2679
|
+
paramsInBody: false,
|
2680
|
+
paramsStore: {},
|
2681
|
+
endpointStore: {},
|
2682
|
+
cors: {
|
2683
|
+
expected: false,
|
2684
|
+
sendCredentials: false
|
2685
|
+
},
|
2686
|
+
maxConnections: 3, // maximum number of concurrent uploads
|
2687
|
+
uuidParamName: 'qquuid',
|
2688
|
+
totalFileSizeParamName: 'qqtotalfilesize',
|
2689
|
+
chunking: {
|
2690
|
+
enabled: false,
|
2691
|
+
partSize: 2000000, //bytes
|
2692
|
+
paramNames: {
|
2693
|
+
partIndex: 'qqpartindex',
|
2694
|
+
partByteOffset: 'qqpartbyteoffset',
|
2695
|
+
chunkSize: 'qqchunksize',
|
2696
|
+
totalParts: 'qqtotalparts',
|
2697
|
+
filename: 'qqfilename'
|
2698
|
+
}
|
2699
|
+
},
|
2700
|
+
resume: {
|
2701
|
+
enabled: false,
|
2702
|
+
id: null,
|
2703
|
+
cookiesExpireIn: 7, //days
|
2704
|
+
paramNames: {
|
2705
|
+
resuming: "qqresume"
|
2706
|
+
}
|
2707
|
+
},
|
2708
|
+
blobs: {
|
2709
|
+
paramNames: {
|
2710
|
+
name: 'qqblobname'
|
2711
|
+
}
|
2712
|
+
},
|
2713
|
+
log: function(str, level) {},
|
2714
|
+
onProgress: function(id, fileName, loaded, total){},
|
2715
|
+
onComplete: function(id, fileName, response, xhr){},
|
2716
|
+
onCancel: function(id, fileName){},
|
2717
|
+
onUpload: function(id, fileName){},
|
2718
|
+
onUploadChunk: function(id, fileName, chunkData){},
|
2719
|
+
onAutoRetry: function(id, fileName, response, xhr){},
|
2720
|
+
onResume: function(id, fileName, chunkData){}
|
2721
|
+
|
2722
|
+
};
|
2723
|
+
qq.extend(options, o);
|
2724
|
+
|
2725
|
+
log = options.log;
|
2726
|
+
|
2727
|
+
/**
|
2728
|
+
* Removes element from queue, starts upload of next
|
2729
|
+
*/
|
2730
|
+
dequeue = function(id) {
|
2731
|
+
var i = qq.indexOf(queue, id),
|
2732
|
+
max = options.maxConnections,
|
2733
|
+
nextId;
|
2734
|
+
|
2735
|
+
if (i >= 0) {
|
2736
|
+
queue.splice(i, 1);
|
2737
|
+
|
2738
|
+
if (queue.length >= max && i < max){
|
2739
|
+
nextId = queue[max-1];
|
2740
|
+
handlerImpl.upload(nextId);
|
2741
|
+
}
|
2742
|
+
}
|
2743
|
+
};
|
2744
|
+
|
2745
|
+
if (qq.isXhrUploadSupported()) {
|
2746
|
+
handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
|
2747
|
+
}
|
2748
|
+
else {
|
2749
|
+
handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
|
2750
|
+
}
|
2751
|
+
|
2752
|
+
|
2753
|
+
return {
|
2754
|
+
/**
|
2755
|
+
* Adds file or file input to the queue
|
2756
|
+
* @returns id
|
2757
|
+
**/
|
2758
|
+
add: function(file){
|
2759
|
+
return handlerImpl.add(file);
|
2760
|
+
},
|
2761
|
+
/**
|
2762
|
+
* Sends the file identified by id
|
2763
|
+
*/
|
2764
|
+
upload: function(id){
|
2765
|
+
var len = queue.push(id);
|
2766
|
+
|
2767
|
+
// if too many active uploads, wait...
|
2768
|
+
if (len <= options.maxConnections){
|
2769
|
+
return handlerImpl.upload(id);
|
2770
|
+
}
|
2771
|
+
},
|
2772
|
+
retry: function(id) {
|
2773
|
+
var i = qq.indexOf(queue, id);
|
2774
|
+
if (i >= 0) {
|
2775
|
+
return handlerImpl.upload(id, true);
|
2776
|
+
}
|
2777
|
+
else {
|
2778
|
+
return this.upload(id);
|
1739
2779
|
}
|
2780
|
+
},
|
2781
|
+
/**
|
2782
|
+
* Cancels file upload by id
|
2783
|
+
*/
|
2784
|
+
cancel: function(id) {
|
2785
|
+
log('Cancelling ' + id);
|
2786
|
+
options.paramsStore.remove(id);
|
2787
|
+
handlerImpl.cancel(id);
|
2788
|
+
dequeue(id);
|
2789
|
+
},
|
2790
|
+
/**
|
2791
|
+
* Cancels all queued or in-progress uploads
|
2792
|
+
*/
|
2793
|
+
cancelAll: function() {
|
2794
|
+
var self = this,
|
2795
|
+
queueCopy = [];
|
2796
|
+
|
2797
|
+
qq.extend(queueCopy, queue);
|
2798
|
+
qq.each(queueCopy, function(idx, fileId) {
|
2799
|
+
self.cancel(fileId);
|
2800
|
+
});
|
1740
2801
|
|
1741
|
-
|
2802
|
+
queue = [];
|
2803
|
+
},
|
2804
|
+
/**
|
2805
|
+
* Returns name of the file identified by id
|
2806
|
+
*/
|
2807
|
+
getName: function(id){
|
2808
|
+
return handlerImpl.getName(id);
|
2809
|
+
},
|
2810
|
+
/**
|
2811
|
+
* Returns size of the file identified by id
|
2812
|
+
*/
|
2813
|
+
getSize: function(id){
|
2814
|
+
if (handlerImpl.getSize) {
|
2815
|
+
return handlerImpl.getSize(id);
|
2816
|
+
}
|
2817
|
+
},
|
2818
|
+
getFile: function(id) {
|
2819
|
+
if (handlerImpl.getFile) {
|
2820
|
+
return handlerImpl.getFile(id);
|
2821
|
+
}
|
2822
|
+
},
|
2823
|
+
/**
|
2824
|
+
* Returns id of files being uploaded or
|
2825
|
+
* waiting for their turn
|
2826
|
+
*/
|
2827
|
+
getQueue: function(){
|
2828
|
+
return queue;
|
2829
|
+
},
|
2830
|
+
reset: function() {
|
2831
|
+
log('Resetting upload handler');
|
2832
|
+
queue = [];
|
2833
|
+
handlerImpl.reset();
|
2834
|
+
},
|
2835
|
+
getUuid: function(id) {
|
2836
|
+
return handlerImpl.getUuid(id);
|
2837
|
+
},
|
2838
|
+
/**
|
2839
|
+
* Determine if the file exists.
|
2840
|
+
*/
|
2841
|
+
isValid: function(id) {
|
2842
|
+
return handlerImpl.isValid(id);
|
2843
|
+
},
|
2844
|
+
getResumableFilesData: function() {
|
2845
|
+
if (handlerImpl.getResumableFilesData) {
|
2846
|
+
return handlerImpl.getResumableFilesData();
|
2847
|
+
}
|
2848
|
+
return [];
|
2849
|
+
}
|
2850
|
+
};
|
2851
|
+
};
|
2852
|
+
/*globals qq, document, setTimeout*/
|
2853
|
+
/*globals clearTimeout*/
|
2854
|
+
qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
|
2855
|
+
"use strict";
|
2856
|
+
|
2857
|
+
var options = o,
|
2858
|
+
inputs = [],
|
2859
|
+
uuids = [],
|
2860
|
+
detachLoadEvents = {},
|
2861
|
+
postMessageCallbackTimers = {},
|
2862
|
+
uploadComplete = uploadCompleteCallback,
|
2863
|
+
log = logCallback,
|
2864
|
+
corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
|
2865
|
+
onloadCallbacks = {},
|
2866
|
+
api;
|
2867
|
+
|
2868
|
+
|
2869
|
+
function detachLoadEvent(id) {
|
2870
|
+
if (detachLoadEvents[id] !== undefined) {
|
2871
|
+
detachLoadEvents[id]();
|
2872
|
+
delete detachLoadEvents[id];
|
2873
|
+
}
|
2874
|
+
}
|
2875
|
+
|
2876
|
+
function registerPostMessageCallback(iframe, callback) {
|
2877
|
+
var id = iframe.id;
|
2878
|
+
|
2879
|
+
onloadCallbacks[uuids[id]] = callback;
|
2880
|
+
|
2881
|
+
detachLoadEvents[id] = qq(iframe).attach('load', function() {
|
2882
|
+
if (inputs[id]) {
|
2883
|
+
log("Received iframe load event for CORS upload request (file id " + id + ")");
|
2884
|
+
|
2885
|
+
postMessageCallbackTimers[id] = setTimeout(function() {
|
2886
|
+
var errorMessage = "No valid message received from loaded iframe for file id " + id;
|
2887
|
+
log(errorMessage, "error");
|
2888
|
+
callback({
|
2889
|
+
error: errorMessage
|
2890
|
+
});
|
2891
|
+
}, 1000);
|
2892
|
+
}
|
1742
2893
|
});
|
1743
|
-
|
2894
|
+
|
2895
|
+
corsMessageReceiver.receiveMessage(id, function(message) {
|
2896
|
+
log("Received the following window message: '" + message + "'");
|
2897
|
+
var response = qq.parseJson(message),
|
2898
|
+
uuid = response.uuid,
|
2899
|
+
onloadCallback;
|
2900
|
+
|
2901
|
+
if (uuid && onloadCallbacks[uuid]) {
|
2902
|
+
clearTimeout(postMessageCallbackTimers[id]);
|
2903
|
+
delete postMessageCallbackTimers[id];
|
2904
|
+
|
2905
|
+
detachLoadEvent(id);
|
2906
|
+
|
2907
|
+
onloadCallback = onloadCallbacks[uuid];
|
2908
|
+
|
2909
|
+
delete onloadCallbacks[uuid];
|
2910
|
+
corsMessageReceiver.stopReceivingMessages(id);
|
2911
|
+
onloadCallback(response);
|
2912
|
+
}
|
2913
|
+
else if (!uuid) {
|
2914
|
+
log("'" + message + "' does not contain a UUID - ignoring.");
|
2915
|
+
}
|
2916
|
+
});
|
2917
|
+
}
|
2918
|
+
|
2919
|
+
function attachLoadEvent(iframe, callback) {
|
2920
|
+
/*jslint eqeq: true*/
|
2921
|
+
|
2922
|
+
if (options.cors.expected) {
|
2923
|
+
registerPostMessageCallback(iframe, callback);
|
2924
|
+
}
|
2925
|
+
else {
|
2926
|
+
detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
|
2927
|
+
log('Received response for ' + iframe.id);
|
2928
|
+
|
2929
|
+
// when we remove iframe from dom
|
2930
|
+
// the request stops, but in IE load
|
2931
|
+
// event fires
|
2932
|
+
if (!iframe.parentNode){
|
2933
|
+
return;
|
2934
|
+
}
|
2935
|
+
|
2936
|
+
try {
|
2937
|
+
// fixing Opera 10.53
|
2938
|
+
if (iframe.contentDocument &&
|
2939
|
+
iframe.contentDocument.body &&
|
2940
|
+
iframe.contentDocument.body.innerHTML == "false"){
|
2941
|
+
// In Opera event is fired second time
|
2942
|
+
// when body.innerHTML changed from false
|
2943
|
+
// to server response approx. after 1 sec
|
2944
|
+
// when we upload file with iframe
|
2945
|
+
return;
|
2946
|
+
}
|
2947
|
+
}
|
2948
|
+
catch (error) {
|
2949
|
+
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
2950
|
+
log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
|
2951
|
+
}
|
2952
|
+
|
2953
|
+
callback();
|
2954
|
+
});
|
2955
|
+
}
|
2956
|
+
}
|
2957
|
+
|
1744
2958
|
/**
|
1745
2959
|
* Returns json object received by iframe from server.
|
1746
2960
|
*/
|
1747
|
-
|
2961
|
+
function getIframeContentJson(iframe) {
|
2962
|
+
/*jshint evil: true*/
|
2963
|
+
|
2964
|
+
var response;
|
2965
|
+
|
1748
2966
|
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
1749
2967
|
try {
|
1750
2968
|
// iframe.contentWindow.document - for IE<7
|
1751
|
-
var doc = iframe.contentDocument
|
1752
|
-
|
2969
|
+
var doc = iframe.contentDocument || iframe.contentWindow.document,
|
2970
|
+
innerHTML = doc.body.innerHTML;
|
1753
2971
|
|
1754
|
-
|
1755
|
-
|
1756
|
-
this.log("innerHTML = " + innerHTML);
|
2972
|
+
log("converting iframe's innerHTML to JSON");
|
2973
|
+
log("innerHTML = " + innerHTML);
|
1757
2974
|
//plain text response may be wrapped in <pre> tag
|
1758
2975
|
if (innerHTML && innerHTML.match(/^<pre/i)) {
|
1759
2976
|
innerHTML = doc.body.firstChild.firstChild.nodeValue;
|
1760
2977
|
}
|
1761
|
-
|
2978
|
+
|
2979
|
+
response = qq.parseJson(innerHTML);
|
1762
2980
|
} catch(error){
|
1763
|
-
|
2981
|
+
log('Error when attempting to parse form upload response (' + error + ")", 'error');
|
1764
2982
|
response = {success: false};
|
1765
2983
|
}
|
1766
2984
|
|
1767
2985
|
return response;
|
1768
|
-
}
|
2986
|
+
}
|
2987
|
+
|
1769
2988
|
/**
|
1770
2989
|
* Creates iframe with unique name
|
1771
2990
|
*/
|
1772
|
-
|
2991
|
+
function createIframe(id){
|
1773
2992
|
// We can't use following code as the name attribute
|
1774
2993
|
// won't be properly registered in IE6, and new window
|
1775
2994
|
// on form submit will open
|
@@ -1777,7 +2996,6 @@ qq.extend(qq.UploadHandlerForm.prototype, {
|
|
1777
2996
|
// iframe.setAttribute('name', id);
|
1778
2997
|
|
1779
2998
|
var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
|
1780
|
-
// src="javascript:false;" removes ie6 prompt on https
|
1781
2999
|
|
1782
3000
|
iframe.setAttribute('id', id);
|
1783
3001
|
|
@@ -1785,194 +3003,759 @@ qq.extend(qq.UploadHandlerForm.prototype, {
|
|
1785
3003
|
document.body.appendChild(iframe);
|
1786
3004
|
|
1787
3005
|
return iframe;
|
1788
|
-
}
|
3006
|
+
}
|
3007
|
+
|
1789
3008
|
/**
|
1790
3009
|
* Creates form, that will be submitted to iframe
|
1791
3010
|
*/
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
3011
|
+
function createForm(id, iframe){
|
3012
|
+
var params = options.paramsStore.getParams(id),
|
3013
|
+
protocol = options.demoMode ? "GET" : "POST",
|
3014
|
+
form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>'),
|
3015
|
+
endpoint = options.endpointStore.getEndpoint(id),
|
3016
|
+
url = endpoint;
|
3017
|
+
|
3018
|
+
params[options.uuidParamName] = uuids[id];
|
3019
|
+
|
3020
|
+
if (!options.paramsInBody) {
|
3021
|
+
url = qq.obj2url(params, endpoint);
|
3022
|
+
}
|
3023
|
+
else {
|
3024
|
+
qq.obj2Inputs(params, form);
|
3025
|
+
}
|
3026
|
+
|
3027
|
+
form.setAttribute('action', url);
|
1804
3028
|
form.setAttribute('target', iframe.name);
|
1805
3029
|
form.style.display = 'none';
|
1806
3030
|
document.body.appendChild(form);
|
1807
3031
|
|
1808
3032
|
return form;
|
1809
3033
|
}
|
1810
|
-
});
|
1811
|
-
/**
|
1812
|
-
* Class for uploading files using xhr
|
1813
|
-
* @inherits qq.UploadHandlerAbstract
|
1814
|
-
*/
|
1815
|
-
qq.UploadHandlerXhr = function(o){
|
1816
|
-
qq.UploadHandlerAbstract.apply(this, arguments);
|
1817
3034
|
|
1818
|
-
this._files = [];
|
1819
|
-
this._xhrs = [];
|
1820
3035
|
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
3036
|
+
api = {
|
3037
|
+
add: function(fileInput) {
|
3038
|
+
fileInput.setAttribute('name', options.inputName);
|
1824
3039
|
|
1825
|
-
|
1826
|
-
|
1827
|
-
var input = document.createElement('input');
|
1828
|
-
input.type = 'file';
|
3040
|
+
var id = inputs.push(fileInput) - 1;
|
3041
|
+
uuids[id] = qq.getUniqueId();
|
1829
3042
|
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
3043
|
+
// remove file input from DOM
|
3044
|
+
if (fileInput.parentNode){
|
3045
|
+
qq(fileInput).remove();
|
3046
|
+
}
|
3047
|
+
|
3048
|
+
return id;
|
3049
|
+
},
|
3050
|
+
getName: function(id) {
|
3051
|
+
/*jslint regexp: true*/
|
3052
|
+
|
3053
|
+
// get input value and remove path to normalize
|
3054
|
+
return inputs[id].value.replace(/.*(\/|\\)/, "");
|
3055
|
+
},
|
3056
|
+
isValid: function(id) {
|
3057
|
+
return inputs[id] !== undefined;
|
3058
|
+
},
|
3059
|
+
reset: function() {
|
3060
|
+
qq.UploadHandler.prototype.reset.apply(this, arguments);
|
3061
|
+
inputs = [];
|
3062
|
+
uuids = [];
|
3063
|
+
detachLoadEvents = {};
|
3064
|
+
},
|
3065
|
+
getUuid: function(id) {
|
3066
|
+
return uuids[id];
|
3067
|
+
},
|
3068
|
+
cancel: function(id) {
|
3069
|
+
options.onCancel(id, this.getName(id));
|
3070
|
+
|
3071
|
+
delete inputs[id];
|
3072
|
+
delete uuids[id];
|
3073
|
+
delete detachLoadEvents[id];
|
3074
|
+
|
3075
|
+
if (options.cors.expected) {
|
3076
|
+
clearTimeout(postMessageCallbackTimers[id]);
|
3077
|
+
delete postMessageCallbackTimers[id];
|
3078
|
+
corsMessageReceiver.stopReceivingMessages(id);
|
3079
|
+
}
|
3080
|
+
|
3081
|
+
var iframe = document.getElementById(id);
|
3082
|
+
if (iframe) {
|
3083
|
+
// to cancel request set src to something else
|
3084
|
+
// we use src="javascript:false;" because it doesn't
|
3085
|
+
// trigger ie6 prompt on https
|
3086
|
+
iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
|
3087
|
+
|
3088
|
+
qq(iframe).remove();
|
3089
|
+
}
|
3090
|
+
},
|
3091
|
+
upload: function(id){
|
3092
|
+
var input = inputs[id],
|
3093
|
+
fileName = api.getName(id),
|
3094
|
+
iframe = createIframe(id),
|
3095
|
+
form;
|
3096
|
+
|
3097
|
+
if (!input){
|
3098
|
+
throw new Error('file with passed id was not added, or already uploaded or cancelled');
|
3099
|
+
}
|
3100
|
+
|
3101
|
+
options.onUpload(id, this.getName(id));
|
3102
|
+
|
3103
|
+
form = createForm(id, iframe);
|
3104
|
+
form.appendChild(input);
|
3105
|
+
|
3106
|
+
attachLoadEvent(iframe, function(responseFromMessage){
|
3107
|
+
log('iframe loaded');
|
3108
|
+
|
3109
|
+
var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
|
3110
|
+
|
3111
|
+
detachLoadEvent(id);
|
3112
|
+
|
3113
|
+
//we can't remove an iframe if the iframe doesn't belong to the same domain
|
3114
|
+
if (!options.cors.expected) {
|
3115
|
+
qq(iframe).remove();
|
3116
|
+
}
|
3117
|
+
|
3118
|
+
if (!response.success) {
|
3119
|
+
if (options.onAutoRetry(id, fileName, response)) {
|
3120
|
+
return;
|
3121
|
+
}
|
3122
|
+
}
|
3123
|
+
options.onComplete(id, fileName, response);
|
3124
|
+
uploadComplete(id);
|
3125
|
+
});
|
3126
|
+
|
3127
|
+
log('Sending upload request for ' + id);
|
3128
|
+
form.submit();
|
3129
|
+
qq(form).remove();
|
3130
|
+
|
3131
|
+
return id;
|
3132
|
+
}
|
3133
|
+
};
|
3134
|
+
|
3135
|
+
return api;
|
1835
3136
|
};
|
3137
|
+
/*globals qq, File, XMLHttpRequest, FormData, Blob*/
|
3138
|
+
qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
|
3139
|
+
"use strict";
|
3140
|
+
|
3141
|
+
var options = o,
|
3142
|
+
uploadComplete = uploadCompleteCallback,
|
3143
|
+
log = logCallback,
|
3144
|
+
fileState = [],
|
3145
|
+
cookieItemDelimiter = "|",
|
3146
|
+
chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
|
3147
|
+
resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
|
3148
|
+
resumeId = getResumeId(),
|
3149
|
+
multipart = options.forceMultipart || options.paramsInBody,
|
3150
|
+
api;
|
3151
|
+
|
3152
|
+
|
3153
|
+
function addChunkingSpecificParams(id, params, chunkData) {
|
3154
|
+
var size = api.getSize(id),
|
3155
|
+
name = api.getName(id);
|
3156
|
+
|
3157
|
+
params[options.chunking.paramNames.partIndex] = chunkData.part;
|
3158
|
+
params[options.chunking.paramNames.partByteOffset] = chunkData.start;
|
3159
|
+
params[options.chunking.paramNames.chunkSize] = chunkData.size;
|
3160
|
+
params[options.chunking.paramNames.totalParts] = chunkData.count;
|
3161
|
+
params[options.totalFileSizeParamName] = size;
|
1836
3162
|
|
1837
|
-
|
1838
|
-
|
3163
|
+
/**
|
3164
|
+
* When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
|
3165
|
+
* or an empty string. So, we will need to include the actual file name as a param in this case.
|
3166
|
+
*/
|
3167
|
+
if (multipart) {
|
3168
|
+
params[options.chunking.paramNames.filename] = name;
|
3169
|
+
}
|
3170
|
+
}
|
1839
3171
|
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
1844
|
-
|
1845
|
-
|
1846
|
-
|
1847
|
-
|
3172
|
+
function addResumeSpecificParams(params) {
|
3173
|
+
params[options.resume.paramNames.resuming] = true;
|
3174
|
+
}
|
3175
|
+
|
3176
|
+
function getChunk(fileOrBlob, startByte, endByte) {
|
3177
|
+
if (fileOrBlob.slice) {
|
3178
|
+
return fileOrBlob.slice(startByte, endByte);
|
3179
|
+
}
|
3180
|
+
else if (fileOrBlob.mozSlice) {
|
3181
|
+
return fileOrBlob.mozSlice(startByte, endByte);
|
3182
|
+
}
|
3183
|
+
else if (fileOrBlob.webkitSlice) {
|
3184
|
+
return fileOrBlob.webkitSlice(startByte, endByte);
|
1848
3185
|
}
|
3186
|
+
}
|
1849
3187
|
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
1853
|
-
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
},
|
1868
|
-
isValid: function(id) {
|
1869
|
-
return this._files[id] !== undefined;
|
1870
|
-
},
|
1871
|
-
reset: function() {
|
1872
|
-
qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments);
|
1873
|
-
this._files = [];
|
1874
|
-
this._xhrs = [];
|
1875
|
-
this._loaded = [];
|
1876
|
-
},
|
1877
|
-
/**
|
1878
|
-
* Sends the file identified by id and additional query params to the server
|
1879
|
-
* @param {Object} params name-value string pairs
|
1880
|
-
*/
|
1881
|
-
_upload: function(id, params){
|
1882
|
-
this._options.onUpload(id, this.getName(id), true);
|
3188
|
+
function getChunkData(id, chunkIndex) {
|
3189
|
+
var chunkSize = options.chunking.partSize,
|
3190
|
+
fileSize = api.getSize(id),
|
3191
|
+
fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
|
3192
|
+
startBytes = chunkSize * chunkIndex,
|
3193
|
+
endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
|
3194
|
+
totalChunks = getTotalChunks(id);
|
3195
|
+
|
3196
|
+
return {
|
3197
|
+
part: chunkIndex,
|
3198
|
+
start: startBytes,
|
3199
|
+
end: endBytes,
|
3200
|
+
count: totalChunks,
|
3201
|
+
blob: getChunk(fileOrBlob, startBytes, endBytes),
|
3202
|
+
size: endBytes - startBytes
|
3203
|
+
};
|
3204
|
+
}
|
1883
3205
|
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
3206
|
+
function getTotalChunks(id) {
|
3207
|
+
var fileSize = api.getSize(id),
|
3208
|
+
chunkSize = options.chunking.partSize;
|
1887
3209
|
|
1888
|
-
|
3210
|
+
return Math.ceil(fileSize / chunkSize);
|
3211
|
+
}
|
1889
3212
|
|
1890
|
-
|
1891
|
-
var
|
3213
|
+
function createXhr(id) {
|
3214
|
+
var xhr = new XMLHttpRequest();
|
1892
3215
|
|
1893
|
-
xhr
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
3216
|
+
fileState[id].xhr = xhr;
|
3217
|
+
|
3218
|
+
return xhr;
|
3219
|
+
}
|
3220
|
+
|
3221
|
+
function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
|
3222
|
+
var formData = new FormData(),
|
3223
|
+
method = options.demoMode ? "GET" : "POST",
|
3224
|
+
endpoint = options.endpointStore.getEndpoint(id),
|
3225
|
+
url = endpoint,
|
3226
|
+
name = api.getName(id),
|
3227
|
+
size = api.getSize(id),
|
3228
|
+
blobData = fileState[id].blobData;
|
3229
|
+
|
3230
|
+
params[options.uuidParamName] = fileState[id].uuid;
|
3231
|
+
|
3232
|
+
if (multipart) {
|
3233
|
+
params[options.totalFileSizeParamName] = size;
|
3234
|
+
|
3235
|
+
if (blobData) {
|
3236
|
+
/**
|
3237
|
+
* When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
|
3238
|
+
* or an empty string. So, we will need to include the actual file name as a param in this case.
|
3239
|
+
*/
|
3240
|
+
params[options.blobs.paramNames.name] = blobData.name;
|
1897
3241
|
}
|
1898
|
-
}
|
3242
|
+
}
|
1899
3243
|
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
3244
|
+
//build query string
|
3245
|
+
if (!options.paramsInBody) {
|
3246
|
+
if (!multipart) {
|
3247
|
+
params[options.inputName] = name;
|
1903
3248
|
}
|
1904
|
-
|
3249
|
+
url = qq.obj2url(params, endpoint);
|
3250
|
+
}
|
3251
|
+
|
3252
|
+
xhr.open(method, url, true);
|
3253
|
+
|
3254
|
+
if (options.cors.expected && options.cors.sendCredentials) {
|
3255
|
+
xhr.withCredentials = true;
|
3256
|
+
}
|
3257
|
+
|
3258
|
+
if (multipart) {
|
3259
|
+
if (options.paramsInBody) {
|
3260
|
+
qq.obj2FormData(params, formData);
|
3261
|
+
}
|
3262
|
+
|
3263
|
+
formData.append(options.inputName, fileOrBlob);
|
3264
|
+
return formData;
|
3265
|
+
}
|
3266
|
+
|
3267
|
+
return fileOrBlob;
|
3268
|
+
}
|
1905
3269
|
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
var queryString = qq.obj2url(params, this._options.endpoint);
|
3270
|
+
function setHeaders(id, xhr) {
|
3271
|
+
var extraHeaders = options.customHeaders,
|
3272
|
+
fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
|
1910
3273
|
|
1911
|
-
var protocol = this._options.demoMode ? "GET" : "POST";
|
1912
|
-
xhr.open(protocol, queryString, true);
|
1913
3274
|
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
1914
|
-
xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
|
1915
3275
|
xhr.setRequestHeader("Cache-Control", "no-cache");
|
1916
|
-
|
1917
|
-
|
1918
|
-
formData.append(this._options.inputName, file);
|
1919
|
-
file = formData;
|
1920
|
-
} else {
|
3276
|
+
|
3277
|
+
if (!multipart) {
|
1921
3278
|
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
1922
3279
|
//NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
|
1923
|
-
xhr.setRequestHeader("X-Mime-Type",
|
3280
|
+
xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
|
1924
3281
|
}
|
1925
|
-
|
1926
|
-
|
3282
|
+
|
3283
|
+
qq.each(extraHeaders, function(name, val) {
|
3284
|
+
xhr.setRequestHeader(name, val);
|
3285
|
+
});
|
3286
|
+
}
|
3287
|
+
|
3288
|
+
function handleCompletedItem(id, response, xhr) {
|
3289
|
+
var name = api.getName(id),
|
3290
|
+
size = api.getSize(id);
|
3291
|
+
|
3292
|
+
fileState[id].attemptingResume = false;
|
3293
|
+
|
3294
|
+
options.onProgress(id, name, size, size);
|
3295
|
+
|
3296
|
+
options.onComplete(id, name, response, xhr);
|
3297
|
+
delete fileState[id].xhr;
|
3298
|
+
uploadComplete(id);
|
3299
|
+
}
|
3300
|
+
|
3301
|
+
function uploadNextChunk(id) {
|
3302
|
+
var chunkIdx = fileState[id].remainingChunkIdxs[0],
|
3303
|
+
chunkData = getChunkData(id, chunkIdx),
|
3304
|
+
xhr = createXhr(id),
|
3305
|
+
size = api.getSize(id),
|
3306
|
+
name = api.getName(id),
|
3307
|
+
toSend, params;
|
3308
|
+
|
3309
|
+
if (fileState[id].loaded === undefined) {
|
3310
|
+
fileState[id].loaded = 0;
|
3311
|
+
}
|
3312
|
+
|
3313
|
+
if (resumeEnabled && fileState[id].file) {
|
3314
|
+
persistChunkData(id, chunkData);
|
3315
|
+
}
|
3316
|
+
|
3317
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
|
3318
|
+
|
3319
|
+
xhr.upload.onprogress = function(e) {
|
3320
|
+
if (e.lengthComputable) {
|
3321
|
+
var totalLoaded = e.loaded + fileState[id].loaded,
|
3322
|
+
estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
|
3323
|
+
|
3324
|
+
options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
|
3325
|
+
}
|
1927
3326
|
};
|
1928
3327
|
|
1929
|
-
|
1930
|
-
xhr.send(file);
|
1931
|
-
},
|
1932
|
-
_onComplete: function(id, xhr){
|
1933
|
-
"use strict";
|
1934
|
-
// the request was aborted/cancelled
|
1935
|
-
if (!this._files[id]) { return; }
|
3328
|
+
options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
|
1936
3329
|
|
1937
|
-
|
1938
|
-
|
1939
|
-
var response; //the parsed JSON response from the server, or the empty object if parsing failed.
|
3330
|
+
params = options.paramsStore.getParams(id);
|
3331
|
+
addChunkingSpecificParams(id, params, chunkData);
|
1940
3332
|
|
1941
|
-
|
3333
|
+
if (fileState[id].attemptingResume) {
|
3334
|
+
addResumeSpecificParams(params);
|
3335
|
+
}
|
1942
3336
|
|
1943
|
-
|
1944
|
-
|
3337
|
+
toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
|
3338
|
+
setHeaders(id, xhr);
|
1945
3339
|
|
1946
|
-
|
1947
|
-
|
1948
|
-
|
1949
|
-
|
1950
|
-
|
3340
|
+
log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
|
3341
|
+
xhr.send(toSend);
|
3342
|
+
}
|
3343
|
+
|
3344
|
+
function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
|
3345
|
+
var chunkData = getChunkData(id, chunkIdx),
|
3346
|
+
blobSize = chunkData.size,
|
3347
|
+
overhead = requestSize - blobSize,
|
3348
|
+
size = api.getSize(id),
|
3349
|
+
chunkCount = chunkData.count,
|
3350
|
+
initialRequestOverhead = fileState[id].initialRequestOverhead,
|
3351
|
+
overheadDiff = overhead - initialRequestOverhead;
|
3352
|
+
|
3353
|
+
fileState[id].lastRequestOverhead = overhead;
|
3354
|
+
|
3355
|
+
if (chunkIdx === 0) {
|
3356
|
+
fileState[id].lastChunkIdxProgress = 0;
|
3357
|
+
fileState[id].initialRequestOverhead = overhead;
|
3358
|
+
fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
|
3359
|
+
}
|
3360
|
+
else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
|
3361
|
+
fileState[id].lastChunkIdxProgress = chunkIdx;
|
3362
|
+
fileState[id].estTotalRequestsSize += overheadDiff;
|
3363
|
+
}
|
3364
|
+
|
3365
|
+
return fileState[id].estTotalRequestsSize;
|
3366
|
+
}
|
3367
|
+
|
3368
|
+
function getLastRequestOverhead(id) {
|
3369
|
+
if (multipart) {
|
3370
|
+
return fileState[id].lastRequestOverhead;
|
3371
|
+
}
|
3372
|
+
else {
|
3373
|
+
return 0;
|
3374
|
+
}
|
3375
|
+
}
|
3376
|
+
|
3377
|
+
function handleSuccessfullyCompletedChunk(id, response, xhr) {
|
3378
|
+
var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
|
3379
|
+
chunkData = getChunkData(id, chunkIdx);
|
3380
|
+
|
3381
|
+
fileState[id].attemptingResume = false;
|
3382
|
+
fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
|
3383
|
+
|
3384
|
+
if (fileState[id].remainingChunkIdxs.length > 0) {
|
3385
|
+
uploadNextChunk(id);
|
3386
|
+
}
|
3387
|
+
else {
|
3388
|
+
if (resumeEnabled) {
|
3389
|
+
deletePersistedChunkData(id);
|
1951
3390
|
}
|
1952
|
-
|
1953
|
-
|
3391
|
+
|
3392
|
+
handleCompletedItem(id, response, xhr);
|
3393
|
+
}
|
3394
|
+
}
|
3395
|
+
|
3396
|
+
function isErrorResponse(xhr, response) {
|
3397
|
+
return xhr.status !== 200 || !response.success || response.reset;
|
3398
|
+
}
|
3399
|
+
|
3400
|
+
function parseResponse(xhr) {
|
3401
|
+
var response;
|
3402
|
+
|
3403
|
+
try {
|
3404
|
+
response = qq.parseJson(xhr.responseText);
|
3405
|
+
}
|
3406
|
+
catch(error) {
|
3407
|
+
log('Error when attempting to parse xhr response text (' + error + ')', 'error');
|
1954
3408
|
response = {};
|
1955
3409
|
}
|
1956
3410
|
|
1957
|
-
|
1958
|
-
|
1959
|
-
|
3411
|
+
return response;
|
3412
|
+
}
|
3413
|
+
|
3414
|
+
function handleResetResponse(id) {
|
3415
|
+
log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
|
3416
|
+
|
3417
|
+
if (resumeEnabled) {
|
3418
|
+
deletePersistedChunkData(id);
|
3419
|
+
fileState[id].attemptingResume = false;
|
3420
|
+
}
|
3421
|
+
|
3422
|
+
fileState[id].remainingChunkIdxs = [];
|
3423
|
+
delete fileState[id].loaded;
|
3424
|
+
delete fileState[id].estTotalRequestsSize;
|
3425
|
+
delete fileState[id].initialRequestOverhead;
|
3426
|
+
}
|
3427
|
+
|
3428
|
+
function handleResetResponseOnResumeAttempt(id) {
|
3429
|
+
fileState[id].attemptingResume = false;
|
3430
|
+
log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
|
3431
|
+
handleResetResponse(id);
|
3432
|
+
api.upload(id, true);
|
3433
|
+
}
|
3434
|
+
|
3435
|
+
function handleNonResetErrorResponse(id, response, xhr) {
|
3436
|
+
var name = api.getName(id);
|
3437
|
+
|
3438
|
+
if (options.onAutoRetry(id, name, response, xhr)) {
|
3439
|
+
return;
|
3440
|
+
}
|
3441
|
+
else {
|
3442
|
+
handleCompletedItem(id, response, xhr);
|
3443
|
+
}
|
3444
|
+
}
|
3445
|
+
|
3446
|
+
function onComplete(id, xhr) {
|
3447
|
+
var response;
|
3448
|
+
|
3449
|
+
// the request was aborted/cancelled
|
3450
|
+
if (!fileState[id]) {
|
3451
|
+
return;
|
3452
|
+
}
|
3453
|
+
|
3454
|
+
log("xhr - server response received for " + id);
|
3455
|
+
log("responseText = " + xhr.responseText);
|
3456
|
+
response = parseResponse(xhr);
|
3457
|
+
|
3458
|
+
if (isErrorResponse(xhr, response)) {
|
3459
|
+
if (response.reset) {
|
3460
|
+
handleResetResponse(id);
|
3461
|
+
}
|
3462
|
+
|
3463
|
+
if (fileState[id].attemptingResume && response.reset) {
|
3464
|
+
handleResetResponseOnResumeAttempt(id);
|
1960
3465
|
}
|
3466
|
+
else {
|
3467
|
+
handleNonResetErrorResponse(id, response, xhr);
|
3468
|
+
}
|
3469
|
+
}
|
3470
|
+
else if (chunkFiles) {
|
3471
|
+
handleSuccessfullyCompletedChunk(id, response, xhr);
|
3472
|
+
}
|
3473
|
+
else {
|
3474
|
+
handleCompletedItem(id, response, xhr);
|
1961
3475
|
}
|
3476
|
+
}
|
1962
3477
|
|
1963
|
-
|
3478
|
+
function getChunkDataForCallback(chunkData) {
|
3479
|
+
return {
|
3480
|
+
partIndex: chunkData.part,
|
3481
|
+
startByte: chunkData.start + 1,
|
3482
|
+
endByte: chunkData.end,
|
3483
|
+
totalParts: chunkData.count
|
3484
|
+
};
|
3485
|
+
}
|
1964
3486
|
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
3487
|
+
function getReadyStateChangeHandler(id, xhr) {
|
3488
|
+
return function() {
|
3489
|
+
if (xhr.readyState === 4) {
|
3490
|
+
onComplete(id, xhr);
|
3491
|
+
}
|
3492
|
+
};
|
3493
|
+
}
|
1970
3494
|
|
1971
|
-
|
3495
|
+
function persistChunkData(id, chunkData) {
|
3496
|
+
var fileUuid = api.getUuid(id),
|
3497
|
+
lastByteSent = fileState[id].loaded,
|
3498
|
+
initialRequestOverhead = fileState[id].initialRequestOverhead,
|
3499
|
+
estTotalRequestsSize = fileState[id].estTotalRequestsSize,
|
3500
|
+
cookieName = getChunkDataCookieName(id),
|
3501
|
+
cookieValue = fileUuid +
|
3502
|
+
cookieItemDelimiter + chunkData.part +
|
3503
|
+
cookieItemDelimiter + lastByteSent +
|
3504
|
+
cookieItemDelimiter + initialRequestOverhead +
|
3505
|
+
cookieItemDelimiter + estTotalRequestsSize,
|
3506
|
+
cookieExpDays = options.resume.cookiesExpireIn;
|
3507
|
+
|
3508
|
+
qq.setCookie(cookieName, cookieValue, cookieExpDays);
|
3509
|
+
}
|
1972
3510
|
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
3511
|
+
function deletePersistedChunkData(id) {
|
3512
|
+
if (fileState[id].file) {
|
3513
|
+
var cookieName = getChunkDataCookieName(id);
|
3514
|
+
qq.deleteCookie(cookieName);
|
1976
3515
|
}
|
1977
3516
|
}
|
1978
|
-
|
3517
|
+
|
3518
|
+
function getPersistedChunkData(id) {
|
3519
|
+
var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
|
3520
|
+
filename = api.getName(id),
|
3521
|
+
sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
|
3522
|
+
|
3523
|
+
if (chunkCookieValue) {
|
3524
|
+
sections = chunkCookieValue.split(cookieItemDelimiter);
|
3525
|
+
|
3526
|
+
if (sections.length === 5) {
|
3527
|
+
uuid = sections[0];
|
3528
|
+
partIndex = parseInt(sections[1], 10);
|
3529
|
+
lastByteSent = parseInt(sections[2], 10);
|
3530
|
+
initialRequestOverhead = parseInt(sections[3], 10);
|
3531
|
+
estTotalRequestsSize = parseInt(sections[4], 10);
|
3532
|
+
|
3533
|
+
return {
|
3534
|
+
uuid: uuid,
|
3535
|
+
part: partIndex,
|
3536
|
+
lastByteSent: lastByteSent,
|
3537
|
+
initialRequestOverhead: initialRequestOverhead,
|
3538
|
+
estTotalRequestsSize: estTotalRequestsSize
|
3539
|
+
};
|
3540
|
+
}
|
3541
|
+
else {
|
3542
|
+
log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
|
3543
|
+
}
|
3544
|
+
}
|
3545
|
+
}
|
3546
|
+
|
3547
|
+
function getChunkDataCookieName(id) {
|
3548
|
+
var filename = api.getName(id),
|
3549
|
+
fileSize = api.getSize(id),
|
3550
|
+
maxChunkSize = options.chunking.partSize,
|
3551
|
+
cookieName;
|
3552
|
+
|
3553
|
+
cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
|
3554
|
+
|
3555
|
+
if (resumeId !== undefined) {
|
3556
|
+
cookieName += cookieItemDelimiter + resumeId;
|
3557
|
+
}
|
3558
|
+
|
3559
|
+
return cookieName;
|
3560
|
+
}
|
3561
|
+
|
3562
|
+
function getResumeId() {
|
3563
|
+
if (options.resume.id !== null &&
|
3564
|
+
options.resume.id !== undefined &&
|
3565
|
+
!qq.isFunction(options.resume.id) &&
|
3566
|
+
!qq.isObject(options.resume.id)) {
|
3567
|
+
|
3568
|
+
return options.resume.id;
|
3569
|
+
}
|
3570
|
+
}
|
3571
|
+
|
3572
|
+
function handleFileChunkingUpload(id, retry) {
|
3573
|
+
var name = api.getName(id),
|
3574
|
+
firstChunkIndex = 0,
|
3575
|
+
persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
|
3576
|
+
|
3577
|
+
if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
|
3578
|
+
fileState[id].remainingChunkIdxs = [];
|
3579
|
+
|
3580
|
+
if (resumeEnabled && !retry && fileState[id].file) {
|
3581
|
+
persistedChunkInfoForResume = getPersistedChunkData(id);
|
3582
|
+
if (persistedChunkInfoForResume) {
|
3583
|
+
firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
|
3584
|
+
if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
|
3585
|
+
firstChunkIndex = persistedChunkInfoForResume.part;
|
3586
|
+
fileState[id].uuid = persistedChunkInfoForResume.uuid;
|
3587
|
+
fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
|
3588
|
+
fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
|
3589
|
+
fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
|
3590
|
+
fileState[id].attemptingResume = true;
|
3591
|
+
log('Resuming ' + name + " at partition index " + firstChunkIndex);
|
3592
|
+
}
|
3593
|
+
}
|
3594
|
+
}
|
3595
|
+
|
3596
|
+
for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
|
3597
|
+
fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
|
3598
|
+
}
|
3599
|
+
}
|
3600
|
+
|
3601
|
+
uploadNextChunk(id);
|
3602
|
+
}
|
3603
|
+
|
3604
|
+
function handleStandardFileUpload(id) {
|
3605
|
+
var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
|
3606
|
+
name = api.getName(id),
|
3607
|
+
xhr, params, toSend;
|
3608
|
+
|
3609
|
+
fileState[id].loaded = 0;
|
3610
|
+
|
3611
|
+
xhr = createXhr(id);
|
3612
|
+
|
3613
|
+
xhr.upload.onprogress = function(e){
|
3614
|
+
if (e.lengthComputable){
|
3615
|
+
fileState[id].loaded = e.loaded;
|
3616
|
+
options.onProgress(id, name, e.loaded, e.total);
|
3617
|
+
}
|
3618
|
+
};
|
3619
|
+
|
3620
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
|
3621
|
+
|
3622
|
+
params = options.paramsStore.getParams(id);
|
3623
|
+
toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
|
3624
|
+
setHeaders(id, xhr);
|
3625
|
+
|
3626
|
+
log('Sending upload request for ' + id);
|
3627
|
+
xhr.send(toSend);
|
3628
|
+
}
|
3629
|
+
|
3630
|
+
|
3631
|
+
api = {
|
3632
|
+
/**
|
3633
|
+
* Adds File or Blob to the queue
|
3634
|
+
* Returns id to use with upload, cancel
|
3635
|
+
**/
|
3636
|
+
add: function(fileOrBlobData){
|
3637
|
+
var id;
|
3638
|
+
|
3639
|
+
if (fileOrBlobData instanceof File) {
|
3640
|
+
id = fileState.push({file: fileOrBlobData}) - 1;
|
3641
|
+
}
|
3642
|
+
else if (fileOrBlobData.blob instanceof Blob) {
|
3643
|
+
id = fileState.push({blobData: fileOrBlobData}) - 1;
|
3644
|
+
}
|
3645
|
+
else {
|
3646
|
+
throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
|
3647
|
+
}
|
3648
|
+
|
3649
|
+
fileState[id].uuid = qq.getUniqueId();
|
3650
|
+
return id;
|
3651
|
+
},
|
3652
|
+
getName: function(id){
|
3653
|
+
var file = fileState[id].file,
|
3654
|
+
blobData = fileState[id].blobData;
|
3655
|
+
|
3656
|
+
if (file) {
|
3657
|
+
// fix missing name in Safari 4
|
3658
|
+
//NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
|
3659
|
+
return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
3660
|
+
}
|
3661
|
+
else {
|
3662
|
+
return blobData.name;
|
3663
|
+
}
|
3664
|
+
},
|
3665
|
+
getSize: function(id){
|
3666
|
+
/*jshint eqnull: true*/
|
3667
|
+
var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
|
3668
|
+
|
3669
|
+
if (qq.isFileOrInput(fileOrBlob)) {
|
3670
|
+
return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
|
3671
|
+
}
|
3672
|
+
else {
|
3673
|
+
return fileOrBlob.size;
|
3674
|
+
}
|
3675
|
+
},
|
3676
|
+
getFile: function(id) {
|
3677
|
+
if (fileState[id]) {
|
3678
|
+
return fileState[id].file || fileState[id].blobData.blob;
|
3679
|
+
}
|
3680
|
+
},
|
3681
|
+
/**
|
3682
|
+
* Returns uploaded bytes for file identified by id
|
3683
|
+
*/
|
3684
|
+
getLoaded: function(id){
|
3685
|
+
return fileState[id].loaded || 0;
|
3686
|
+
},
|
3687
|
+
isValid: function(id) {
|
3688
|
+
return fileState[id] !== undefined;
|
3689
|
+
},
|
3690
|
+
reset: function() {
|
3691
|
+
fileState = [];
|
3692
|
+
},
|
3693
|
+
getUuid: function(id) {
|
3694
|
+
return fileState[id].uuid;
|
3695
|
+
},
|
3696
|
+
/**
|
3697
|
+
* Sends the file identified by id to the server
|
3698
|
+
*/
|
3699
|
+
upload: function(id, retry){
|
3700
|
+
var name = this.getName(id);
|
3701
|
+
|
3702
|
+
options.onUpload(id, name);
|
3703
|
+
|
3704
|
+
if (chunkFiles) {
|
3705
|
+
handleFileChunkingUpload(id, retry);
|
3706
|
+
}
|
3707
|
+
else {
|
3708
|
+
handleStandardFileUpload(id);
|
3709
|
+
}
|
3710
|
+
},
|
3711
|
+
cancel: function(id){
|
3712
|
+
var xhr = fileState[id].xhr;
|
3713
|
+
|
3714
|
+
options.onCancel(id, this.getName(id));
|
3715
|
+
|
3716
|
+
if (xhr) {
|
3717
|
+
xhr.onreadystatechange = null;
|
3718
|
+
xhr.abort();
|
3719
|
+
}
|
3720
|
+
|
3721
|
+
if (resumeEnabled) {
|
3722
|
+
deletePersistedChunkData(id);
|
3723
|
+
}
|
3724
|
+
|
3725
|
+
delete fileState[id];
|
3726
|
+
},
|
3727
|
+
getResumableFilesData: function() {
|
3728
|
+
var matchingCookieNames = [],
|
3729
|
+
resumableFilesData = [];
|
3730
|
+
|
3731
|
+
if (chunkFiles && resumeEnabled) {
|
3732
|
+
if (resumeId === undefined) {
|
3733
|
+
matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
|
3734
|
+
cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
|
3735
|
+
}
|
3736
|
+
else {
|
3737
|
+
matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
|
3738
|
+
cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
|
3739
|
+
cookieItemDelimiter + resumeId + "="));
|
3740
|
+
}
|
3741
|
+
|
3742
|
+
qq.each(matchingCookieNames, function(idx, cookieName) {
|
3743
|
+
var cookiesNameParts = cookieName.split(cookieItemDelimiter);
|
3744
|
+
var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
|
3745
|
+
|
3746
|
+
resumableFilesData.push({
|
3747
|
+
name: decodeURIComponent(cookiesNameParts[1]),
|
3748
|
+
size: cookiesNameParts[2],
|
3749
|
+
uuid: cookieValueParts[0],
|
3750
|
+
partIdx: cookieValueParts[1]
|
3751
|
+
});
|
3752
|
+
});
|
3753
|
+
|
3754
|
+
return resumableFilesData;
|
3755
|
+
}
|
3756
|
+
return [];
|
3757
|
+
}
|
3758
|
+
};
|
3759
|
+
|
3760
|
+
return api;
|
3761
|
+
};
|