fileuploader-rails 3.1.1 → 3.4.1
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.
@@ -1,14 +1,13 @@
|
|
1
1
|
/**
|
2
|
-
* http://github.com/
|
2
|
+
* http://github.com/Widen/fine-uploader
|
3
3
|
*
|
4
4
|
* Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
|
5
5
|
*
|
6
|
-
*
|
7
|
-
* Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
|
6
|
+
* Copyright © 2013, Widen Enterprises info@fineupoader.com
|
8
7
|
*
|
9
|
-
* Licensed under
|
8
|
+
* Licensed under GNU GPL v3, see license.txt.
|
10
9
|
*/
|
11
|
-
/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest*/
|
10
|
+
/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
|
12
11
|
var qq = function(element) {
|
13
12
|
"use strict";
|
14
13
|
|
@@ -172,9 +171,22 @@ qq.isFunction = function(variable) {
|
|
172
171
|
return typeof(variable) === "function";
|
173
172
|
};
|
174
173
|
|
174
|
+
qq.isString = function(maybeString) {
|
175
|
+
"use strict";
|
176
|
+
return Object.prototype.toString.call(maybeString) === '[object String]';
|
177
|
+
};
|
178
|
+
|
179
|
+
qq.trimStr = function(string) {
|
180
|
+
if (String.prototype.trim) {
|
181
|
+
return string.trim();
|
182
|
+
}
|
183
|
+
|
184
|
+
return string.replace(/^\s+|\s+$/g,'');
|
185
|
+
};
|
186
|
+
|
175
187
|
qq.isFileOrInput = function(maybeFileOrInput) {
|
176
188
|
"use strict";
|
177
|
-
if (window.File && maybeFileOrInput instanceof File) {
|
189
|
+
if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
|
178
190
|
return true;
|
179
191
|
}
|
180
192
|
else if (window.HTMLInputElement) {
|
@@ -195,6 +207,11 @@ qq.isFileOrInput = function(maybeFileOrInput) {
|
|
195
207
|
return false;
|
196
208
|
};
|
197
209
|
|
210
|
+
qq.isBlob = function(maybeBlob) {
|
211
|
+
"use strict";
|
212
|
+
return window.Blob && maybeBlob instanceof Blob;
|
213
|
+
};
|
214
|
+
|
198
215
|
qq.isXhrUploadSupported = function() {
|
199
216
|
"use strict";
|
200
217
|
var input = document.createElement('input');
|
@@ -212,6 +229,13 @@ qq.isFolderDropSupported = function(dataTransfer) {
|
|
212
229
|
return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
|
213
230
|
};
|
214
231
|
|
232
|
+
qq.isFileChunkingSupported = function() {
|
233
|
+
"use strict";
|
234
|
+
return !qq.android() && //android's impl of Blob.slice is broken
|
235
|
+
qq.isXhrUploadSupported() &&
|
236
|
+
(File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
|
237
|
+
};
|
238
|
+
|
215
239
|
qq.extend = function (first, second, extendNested) {
|
216
240
|
"use strict";
|
217
241
|
qq.each(second, function(prop, val) {
|
@@ -245,7 +269,7 @@ qq.indexOf = function(arr, elt, from){
|
|
245
269
|
from += len;
|
246
270
|
}
|
247
271
|
|
248
|
-
for (
|
272
|
+
for (; from < len; from+=1){
|
249
273
|
if (arr.hasOwnProperty(from) && arr[from] === elt){
|
250
274
|
return from;
|
251
275
|
}
|
@@ -253,15 +277,16 @@ qq.indexOf = function(arr, elt, from){
|
|
253
277
|
return -1;
|
254
278
|
};
|
255
279
|
|
256
|
-
|
280
|
+
//this is a version 4 UUID
|
281
|
+
qq.getUniqueId = function(){
|
257
282
|
"use strict";
|
258
283
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
return
|
263
|
-
};
|
264
|
-
}
|
284
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
285
|
+
/*jslint eqeq: true, bitwise: true*/
|
286
|
+
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
287
|
+
return v.toString(16);
|
288
|
+
});
|
289
|
+
};
|
265
290
|
|
266
291
|
//
|
267
292
|
// Browsers and platforms detection
|
@@ -290,6 +315,10 @@ qq.windows = function(){
|
|
290
315
|
"use strict";
|
291
316
|
return navigator.platform === "Win32";
|
292
317
|
};
|
318
|
+
qq.android = function(){
|
319
|
+
"use strict";
|
320
|
+
return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
|
321
|
+
};
|
293
322
|
|
294
323
|
//
|
295
324
|
// Events
|
@@ -352,6 +381,7 @@ qq.each = function(obj, callback) {
|
|
352
381
|
*/
|
353
382
|
qq.obj2url = function(obj, temp, prefixDone){
|
354
383
|
"use strict";
|
384
|
+
/*jshint laxbreak: true*/
|
355
385
|
var i, len,
|
356
386
|
uristrings = [],
|
357
387
|
prefix = '&',
|
@@ -414,10 +444,10 @@ qq.obj2FormData = function(obj, formData, arrayKeyName) {
|
|
414
444
|
qq.obj2FormData(val, formData, key);
|
415
445
|
}
|
416
446
|
else if (qq.isFunction(val)) {
|
417
|
-
formData.append(
|
447
|
+
formData.append(key, val());
|
418
448
|
}
|
419
449
|
else {
|
420
|
-
formData.append(
|
450
|
+
formData.append(key, val);
|
421
451
|
}
|
422
452
|
});
|
423
453
|
|
@@ -444,6 +474,80 @@ qq.obj2Inputs = function(obj, form) {
|
|
444
474
|
return form;
|
445
475
|
};
|
446
476
|
|
477
|
+
qq.setCookie = function(name, value, days) {
|
478
|
+
var date = new Date(),
|
479
|
+
expires = "";
|
480
|
+
|
481
|
+
if (days) {
|
482
|
+
date.setTime(date.getTime()+(days*24*60*60*1000));
|
483
|
+
expires = "; expires="+date.toGMTString();
|
484
|
+
}
|
485
|
+
|
486
|
+
document.cookie = name+"="+value+expires+"; path=/";
|
487
|
+
};
|
488
|
+
|
489
|
+
qq.getCookie = function(name) {
|
490
|
+
var nameEQ = name + "=",
|
491
|
+
ca = document.cookie.split(';'),
|
492
|
+
c;
|
493
|
+
|
494
|
+
for(var i=0;i < ca.length;i++) {
|
495
|
+
c = ca[i];
|
496
|
+
while (c.charAt(0)==' ') {
|
497
|
+
c = c.substring(1,c.length);
|
498
|
+
}
|
499
|
+
if (c.indexOf(nameEQ) === 0) {
|
500
|
+
return c.substring(nameEQ.length,c.length);
|
501
|
+
}
|
502
|
+
}
|
503
|
+
};
|
504
|
+
|
505
|
+
qq.getCookieNames = function(regexp) {
|
506
|
+
var cookies = document.cookie.split(';'),
|
507
|
+
cookieNames = [];
|
508
|
+
|
509
|
+
qq.each(cookies, function(idx, cookie) {
|
510
|
+
cookie = qq.trimStr(cookie);
|
511
|
+
|
512
|
+
var equalsIdx = cookie.indexOf("=");
|
513
|
+
|
514
|
+
if (cookie.match(regexp)) {
|
515
|
+
cookieNames.push(cookie.substr(0, equalsIdx));
|
516
|
+
}
|
517
|
+
});
|
518
|
+
|
519
|
+
return cookieNames;
|
520
|
+
};
|
521
|
+
|
522
|
+
qq.deleteCookie = function(name) {
|
523
|
+
qq.setCookie(name, "", -1);
|
524
|
+
};
|
525
|
+
|
526
|
+
qq.areCookiesEnabled = function() {
|
527
|
+
var randNum = Math.random() * 100000,
|
528
|
+
name = "qqCookieTest:" + randNum;
|
529
|
+
qq.setCookie(name, 1);
|
530
|
+
|
531
|
+
if (qq.getCookie(name)) {
|
532
|
+
qq.deleteCookie(name);
|
533
|
+
return true;
|
534
|
+
}
|
535
|
+
return false;
|
536
|
+
};
|
537
|
+
|
538
|
+
/**
|
539
|
+
* Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
|
540
|
+
* implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
|
541
|
+
*/
|
542
|
+
qq.parseJson = function(json) {
|
543
|
+
/*jshint evil: true*/
|
544
|
+
if (window.JSON && qq.isFunction(JSON.parse)) {
|
545
|
+
return JSON.parse(json);
|
546
|
+
} else {
|
547
|
+
return eval("(" + json + ")");
|
548
|
+
}
|
549
|
+
};
|
550
|
+
|
447
551
|
/**
|
448
552
|
* A generic module which supports object disposing in dispose() method.
|
449
553
|
* */
|
@@ -477,61 +581,82 @@ qq.DisposeSupport = function() {
|
|
477
581
|
}
|
478
582
|
};
|
479
583
|
};
|
480
|
-
qq
|
481
|
-
|
482
|
-
|
483
|
-
// if set to true adds multiple attribute to file input
|
484
|
-
multiple: false,
|
485
|
-
acceptFiles: null,
|
486
|
-
// name attribute of file input
|
487
|
-
name: 'file',
|
488
|
-
onChange: function(input){},
|
489
|
-
hoverClass: 'qq-upload-button-hover',
|
490
|
-
focusClass: 'qq-upload-button-focus'
|
491
|
-
};
|
584
|
+
/*globals qq*/
|
585
|
+
qq.Promise = function() {
|
586
|
+
"use strict";
|
492
587
|
|
493
|
-
|
494
|
-
|
588
|
+
var successValue, failureValue,
|
589
|
+
successCallback, failureCallback,
|
590
|
+
state = 0;
|
495
591
|
|
496
|
-
|
592
|
+
return {
|
593
|
+
then: function(onSuccess, onFailure) {
|
594
|
+
if (state === 0) {
|
595
|
+
successCallback = onSuccess;
|
596
|
+
failureCallback = onFailure;
|
597
|
+
}
|
598
|
+
else if (state === -1 && onFailure) {
|
599
|
+
onFailure(failureValue);
|
600
|
+
}
|
601
|
+
else if (onSuccess) {
|
602
|
+
onSuccess(successValue);
|
603
|
+
}
|
604
|
+
},
|
497
605
|
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
overflow: 'hidden',
|
502
|
-
// Make sure browse button is in the right side
|
503
|
-
// in Internet Explorer
|
504
|
-
direction: 'ltr'
|
505
|
-
});
|
606
|
+
success: function(val) {
|
607
|
+
state = 1;
|
608
|
+
successValue = val;
|
506
609
|
|
507
|
-
|
508
|
-
|
610
|
+
if (successCallback) {
|
611
|
+
successCallback(val);
|
612
|
+
}
|
509
613
|
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
614
|
+
return this;
|
615
|
+
},
|
616
|
+
|
617
|
+
failure: function(val) {
|
618
|
+
state = -1;
|
619
|
+
failureValue = val;
|
620
|
+
|
621
|
+
if (failureCallback) {
|
622
|
+
failureCallback(val);
|
623
|
+
}
|
624
|
+
|
625
|
+
return this;
|
519
626
|
}
|
627
|
+
};
|
628
|
+
};
|
629
|
+
/*globals qq*/
|
630
|
+
qq.UploadButton = function(o) {
|
631
|
+
"use strict";
|
520
632
|
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
633
|
+
var input,
|
634
|
+
disposeSupport = new qq.DisposeSupport(),
|
635
|
+
options = {
|
636
|
+
element: null,
|
637
|
+
// if set to true adds multiple attribute to file input
|
638
|
+
multiple: false,
|
639
|
+
acceptFiles: null,
|
640
|
+
// name attribute of file input
|
641
|
+
name: 'file',
|
642
|
+
onChange: function(input) {},
|
643
|
+
hoverClass: 'qq-upload-button-hover',
|
644
|
+
focusClass: 'qq-upload-button-focus'
|
645
|
+
};
|
646
|
+
|
647
|
+
function createInput() {
|
525
648
|
var input = document.createElement("input");
|
526
649
|
|
527
|
-
if (
|
650
|
+
if (options.multiple){
|
528
651
|
input.setAttribute("multiple", "multiple");
|
529
652
|
}
|
530
653
|
|
531
|
-
if (
|
654
|
+
if (options.acceptFiles) {
|
655
|
+
input.setAttribute("accept", options.acceptFiles);
|
656
|
+
}
|
532
657
|
|
533
658
|
input.setAttribute("type", "file");
|
534
|
-
input.setAttribute("name",
|
659
|
+
input.setAttribute("name", options.name);
|
535
660
|
|
536
661
|
qq(input).css({
|
537
662
|
position: 'absolute',
|
@@ -549,24 +674,23 @@ qq.UploadButton.prototype = {
|
|
549
674
|
opacity: 0
|
550
675
|
});
|
551
676
|
|
552
|
-
|
677
|
+
options.element.appendChild(input);
|
553
678
|
|
554
|
-
|
555
|
-
|
556
|
-
self._options.onChange(input);
|
679
|
+
disposeSupport.attach(input, 'change', function(){
|
680
|
+
options.onChange(input);
|
557
681
|
});
|
558
682
|
|
559
|
-
|
560
|
-
qq(
|
683
|
+
disposeSupport.attach(input, 'mouseover', function(){
|
684
|
+
qq(options.element).addClass(options.hoverClass);
|
561
685
|
});
|
562
|
-
|
563
|
-
qq(
|
686
|
+
disposeSupport.attach(input, 'mouseout', function(){
|
687
|
+
qq(options.element).removeClass(options.hoverClass);
|
564
688
|
});
|
565
|
-
|
566
|
-
qq(
|
689
|
+
disposeSupport.attach(input, 'focus', function(){
|
690
|
+
qq(options.element).addClass(options.focusClass);
|
567
691
|
});
|
568
|
-
|
569
|
-
qq(
|
692
|
+
disposeSupport.attach(input, 'blur', function(){
|
693
|
+
qq(options.element).removeClass(options.focusClass);
|
570
694
|
});
|
571
695
|
|
572
696
|
// IE and Opera, unfortunately have 2 tab stops on file input
|
@@ -578,6 +702,84 @@ qq.UploadButton.prototype = {
|
|
578
702
|
|
579
703
|
return input;
|
580
704
|
}
|
705
|
+
|
706
|
+
|
707
|
+
qq.extend(options, o);
|
708
|
+
|
709
|
+
// make button suitable container for input
|
710
|
+
qq(options.element).css({
|
711
|
+
position: 'relative',
|
712
|
+
overflow: 'hidden',
|
713
|
+
// Make sure browse button is in the right side
|
714
|
+
// in Internet Explorer
|
715
|
+
direction: 'ltr'
|
716
|
+
});
|
717
|
+
|
718
|
+
input = createInput();
|
719
|
+
|
720
|
+
return {
|
721
|
+
getInput: function(){
|
722
|
+
return input;
|
723
|
+
},
|
724
|
+
|
725
|
+
reset: function(){
|
726
|
+
if (input.parentNode){
|
727
|
+
qq(input).remove();
|
728
|
+
}
|
729
|
+
|
730
|
+
qq(options.element).removeClass(options.focusClass);
|
731
|
+
input = createInput();
|
732
|
+
}
|
733
|
+
};
|
734
|
+
};
|
735
|
+
/*globals qq*/
|
736
|
+
qq.PasteSupport = function(o) {
|
737
|
+
"use strict";
|
738
|
+
|
739
|
+
var options, detachPasteHandler;
|
740
|
+
|
741
|
+
options = {
|
742
|
+
targetElement: null,
|
743
|
+
callbacks: {
|
744
|
+
log: function(message, level) {},
|
745
|
+
pasteReceived: function(blob) {}
|
746
|
+
}
|
747
|
+
};
|
748
|
+
|
749
|
+
function isImage(item) {
|
750
|
+
return item.type &&
|
751
|
+
item.type.indexOf("image/") === 0;
|
752
|
+
}
|
753
|
+
|
754
|
+
function registerPasteHandler() {
|
755
|
+
qq(options.targetElement).attach("paste", function(event) {
|
756
|
+
var clipboardData = event.clipboardData;
|
757
|
+
|
758
|
+
if (clipboardData) {
|
759
|
+
qq.each(clipboardData.items, function(idx, item) {
|
760
|
+
if (isImage(item)) {
|
761
|
+
var blob = item.getAsFile();
|
762
|
+
options.callbacks.pasteReceived(blob);
|
763
|
+
}
|
764
|
+
});
|
765
|
+
}
|
766
|
+
});
|
767
|
+
}
|
768
|
+
|
769
|
+
function unregisterPasteHandler() {
|
770
|
+
if (detachPasteHandler) {
|
771
|
+
detachPasteHandler();
|
772
|
+
}
|
773
|
+
}
|
774
|
+
|
775
|
+
qq.extend(options, o);
|
776
|
+
registerPasteHandler();
|
777
|
+
|
778
|
+
return {
|
779
|
+
reset: function() {
|
780
|
+
unregisterPasteHandler();
|
781
|
+
}
|
782
|
+
};
|
581
783
|
};
|
582
784
|
qq.FineUploaderBasic = function(o){
|
583
785
|
var that = this;
|
@@ -591,27 +793,40 @@ qq.FineUploaderBasic = function(o){
|
|
591
793
|
request: {
|
592
794
|
endpoint: '/server/upload',
|
593
795
|
params: {},
|
594
|
-
paramsInBody:
|
796
|
+
paramsInBody: true,
|
595
797
|
customHeaders: {},
|
596
|
-
forceMultipart:
|
597
|
-
inputName: 'qqfile'
|
798
|
+
forceMultipart: true,
|
799
|
+
inputName: 'qqfile',
|
800
|
+
uuidName: 'qquuid',
|
801
|
+
totalFileSizeName: 'qqtotalfilesize'
|
598
802
|
},
|
599
803
|
validation: {
|
600
804
|
allowedExtensions: [],
|
601
805
|
sizeLimit: 0,
|
602
806
|
minSizeLimit: 0,
|
807
|
+
itemLimit: 0,
|
603
808
|
stopOnFirstInvalidFile: true
|
604
809
|
},
|
605
810
|
callbacks: {
|
606
|
-
onSubmit: function(id,
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
811
|
+
onSubmit: function(id, name){},
|
812
|
+
onSubmitted: function(id, name){},
|
813
|
+
onComplete: function(id, name, responseJSON){},
|
814
|
+
onCancel: function(id, name){},
|
815
|
+
onUpload: function(id, name){},
|
816
|
+
onUploadChunk: function(id, name, chunkData){},
|
817
|
+
onResume: function(id, fileName, chunkData){},
|
818
|
+
onProgress: function(id, name, loaded, total){},
|
819
|
+
onError: function(id, name, reason, maybeXhr) {},
|
820
|
+
onAutoRetry: function(id, name, attemptNumber) {},
|
821
|
+
onManualRetry: function(id, name) {},
|
822
|
+
onValidateBatch: function(fileOrBlobData) {},
|
823
|
+
onValidate: function(fileOrBlobData) {},
|
824
|
+
onSubmitDelete: function(id) {},
|
825
|
+
onDelete: function(id){},
|
826
|
+
onDeleteComplete: function(id, xhr, isError){},
|
827
|
+
onPasteReceived: function(blob) {
|
828
|
+
return new qq.Promise().success();
|
829
|
+
}
|
615
830
|
},
|
616
831
|
messages: {
|
617
832
|
typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
|
@@ -619,6 +834,8 @@ qq.FineUploaderBasic = function(o){
|
|
619
834
|
minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
|
620
835
|
emptyError: "{file} is empty, please select files again without it.",
|
621
836
|
noFilesError: "No files to upload.",
|
837
|
+
tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
|
838
|
+
retryFailTooManyItems: "Retry failed - you have reached your file limit.",
|
622
839
|
onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
|
623
840
|
},
|
624
841
|
retry: {
|
@@ -630,6 +847,55 @@ qq.FineUploaderBasic = function(o){
|
|
630
847
|
classes: {
|
631
848
|
buttonHover: 'qq-upload-button-hover',
|
632
849
|
buttonFocus: 'qq-upload-button-focus'
|
850
|
+
},
|
851
|
+
chunking: {
|
852
|
+
enabled: false,
|
853
|
+
partSize: 2000000,
|
854
|
+
paramNames: {
|
855
|
+
partIndex: 'qqpartindex',
|
856
|
+
partByteOffset: 'qqpartbyteoffset',
|
857
|
+
chunkSize: 'qqchunksize',
|
858
|
+
totalFileSize: 'qqtotalfilesize',
|
859
|
+
totalParts: 'qqtotalparts',
|
860
|
+
filename: 'qqfilename'
|
861
|
+
}
|
862
|
+
},
|
863
|
+
resume: {
|
864
|
+
enabled: false,
|
865
|
+
id: null,
|
866
|
+
cookiesExpireIn: 7, //days
|
867
|
+
paramNames: {
|
868
|
+
resuming: "qqresume"
|
869
|
+
}
|
870
|
+
},
|
871
|
+
formatFileName: function(fileOrBlobName) {
|
872
|
+
if (fileOrBlobName.length > 33) {
|
873
|
+
fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
|
874
|
+
}
|
875
|
+
return fileOrBlobName;
|
876
|
+
},
|
877
|
+
text: {
|
878
|
+
sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
|
879
|
+
},
|
880
|
+
deleteFile : {
|
881
|
+
enabled: false,
|
882
|
+
endpoint: '/server/upload',
|
883
|
+
customHeaders: {},
|
884
|
+
params: {}
|
885
|
+
},
|
886
|
+
cors: {
|
887
|
+
expected: false,
|
888
|
+
sendCredentials: false
|
889
|
+
},
|
890
|
+
blobs: {
|
891
|
+
defaultName: 'misc_data',
|
892
|
+
paramNames: {
|
893
|
+
name: 'qqblobname'
|
894
|
+
}
|
895
|
+
},
|
896
|
+
paste: {
|
897
|
+
targetElement: null,
|
898
|
+
defaultName: 'pasted_image'
|
633
899
|
}
|
634
900
|
};
|
635
901
|
|
@@ -637,23 +903,30 @@ qq.FineUploaderBasic = function(o){
|
|
637
903
|
this._wrapCallbacks();
|
638
904
|
this._disposeSupport = new qq.DisposeSupport();
|
639
905
|
|
640
|
-
|
641
|
-
this.
|
642
|
-
|
643
|
-
this._storedFileIds = [];
|
644
|
-
|
906
|
+
this._filesInProgress = [];
|
907
|
+
this._storedIds = [];
|
645
908
|
this._autoRetries = [];
|
646
909
|
this._retryTimeouts = [];
|
647
910
|
this._preventRetries = [];
|
911
|
+
this._netFilesUploadedOrQueued = 0;
|
912
|
+
|
913
|
+
this._paramsStore = this._createParamsStore("request");
|
914
|
+
this._deleteFileParamsStore = this._createParamsStore("deleteFile");
|
648
915
|
|
649
|
-
this.
|
916
|
+
this._endpointStore = this._createEndpointStore("request");
|
917
|
+
this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
|
650
918
|
|
651
919
|
this._handler = this._createUploadHandler();
|
920
|
+
this._deleteHandler = this._createDeleteHandler();
|
652
921
|
|
653
922
|
if (this._options.button){
|
654
923
|
this._button = this._createUploadButton(this._options.button);
|
655
924
|
}
|
656
925
|
|
926
|
+
if (this._options.paste.targetElement) {
|
927
|
+
this._pasteHandler = this._createPasteHandler();
|
928
|
+
}
|
929
|
+
|
657
930
|
this._preventLeaveInProgress();
|
658
931
|
};
|
659
932
|
|
@@ -667,29 +940,52 @@ qq.FineUploaderBasic.prototype = {
|
|
667
940
|
|
668
941
|
}
|
669
942
|
},
|
670
|
-
setParams: function(params,
|
671
|
-
|
943
|
+
setParams: function(params, id) {
|
944
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
945
|
+
if (id == null) {
|
672
946
|
this._options.request.params = params;
|
673
947
|
}
|
674
948
|
else {
|
675
|
-
this._paramsStore.setParams(params,
|
949
|
+
this._paramsStore.setParams(params, id);
|
950
|
+
}
|
951
|
+
},
|
952
|
+
setDeleteFileParams: function(params, id) {
|
953
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
954
|
+
if (id == null) {
|
955
|
+
this._options.deleteFile.params = params;
|
956
|
+
}
|
957
|
+
else {
|
958
|
+
this._deleteFileParamsStore.setParams(params, id);
|
959
|
+
}
|
960
|
+
},
|
961
|
+
setEndpoint: function(endpoint, id) {
|
962
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
963
|
+
if (id == null) {
|
964
|
+
this._options.request.endpoint = endpoint;
|
965
|
+
}
|
966
|
+
else {
|
967
|
+
this._endpointStore.setEndpoint(endpoint, id);
|
676
968
|
}
|
677
969
|
},
|
678
970
|
getInProgress: function(){
|
679
|
-
return this._filesInProgress;
|
971
|
+
return this._filesInProgress.length;
|
680
972
|
},
|
681
973
|
uploadStoredFiles: function(){
|
682
974
|
"use strict";
|
683
|
-
|
684
|
-
|
685
|
-
|
975
|
+
var idToUpload;
|
976
|
+
|
977
|
+
while(this._storedIds.length) {
|
978
|
+
idToUpload = this._storedIds.shift();
|
979
|
+
this._filesInProgress.push(idToUpload);
|
980
|
+
this._handler.upload(idToUpload);
|
686
981
|
}
|
687
982
|
},
|
688
983
|
clearStoredFiles: function(){
|
689
|
-
this.
|
984
|
+
this._storedIds = [];
|
690
985
|
},
|
691
986
|
retry: function(id) {
|
692
987
|
if (this._onBeforeManualRetry(id)) {
|
988
|
+
this._netFilesUploadedOrQueued++;
|
693
989
|
this._handler.retry(id);
|
694
990
|
return true;
|
695
991
|
}
|
@@ -697,32 +993,49 @@ qq.FineUploaderBasic.prototype = {
|
|
697
993
|
return false;
|
698
994
|
}
|
699
995
|
},
|
700
|
-
cancel: function(
|
701
|
-
this._handler.cancel(
|
996
|
+
cancel: function(id) {
|
997
|
+
this._handler.cancel(id);
|
998
|
+
},
|
999
|
+
cancelAll: function() {
|
1000
|
+
var storedIdsCopy = [],
|
1001
|
+
self = this;
|
1002
|
+
|
1003
|
+
qq.extend(storedIdsCopy, this._storedIds);
|
1004
|
+
qq.each(storedIdsCopy, function(idx, storedFileId) {
|
1005
|
+
self.cancel(storedFileId);
|
1006
|
+
});
|
1007
|
+
|
1008
|
+
this._handler.cancelAll();
|
702
1009
|
},
|
703
1010
|
reset: function() {
|
704
1011
|
this.log("Resetting uploader...");
|
705
1012
|
this._handler.reset();
|
706
|
-
this._filesInProgress =
|
707
|
-
this.
|
1013
|
+
this._filesInProgress = [];
|
1014
|
+
this._storedIds = [];
|
708
1015
|
this._autoRetries = [];
|
709
1016
|
this._retryTimeouts = [];
|
710
1017
|
this._preventRetries = [];
|
711
1018
|
this._button.reset();
|
712
1019
|
this._paramsStore.reset();
|
1020
|
+
this._endpointStore.reset();
|
1021
|
+
this._netFilesUploadedOrQueued = 0;
|
1022
|
+
|
1023
|
+
if (this._pasteHandler) {
|
1024
|
+
this._pasteHandler.reset();
|
1025
|
+
}
|
713
1026
|
},
|
714
|
-
addFiles: function(
|
1027
|
+
addFiles: function(filesBlobDataOrInputs) {
|
715
1028
|
var self = this,
|
716
1029
|
verifiedFilesOrInputs = [],
|
717
1030
|
index, fileOrInput;
|
718
1031
|
|
719
|
-
if (
|
720
|
-
if (!window.FileList || !(
|
721
|
-
|
1032
|
+
if (filesBlobDataOrInputs) {
|
1033
|
+
if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
|
1034
|
+
filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
|
722
1035
|
}
|
723
1036
|
|
724
|
-
for (index = 0; index <
|
725
|
-
fileOrInput =
|
1037
|
+
for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
|
1038
|
+
fileOrInput = filesBlobDataOrInputs[index];
|
726
1039
|
|
727
1040
|
if (qq.isFileOrInput(fileOrInput)) {
|
728
1041
|
verifiedFilesOrInputs.push(fileOrInput);
|
@@ -733,9 +1046,66 @@ qq.FineUploaderBasic.prototype = {
|
|
733
1046
|
}
|
734
1047
|
|
735
1048
|
this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
|
736
|
-
this.
|
1049
|
+
this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
|
1050
|
+
}
|
1051
|
+
},
|
1052
|
+
addBlobs: function(blobDataOrArray) {
|
1053
|
+
if (blobDataOrArray) {
|
1054
|
+
var blobDataArray = [].concat(blobDataOrArray),
|
1055
|
+
verifiedBlobDataList = [],
|
1056
|
+
self = this;
|
1057
|
+
|
1058
|
+
qq.each(blobDataArray, function(idx, blobData) {
|
1059
|
+
if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
|
1060
|
+
verifiedBlobDataList.push({
|
1061
|
+
blob: blobData,
|
1062
|
+
name: self._options.blobs.defaultName
|
1063
|
+
});
|
1064
|
+
}
|
1065
|
+
else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
|
1066
|
+
verifiedBlobDataList.push(blobData);
|
1067
|
+
}
|
1068
|
+
else {
|
1069
|
+
self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
|
1070
|
+
}
|
1071
|
+
});
|
1072
|
+
|
1073
|
+
this._uploadFileOrBlobDataList(verifiedBlobDataList);
|
1074
|
+
}
|
1075
|
+
else {
|
1076
|
+
this.log("undefined or non-array parameter passed into addBlobs", "error");
|
1077
|
+
}
|
1078
|
+
},
|
1079
|
+
getUuid: function(id) {
|
1080
|
+
return this._handler.getUuid(id);
|
1081
|
+
},
|
1082
|
+
getResumableFilesData: function() {
|
1083
|
+
return this._handler.getResumableFilesData();
|
1084
|
+
},
|
1085
|
+
getSize: function(id) {
|
1086
|
+
return this._handler.getSize(id);
|
1087
|
+
},
|
1088
|
+
getName: function(id) {
|
1089
|
+
return this._handler.getName(id);
|
1090
|
+
},
|
1091
|
+
getFile: function(fileOrBlobId) {
|
1092
|
+
return this._handler.getFile(fileOrBlobId);
|
1093
|
+
},
|
1094
|
+
deleteFile: function(id) {
|
1095
|
+
this._onSubmitDelete(id);
|
1096
|
+
},
|
1097
|
+
setDeleteFileEndpoint: function(endpoint, id) {
|
1098
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
1099
|
+
if (id == null) {
|
1100
|
+
this._options.deleteFile.endpoint = endpoint;
|
1101
|
+
}
|
1102
|
+
else {
|
1103
|
+
this._deleteFileEndpointStore.setEndpoint(endpoint, id);
|
737
1104
|
}
|
738
1105
|
},
|
1106
|
+
getPromissoryCallbackNames: function() {
|
1107
|
+
return ["onPasteReceived"];
|
1108
|
+
},
|
739
1109
|
_createUploadButton: function(element){
|
740
1110
|
var self = this;
|
741
1111
|
|
@@ -754,52 +1124,59 @@ qq.FineUploaderBasic.prototype = {
|
|
754
1124
|
return button;
|
755
1125
|
},
|
756
1126
|
_createUploadHandler: function(){
|
757
|
-
var self = this
|
758
|
-
handlerClass;
|
759
|
-
|
760
|
-
if(qq.isXhrUploadSupported()){
|
761
|
-
handlerClass = 'UploadHandlerXhr';
|
762
|
-
} else {
|
763
|
-
handlerClass = 'UploadHandlerForm';
|
764
|
-
}
|
1127
|
+
var self = this;
|
765
1128
|
|
766
|
-
|
1129
|
+
return new qq.UploadHandler({
|
767
1130
|
debug: this._options.debug,
|
768
|
-
endpoint: this._options.request.endpoint,
|
769
1131
|
forceMultipart: this._options.request.forceMultipart,
|
770
1132
|
maxConnections: this._options.maxConnections,
|
771
1133
|
customHeaders: this._options.request.customHeaders,
|
772
1134
|
inputName: this._options.request.inputName,
|
1135
|
+
uuidParamName: this._options.request.uuidName,
|
1136
|
+
totalFileSizeParamName: this._options.request.totalFileSizeName,
|
1137
|
+
cors: this._options.cors,
|
773
1138
|
demoMode: this._options.demoMode,
|
774
|
-
log: this.log,
|
775
1139
|
paramsInBody: this._options.request.paramsInBody,
|
776
1140
|
paramsStore: this._paramsStore,
|
777
|
-
|
778
|
-
|
779
|
-
|
1141
|
+
endpointStore: this._endpointStore,
|
1142
|
+
chunking: this._options.chunking,
|
1143
|
+
resume: this._options.resume,
|
1144
|
+
blobs: this._options.blobs,
|
1145
|
+
log: function(str, level) {
|
1146
|
+
self.log(str, level);
|
1147
|
+
},
|
1148
|
+
onProgress: function(id, name, loaded, total){
|
1149
|
+
self._onProgress(id, name, loaded, total);
|
1150
|
+
self._options.callbacks.onProgress(id, name, loaded, total);
|
1151
|
+
},
|
1152
|
+
onComplete: function(id, name, result, xhr){
|
1153
|
+
self._onComplete(id, name, result, xhr);
|
1154
|
+
self._options.callbacks.onComplete(id, name, result);
|
1155
|
+
},
|
1156
|
+
onCancel: function(id, name){
|
1157
|
+
self._onCancel(id, name);
|
1158
|
+
self._options.callbacks.onCancel(id, name);
|
780
1159
|
},
|
781
|
-
|
782
|
-
self.
|
783
|
-
self._options.callbacks.
|
1160
|
+
onUpload: function(id, name){
|
1161
|
+
self._onUpload(id, name);
|
1162
|
+
self._options.callbacks.onUpload(id, name);
|
784
1163
|
},
|
785
|
-
|
786
|
-
self.
|
787
|
-
self._options.callbacks.onCancel(id, fileName);
|
1164
|
+
onUploadChunk: function(id, name, chunkData){
|
1165
|
+
self._options.callbacks.onUploadChunk(id, name, chunkData);
|
788
1166
|
},
|
789
|
-
|
790
|
-
self.
|
791
|
-
self._options.callbacks.onUpload(id, fileName, xhr);
|
1167
|
+
onResume: function(id, name, chunkData) {
|
1168
|
+
return self._options.callbacks.onResume(id, name, chunkData);
|
792
1169
|
},
|
793
|
-
onAutoRetry: function(id,
|
1170
|
+
onAutoRetry: function(id, name, responseJSON, xhr) {
|
794
1171
|
self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
|
795
1172
|
|
796
|
-
if (self._shouldAutoRetry(id,
|
797
|
-
self._maybeParseAndSendUploadError(id,
|
798
|
-
self._options.callbacks.onAutoRetry(id,
|
799
|
-
self._onBeforeAutoRetry(id,
|
1173
|
+
if (self._shouldAutoRetry(id, name, responseJSON)) {
|
1174
|
+
self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
|
1175
|
+
self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
|
1176
|
+
self._onBeforeAutoRetry(id, name);
|
800
1177
|
|
801
1178
|
self._retryTimeouts[id] = setTimeout(function() {
|
802
|
-
self._onAutoRetry(id,
|
1179
|
+
self._onAutoRetry(id, name, responseJSON)
|
803
1180
|
}, self._options.retry.autoAttemptDelay * 1000);
|
804
1181
|
|
805
1182
|
return true;
|
@@ -809,14 +1186,79 @@ qq.FineUploaderBasic.prototype = {
|
|
809
1186
|
}
|
810
1187
|
}
|
811
1188
|
});
|
1189
|
+
},
|
1190
|
+
_createDeleteHandler: function() {
|
1191
|
+
var self = this;
|
1192
|
+
|
1193
|
+
return new qq.DeleteFileAjaxRequestor({
|
1194
|
+
maxConnections: this._options.maxConnections,
|
1195
|
+
customHeaders: this._options.deleteFile.customHeaders,
|
1196
|
+
paramsStore: this._deleteFileParamsStore,
|
1197
|
+
endpointStore: this._deleteFileEndpointStore,
|
1198
|
+
demoMode: this._options.demoMode,
|
1199
|
+
cors: this._options.cors,
|
1200
|
+
log: function(str, level) {
|
1201
|
+
self.log(str, level);
|
1202
|
+
},
|
1203
|
+
onDelete: function(id) {
|
1204
|
+
self._onDelete(id);
|
1205
|
+
self._options.callbacks.onDelete(id);
|
1206
|
+
},
|
1207
|
+
onDeleteComplete: function(id, xhr, isError) {
|
1208
|
+
self._onDeleteComplete(id, xhr, isError);
|
1209
|
+
self._options.callbacks.onDeleteComplete(id, xhr, isError);
|
1210
|
+
}
|
1211
|
+
|
1212
|
+
});
|
1213
|
+
},
|
1214
|
+
_createPasteHandler: function() {
|
1215
|
+
var self = this;
|
1216
|
+
|
1217
|
+
return new qq.PasteSupport({
|
1218
|
+
targetElement: this._options.paste.targetElement,
|
1219
|
+
callbacks: {
|
1220
|
+
log: function(str, level) {
|
1221
|
+
self.log(str, level);
|
1222
|
+
},
|
1223
|
+
pasteReceived: function(blob) {
|
1224
|
+
var pasteReceivedCallback = self._options.callbacks.onPasteReceived,
|
1225
|
+
promise = pasteReceivedCallback(blob);
|
1226
|
+
|
1227
|
+
if (promise.then) {
|
1228
|
+
promise.then(function(successData) {
|
1229
|
+
self._handlePasteSuccess(blob, successData);
|
1230
|
+
}, function(failureData) {
|
1231
|
+
self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
|
1232
|
+
});
|
1233
|
+
}
|
1234
|
+
else {
|
1235
|
+
self.log("Promise contract not fulfilled in pasteReceived callback handler! Ignoring pasted item.", "error");
|
1236
|
+
}
|
1237
|
+
}
|
1238
|
+
}
|
1239
|
+
});
|
1240
|
+
},
|
1241
|
+
_handlePasteSuccess: function(blob, extSuppliedName) {
|
1242
|
+
var extension = blob.type.split("/")[1],
|
1243
|
+
name = extSuppliedName;
|
1244
|
+
|
1245
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
1246
|
+
if (name == null) {
|
1247
|
+
name = this._options.paste.defaultName;
|
1248
|
+
}
|
812
1249
|
|
813
|
-
|
1250
|
+
name += '.' + extension;
|
1251
|
+
|
1252
|
+
this.addBlobs({
|
1253
|
+
name: name,
|
1254
|
+
blob: blob
|
1255
|
+
});
|
814
1256
|
},
|
815
1257
|
_preventLeaveInProgress: function(){
|
816
1258
|
var self = this;
|
817
1259
|
|
818
1260
|
this._disposeSupport.attach(window, 'beforeunload', function(e){
|
819
|
-
if (!self._filesInProgress){return;}
|
1261
|
+
if (!self._filesInProgress.length){return;}
|
820
1262
|
|
821
1263
|
var e = e || window.event;
|
822
1264
|
// for ie, ff
|
@@ -825,61 +1267,105 @@ qq.FineUploaderBasic.prototype = {
|
|
825
1267
|
return self._options.messages.onLeave;
|
826
1268
|
});
|
827
1269
|
},
|
828
|
-
_onSubmit: function(id,
|
1270
|
+
_onSubmit: function(id, name) {
|
1271
|
+
this._netFilesUploadedOrQueued++;
|
1272
|
+
|
829
1273
|
if (this._options.autoUpload) {
|
830
|
-
this._filesInProgress
|
1274
|
+
this._filesInProgress.push(id);
|
831
1275
|
}
|
832
1276
|
},
|
833
|
-
_onProgress: function(id,
|
1277
|
+
_onProgress: function(id, name, loaded, total){
|
834
1278
|
},
|
835
|
-
_onComplete: function(id,
|
836
|
-
|
837
|
-
|
1279
|
+
_onComplete: function(id, name, result, xhr) {
|
1280
|
+
if (!result.success) {
|
1281
|
+
this._netFilesUploadedOrQueued--;
|
1282
|
+
}
|
1283
|
+
|
1284
|
+
this._removeFromFilesInProgress(id);
|
1285
|
+
this._maybeParseAndSendUploadError(id, name, result, xhr);
|
838
1286
|
},
|
839
|
-
_onCancel: function(id,
|
1287
|
+
_onCancel: function(id, name){
|
1288
|
+
this._netFilesUploadedOrQueued--;
|
1289
|
+
|
1290
|
+
this._removeFromFilesInProgress(id);
|
1291
|
+
|
840
1292
|
clearTimeout(this._retryTimeouts[id]);
|
841
1293
|
|
842
|
-
var
|
843
|
-
if (this._options.autoUpload
|
844
|
-
this.
|
1294
|
+
var storedItemIndex = qq.indexOf(this._storedIds, id);
|
1295
|
+
if (!this._options.autoUpload && storedItemIndex >= 0) {
|
1296
|
+
this._storedIds.splice(storedItemIndex, 1);
|
1297
|
+
}
|
1298
|
+
},
|
1299
|
+
_isDeletePossible: function() {
|
1300
|
+
return (this._options.deleteFile.enabled &&
|
1301
|
+
(!this._options.cors.expected ||
|
1302
|
+
(this._options.cors.expected && (qq.ie10() || !qq.ie()))
|
1303
|
+
)
|
1304
|
+
);
|
1305
|
+
},
|
1306
|
+
_onSubmitDelete: function(id) {
|
1307
|
+
if (this._isDeletePossible()) {
|
1308
|
+
if (this._options.callbacks.onSubmitDelete(id) !== false) {
|
1309
|
+
this._deleteHandler.sendDelete(id, this.getUuid(id));
|
1310
|
+
}
|
1311
|
+
}
|
1312
|
+
else {
|
1313
|
+
this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
|
1314
|
+
"due to CORS on a user agent that does not support pre-flighting.", "warn");
|
1315
|
+
return false;
|
1316
|
+
}
|
1317
|
+
},
|
1318
|
+
_onDelete: function(fileId) {},
|
1319
|
+
_onDeleteComplete: function(id, xhr, isError) {
|
1320
|
+
var name = this._handler.getName(id);
|
1321
|
+
|
1322
|
+
if (isError) {
|
1323
|
+
this.log("Delete request for '" + name + "' has failed.", "error");
|
1324
|
+
this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
|
845
1325
|
}
|
846
|
-
else
|
847
|
-
this.
|
1326
|
+
else {
|
1327
|
+
this._netFilesUploadedOrQueued--;
|
1328
|
+
this.log("Delete request for '" + name + "' has succeeded.");
|
848
1329
|
}
|
849
1330
|
},
|
850
|
-
|
1331
|
+
_removeFromFilesInProgress: function(id) {
|
1332
|
+
var index = qq.indexOf(this._filesInProgress, id);
|
1333
|
+
if (index >= 0) {
|
1334
|
+
this._filesInProgress.splice(index, 1);
|
1335
|
+
}
|
851
1336
|
},
|
1337
|
+
_onUpload: function(id, name){},
|
852
1338
|
_onInputChange: function(input){
|
853
|
-
if (
|
1339
|
+
if (qq.isXhrUploadSupported()){
|
854
1340
|
this.addFiles(input.files);
|
855
1341
|
} else {
|
856
|
-
|
857
|
-
this.addFiles(input);
|
858
|
-
}
|
1342
|
+
this.addFiles(input);
|
859
1343
|
}
|
860
1344
|
this._button.reset();
|
861
1345
|
},
|
862
|
-
_onBeforeAutoRetry: function(id,
|
863
|
-
this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " +
|
1346
|
+
_onBeforeAutoRetry: function(id, name) {
|
1347
|
+
this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
|
864
1348
|
},
|
865
|
-
_onAutoRetry: function(id,
|
866
|
-
this.log("Retrying " +
|
1349
|
+
_onAutoRetry: function(id, name, responseJSON) {
|
1350
|
+
this.log("Retrying " + name + "...");
|
867
1351
|
this._autoRetries[id]++;
|
868
1352
|
this._handler.retry(id);
|
869
1353
|
},
|
870
|
-
_shouldAutoRetry: function(id,
|
1354
|
+
_shouldAutoRetry: function(id, name, responseJSON) {
|
871
1355
|
if (!this._preventRetries[id] && this._options.retry.enableAuto) {
|
872
1356
|
if (this._autoRetries[id] === undefined) {
|
873
1357
|
this._autoRetries[id] = 0;
|
874
1358
|
}
|
875
1359
|
|
876
|
-
return this._autoRetries[id] < this._options.retry.maxAutoAttempts
|
1360
|
+
return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
|
877
1361
|
}
|
878
1362
|
|
879
1363
|
return false;
|
880
1364
|
},
|
881
1365
|
//return false if we should not attempt the requested retry
|
882
1366
|
_onBeforeManualRetry: function(id) {
|
1367
|
+
var itemLimit = this._options.validation.itemLimit;
|
1368
|
+
|
883
1369
|
if (this._preventRetries[id]) {
|
884
1370
|
this.log("Retries are forbidden for id " + id, 'warn');
|
885
1371
|
return false;
|
@@ -891,8 +1377,13 @@ qq.FineUploaderBasic.prototype = {
|
|
891
1377
|
return false;
|
892
1378
|
}
|
893
1379
|
|
1380
|
+
if (itemLimit > 0 && this._netFilesUploadedOrQueued+1 > itemLimit) {
|
1381
|
+
this._itemError("retryFailTooManyItems", "");
|
1382
|
+
return false;
|
1383
|
+
}
|
1384
|
+
|
894
1385
|
this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
|
895
|
-
this._filesInProgress
|
1386
|
+
this._filesInProgress.push(id);
|
896
1387
|
return true;
|
897
1388
|
}
|
898
1389
|
else {
|
@@ -900,29 +1391,28 @@ qq.FineUploaderBasic.prototype = {
|
|
900
1391
|
return false;
|
901
1392
|
}
|
902
1393
|
},
|
903
|
-
_maybeParseAndSendUploadError: function(id,
|
1394
|
+
_maybeParseAndSendUploadError: function(id, name, response, xhr) {
|
904
1395
|
//assuming no one will actually set the response code to something other than 200 and still set 'success' to true
|
905
1396
|
if (!response.success){
|
906
1397
|
if (xhr && xhr.status !== 200 && !response.error) {
|
907
|
-
this._options.callbacks.onError(id,
|
1398
|
+
this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
|
908
1399
|
}
|
909
1400
|
else {
|
910
1401
|
var errorReason = response.error ? response.error : "Upload failure reason unknown";
|
911
|
-
this._options.callbacks.onError(id,
|
1402
|
+
this._options.callbacks.onError(id, name, errorReason, xhr);
|
912
1403
|
}
|
913
1404
|
}
|
914
1405
|
},
|
915
|
-
|
916
|
-
var
|
1406
|
+
_uploadFileOrBlobDataList: function(fileOrBlobDataList){
|
1407
|
+
var index,
|
1408
|
+
validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
|
1409
|
+
batchValid = this._isBatchValid(validationDescriptors);
|
917
1410
|
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
for (index = 0; index < files.length; index++){
|
924
|
-
if (this._validateFile(files[index])){
|
925
|
-
this._uploadFile(files[index]);
|
1411
|
+
if (batchValid) {
|
1412
|
+
if (fileOrBlobDataList.length > 0) {
|
1413
|
+
for (index = 0; index < fileOrBlobDataList.length; index++){
|
1414
|
+
if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
|
1415
|
+
this._upload(fileOrBlobDataList[index]);
|
926
1416
|
} else {
|
927
1417
|
if (this._options.validation.stopOnFirstInvalidFile){
|
928
1418
|
return;
|
@@ -931,94 +1421,141 @@ qq.FineUploaderBasic.prototype = {
|
|
931
1421
|
}
|
932
1422
|
}
|
933
1423
|
else {
|
934
|
-
this.
|
1424
|
+
this._itemError("noFilesError", "");
|
935
1425
|
}
|
936
1426
|
}
|
937
1427
|
},
|
938
|
-
|
939
|
-
var id = this._handler.add(
|
940
|
-
var
|
1428
|
+
_upload: function(blobOrFileContainer){
|
1429
|
+
var id = this._handler.add(blobOrFileContainer);
|
1430
|
+
var name = this._handler.getName(id);
|
1431
|
+
|
1432
|
+
if (this._options.callbacks.onSubmit(id, name) !== false) {
|
1433
|
+
this._onSubmit(id, name);
|
1434
|
+
this._options.callbacks.onSubmitted(id, name);
|
941
1435
|
|
942
|
-
if (this._options.callbacks.onSubmit(id, fileName) !== false){
|
943
|
-
this._onSubmit(id, fileName);
|
944
1436
|
if (this._options.autoUpload) {
|
945
1437
|
this._handler.upload(id);
|
946
1438
|
}
|
947
1439
|
else {
|
948
|
-
this.
|
1440
|
+
this._storeForLater(id);
|
949
1441
|
}
|
950
1442
|
}
|
951
1443
|
},
|
952
|
-
|
953
|
-
this.
|
1444
|
+
_storeForLater: function(id) {
|
1445
|
+
this._storedIds.push(id);
|
954
1446
|
},
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
1447
|
+
_isBatchValid: function(validationDescriptors) {
|
1448
|
+
//first, defer the check to the callback (ask the integrator)
|
1449
|
+
var errorMessage,
|
1450
|
+
itemLimit = this._options.validation.itemLimit,
|
1451
|
+
proposedNetFilesUploadedOrQueued = this._netFilesUploadedOrQueued + validationDescriptors.length,
|
1452
|
+
batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
|
961
1453
|
|
962
|
-
if
|
963
|
-
|
964
|
-
|
1454
|
+
//if the callback hasn't rejected the batch, run some internal tests on the batch next
|
1455
|
+
if (batchValid) {
|
1456
|
+
if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
|
1457
|
+
batchValid = true;
|
1458
|
+
}
|
1459
|
+
else {
|
1460
|
+
batchValid = false;
|
1461
|
+
errorMessage = this._options.messages.tooManyItemsError
|
1462
|
+
.replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
|
1463
|
+
.replace(/\{itemLimit\}/g, itemLimit);
|
1464
|
+
this._batchError(errorMessage);
|
1465
|
+
}
|
1466
|
+
}
|
1467
|
+
|
1468
|
+
return batchValid;
|
1469
|
+
},
|
1470
|
+
_validateFileOrBlobData: function(fileOrBlobData){
|
1471
|
+
var validationDescriptor, name, size;
|
1472
|
+
|
1473
|
+
validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
|
1474
|
+
name = validationDescriptor.name;
|
1475
|
+
size = validationDescriptor.size;
|
1476
|
+
|
1477
|
+
if (this._options.callbacks.onValidate(validationDescriptor) === false) {
|
1478
|
+
return false;
|
1479
|
+
}
|
965
1480
|
|
966
|
-
if (!this._isAllowedExtension(name)){
|
967
|
-
this.
|
1481
|
+
if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
|
1482
|
+
this._itemError('typeError', name);
|
968
1483
|
return false;
|
969
1484
|
|
970
1485
|
}
|
971
1486
|
else if (size === 0){
|
972
|
-
this.
|
1487
|
+
this._itemError('emptyError', name);
|
973
1488
|
return false;
|
974
1489
|
|
975
1490
|
}
|
976
1491
|
else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
|
977
|
-
this.
|
1492
|
+
this._itemError('sizeError', name);
|
978
1493
|
return false;
|
979
1494
|
|
980
1495
|
}
|
981
1496
|
else if (size && size < this._options.validation.minSizeLimit){
|
982
|
-
this.
|
1497
|
+
this._itemError('minSizeError', name);
|
983
1498
|
return false;
|
984
1499
|
}
|
985
1500
|
|
986
1501
|
return true;
|
987
1502
|
},
|
988
|
-
|
989
|
-
var message = this._options.messages[code]
|
1503
|
+
_itemError: function(code, name) {
|
1504
|
+
var message = this._options.messages[code],
|
1505
|
+
allowedExtensions = [],
|
1506
|
+
extensionsForMessage;
|
1507
|
+
|
990
1508
|
function r(name, replacement){ message = message.replace(name, replacement); }
|
991
1509
|
|
992
|
-
|
1510
|
+
qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
|
1511
|
+
/**
|
1512
|
+
* If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
|
1513
|
+
* `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
|
1514
|
+
*/
|
1515
|
+
if (qq.isString(allowedExtension)) {
|
1516
|
+
allowedExtensions.push(allowedExtension);
|
1517
|
+
}
|
1518
|
+
});
|
1519
|
+
|
1520
|
+
extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
|
993
1521
|
|
994
|
-
r('{file}', this.
|
995
|
-
r('{extensions}',
|
1522
|
+
r('{file}', this._options.formatFileName(name));
|
1523
|
+
r('{extensions}', extensionsForMessage);
|
996
1524
|
r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
|
997
1525
|
r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
|
998
1526
|
|
999
|
-
this._options.callbacks.onError(null,
|
1527
|
+
this._options.callbacks.onError(null, name, message);
|
1000
1528
|
|
1001
1529
|
return message;
|
1002
1530
|
},
|
1003
|
-
|
1004
|
-
|
1005
|
-
name = name.slice(0, 19) + '...' + name.slice(-13);
|
1006
|
-
}
|
1007
|
-
return name;
|
1531
|
+
_batchError: function(message) {
|
1532
|
+
this._options.callbacks.onError(null, null, message);
|
1008
1533
|
},
|
1009
1534
|
_isAllowedExtension: function(fileName){
|
1010
|
-
var
|
1011
|
-
|
1012
|
-
: '';
|
1013
|
-
var allowed = this._options.validation.allowedExtensions;
|
1014
|
-
|
1015
|
-
if (!allowed.length){return true;}
|
1535
|
+
var allowed = this._options.validation.allowedExtensions,
|
1536
|
+
valid = false;
|
1016
1537
|
|
1017
|
-
|
1018
|
-
|
1538
|
+
if (!allowed.length) {
|
1539
|
+
return true;
|
1019
1540
|
}
|
1020
1541
|
|
1021
|
-
|
1542
|
+
qq.each(allowed, function(idx, allowedExt) {
|
1543
|
+
/**
|
1544
|
+
* If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
|
1545
|
+
* `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
|
1546
|
+
*/
|
1547
|
+
if (qq.isString(allowedExt)) {
|
1548
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
1549
|
+
var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
|
1550
|
+
|
1551
|
+
if (fileName.match(extRegex) != null) {
|
1552
|
+
valid = true;
|
1553
|
+
return false;
|
1554
|
+
}
|
1555
|
+
}
|
1556
|
+
});
|
1557
|
+
|
1558
|
+
return valid;
|
1022
1559
|
},
|
1023
1560
|
_formatSize: function(bytes){
|
1024
1561
|
var i = -1;
|
@@ -1027,7 +1564,7 @@ qq.FineUploaderBasic.prototype = {
|
|
1027
1564
|
i++;
|
1028
1565
|
} while (bytes > 99);
|
1029
1566
|
|
1030
|
-
return Math.max(bytes, 0.1).toFixed(1) + [
|
1567
|
+
return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
|
1031
1568
|
},
|
1032
1569
|
_wrapCallbacks: function() {
|
1033
1570
|
var self, safeCallback;
|
@@ -1039,9 +1576,9 @@ qq.FineUploaderBasic.prototype = {
|
|
1039
1576
|
return callback.apply(self, args);
|
1040
1577
|
}
|
1041
1578
|
catch (exception) {
|
1042
|
-
self.log("Caught exception in '" + name + "' callback - " + exception, 'error');
|
1579
|
+
self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
|
1043
1580
|
}
|
1044
|
-
}
|
1581
|
+
};
|
1045
1582
|
|
1046
1583
|
for (var prop in this._options.callbacks) {
|
1047
1584
|
(function() {
|
@@ -1050,40 +1587,50 @@ qq.FineUploaderBasic.prototype = {
|
|
1050
1587
|
callbackFunc = self._options.callbacks[callbackName];
|
1051
1588
|
self._options.callbacks[callbackName] = function() {
|
1052
1589
|
return safeCallback(callbackName, callbackFunc, arguments);
|
1053
|
-
}
|
1590
|
+
};
|
1054
1591
|
}());
|
1055
1592
|
}
|
1056
1593
|
},
|
1057
|
-
|
1594
|
+
_parseFileOrBlobDataName: function(fileOrBlobData) {
|
1058
1595
|
var name;
|
1059
1596
|
|
1060
|
-
if (
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1597
|
+
if (qq.isFileOrInput(fileOrBlobData)) {
|
1598
|
+
if (fileOrBlobData.value) {
|
1599
|
+
// it is a file input
|
1600
|
+
// get input value and remove path to normalize
|
1601
|
+
name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
|
1602
|
+
} else {
|
1603
|
+
// fix missing properties in Safari 4 and firefox 11.0a2
|
1604
|
+
name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
|
1605
|
+
}
|
1606
|
+
}
|
1607
|
+
else {
|
1608
|
+
name = fileOrBlobData.name;
|
1067
1609
|
}
|
1068
1610
|
|
1069
1611
|
return name;
|
1070
1612
|
},
|
1071
|
-
|
1613
|
+
_parseFileOrBlobDataSize: function(fileOrBlobData) {
|
1072
1614
|
var size;
|
1073
1615
|
|
1074
|
-
if (
|
1075
|
-
|
1076
|
-
|
1616
|
+
if (qq.isFileOrInput(fileOrBlobData)) {
|
1617
|
+
if (!fileOrBlobData.value){
|
1618
|
+
// fix missing properties in Safari 4 and firefox 11.0a2
|
1619
|
+
size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
|
1620
|
+
}
|
1621
|
+
}
|
1622
|
+
else {
|
1623
|
+
size = fileOrBlobData.blob.size;
|
1077
1624
|
}
|
1078
1625
|
|
1079
1626
|
return size;
|
1080
1627
|
},
|
1081
|
-
_getValidationDescriptor: function(
|
1628
|
+
_getValidationDescriptor: function(fileOrBlobData) {
|
1082
1629
|
var name, size, fileDescriptor;
|
1083
1630
|
|
1084
1631
|
fileDescriptor = {};
|
1085
|
-
name = this.
|
1086
|
-
size = this.
|
1632
|
+
name = this._parseFileOrBlobDataName(fileOrBlobData);
|
1633
|
+
size = this._parseFileOrBlobDataSize(fileOrBlobData);
|
1087
1634
|
|
1088
1635
|
fileDescriptor.name = name;
|
1089
1636
|
if (size) {
|
@@ -1093,35 +1640,35 @@ qq.FineUploaderBasic.prototype = {
|
|
1093
1640
|
return fileDescriptor;
|
1094
1641
|
},
|
1095
1642
|
_getValidationDescriptors: function(files) {
|
1096
|
-
var
|
1097
|
-
|
1098
|
-
fileDescriptors = [];
|
1643
|
+
var self = this,
|
1644
|
+
fileDescriptors = [];
|
1099
1645
|
|
1100
|
-
|
1101
|
-
fileDescriptors.push(
|
1102
|
-
}
|
1646
|
+
qq.each(files, function(idx, file) {
|
1647
|
+
fileDescriptors.push(self._getValidationDescriptor(file));
|
1648
|
+
});
|
1103
1649
|
|
1104
1650
|
return fileDescriptors;
|
1105
1651
|
},
|
1106
|
-
_createParamsStore: function() {
|
1652
|
+
_createParamsStore: function(type) {
|
1107
1653
|
var paramsStore = {},
|
1108
1654
|
self = this;
|
1109
1655
|
|
1110
1656
|
return {
|
1111
|
-
setParams: function(params,
|
1657
|
+
setParams: function(params, id) {
|
1112
1658
|
var paramsCopy = {};
|
1113
1659
|
qq.extend(paramsCopy, params);
|
1114
|
-
paramsStore[
|
1660
|
+
paramsStore[id] = paramsCopy;
|
1115
1661
|
},
|
1116
1662
|
|
1117
|
-
getParams: function(
|
1663
|
+
getParams: function(id) {
|
1664
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
1118
1665
|
var paramsCopy = {};
|
1119
1666
|
|
1120
|
-
if (
|
1121
|
-
qq.extend(paramsCopy, paramsStore[
|
1667
|
+
if (id != null && paramsStore[id]) {
|
1668
|
+
qq.extend(paramsCopy, paramsStore[id]);
|
1122
1669
|
}
|
1123
1670
|
else {
|
1124
|
-
qq.extend(paramsCopy, self._options.
|
1671
|
+
qq.extend(paramsCopy, self._options[type].params);
|
1125
1672
|
}
|
1126
1673
|
|
1127
1674
|
return paramsCopy;
|
@@ -1134,7 +1681,34 @@ qq.FineUploaderBasic.prototype = {
|
|
1134
1681
|
reset: function() {
|
1135
1682
|
paramsStore = {};
|
1136
1683
|
}
|
1137
|
-
}
|
1684
|
+
};
|
1685
|
+
},
|
1686
|
+
_createEndpointStore: function(type) {
|
1687
|
+
var endpointStore = {},
|
1688
|
+
self = this;
|
1689
|
+
|
1690
|
+
return {
|
1691
|
+
setEndpoint: function(endpoint, id) {
|
1692
|
+
endpointStore[id] = endpoint;
|
1693
|
+
},
|
1694
|
+
|
1695
|
+
getEndpoint: function(id) {
|
1696
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
1697
|
+
if (id != null && endpointStore[id]) {
|
1698
|
+
return endpointStore[id];
|
1699
|
+
}
|
1700
|
+
|
1701
|
+
return self._options[type].endpoint;
|
1702
|
+
},
|
1703
|
+
|
1704
|
+
remove: function(fileId) {
|
1705
|
+
return delete endpointStore[fileId];
|
1706
|
+
},
|
1707
|
+
|
1708
|
+
reset: function() {
|
1709
|
+
endpointStore = {};
|
1710
|
+
}
|
1711
|
+
};
|
1138
1712
|
}
|
1139
1713
|
};
|
1140
1714
|
/*globals qq, document*/
|
@@ -1212,7 +1786,9 @@ qq.DragAndDrop = function(o) {
|
|
1212
1786
|
dz.dropDisabled(true);
|
1213
1787
|
|
1214
1788
|
if (dataTransfer.files.length > 1 && !options.multiple) {
|
1789
|
+
options.callbacks.dropProcessing(false);
|
1215
1790
|
options.callbacks.error('tooManyFilesError', "");
|
1791
|
+
dz.dropDisabled(false);
|
1216
1792
|
}
|
1217
1793
|
else {
|
1218
1794
|
droppedFiles = [];
|
@@ -1513,6 +2089,7 @@ qq.FineUploader = function(o){
|
|
1513
2089
|
uploadButton: 'Upload a file',
|
1514
2090
|
cancelButton: 'Cancel',
|
1515
2091
|
retryButton: 'Retry',
|
2092
|
+
deleteButton: 'Delete',
|
1516
2093
|
failUpload: 'Upload failed',
|
1517
2094
|
dragZone: 'Drop files here to upload',
|
1518
2095
|
dropProcessing: 'Processing dropped files...',
|
@@ -1535,6 +2112,7 @@ qq.FineUploader = function(o){
|
|
1535
2112
|
'<span class="qq-upload-size"></span>' +
|
1536
2113
|
'<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
|
1537
2114
|
'<a class="qq-upload-retry" href="#">{retryButtonText}</a>' +
|
2115
|
+
'<a class="qq-upload-delete" href="#">{deleteButtonText}</a>' +
|
1538
2116
|
'<span class="qq-upload-status-text">{statusText}</span>' +
|
1539
2117
|
'</li>',
|
1540
2118
|
classes: {
|
@@ -1551,6 +2129,7 @@ qq.FineUploader = function(o){
|
|
1551
2129
|
retryable: 'qq-upload-retryable',
|
1552
2130
|
size: 'qq-upload-size',
|
1553
2131
|
cancel: 'qq-upload-cancel',
|
2132
|
+
deleteButton: 'qq-upload-delete',
|
1554
2133
|
retry: 'qq-upload-retry',
|
1555
2134
|
statusText: 'qq-upload-status-text',
|
1556
2135
|
|
@@ -1577,8 +2156,49 @@ qq.FineUploader = function(o){
|
|
1577
2156
|
autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
|
1578
2157
|
showButton: false
|
1579
2158
|
},
|
2159
|
+
deleteFile: {
|
2160
|
+
forceConfirm: false,
|
2161
|
+
confirmMessage: "Are you sure you want to delete {filename}?",
|
2162
|
+
deletingStatusText: "Deleting...",
|
2163
|
+
deletingFailedText: "Delete failed"
|
2164
|
+
|
2165
|
+
},
|
2166
|
+
display: {
|
2167
|
+
fileSizeOnSubmit: false
|
2168
|
+
},
|
2169
|
+
paste: {
|
2170
|
+
promptForName: false,
|
2171
|
+
namePromptMessage: "Please name this image"
|
2172
|
+
},
|
1580
2173
|
showMessage: function(message){
|
1581
|
-
|
2174
|
+
setTimeout(function() {
|
2175
|
+
window.alert(message);
|
2176
|
+
}, 0);
|
2177
|
+
},
|
2178
|
+
showConfirm: function(message, okCallback, cancelCallback) {
|
2179
|
+
setTimeout(function() {
|
2180
|
+
var result = window.confirm(message);
|
2181
|
+
if (result) {
|
2182
|
+
okCallback();
|
2183
|
+
}
|
2184
|
+
else if (cancelCallback) {
|
2185
|
+
cancelCallback();
|
2186
|
+
}
|
2187
|
+
}, 0);
|
2188
|
+
},
|
2189
|
+
showPrompt: function(message, defaultValue) {
|
2190
|
+
var promise = new qq.Promise(),
|
2191
|
+
retVal = window.prompt(message, defaultValue);
|
2192
|
+
|
2193
|
+
/*jshint eqeqeq: true, eqnull: true*/
|
2194
|
+
if (retVal != null && qq.trimStr(retVal).length > 0) {
|
2195
|
+
promise.success(retVal);
|
2196
|
+
}
|
2197
|
+
else {
|
2198
|
+
promise.failure("Undefined or invalid user-supplied value.");
|
2199
|
+
}
|
2200
|
+
|
2201
|
+
return promise;
|
1582
2202
|
}
|
1583
2203
|
}, true);
|
1584
2204
|
|
@@ -1593,6 +2213,7 @@ qq.FineUploader = function(o){
|
|
1593
2213
|
this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
|
1594
2214
|
this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
|
1595
2215
|
this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
|
2216
|
+
this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
|
1596
2217
|
this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
|
1597
2218
|
|
1598
2219
|
this._element = this._options.element;
|
@@ -1608,6 +2229,10 @@ qq.FineUploader = function(o){
|
|
1608
2229
|
this._bindCancelAndRetryEvents();
|
1609
2230
|
|
1610
2231
|
this._dnd = this._setupDragAndDrop();
|
2232
|
+
|
2233
|
+
if (this._options.paste.targetElement && this._options.paste.promptForName) {
|
2234
|
+
this._setupPastePrompt();
|
2235
|
+
}
|
1611
2236
|
};
|
1612
2237
|
|
1613
2238
|
// inherit from Basic Uploader
|
@@ -1634,11 +2259,6 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1634
2259
|
item = item.nextSibling;
|
1635
2260
|
}
|
1636
2261
|
},
|
1637
|
-
cancel: function(fileId) {
|
1638
|
-
qq.FineUploaderBasic.prototype.cancel.apply(this, arguments);
|
1639
|
-
var item = this.getItemByFileId(fileId);
|
1640
|
-
qq(item).remove();
|
1641
|
-
},
|
1642
2262
|
reset: function() {
|
1643
2263
|
qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
|
1644
2264
|
this._element.innerHTML = this._options.template;
|
@@ -1650,6 +2270,10 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1650
2270
|
this._dnd.dispose();
|
1651
2271
|
this._dnd = this._setupDragAndDrop();
|
1652
2272
|
},
|
2273
|
+
_removeFileItem: function(fileId) {
|
2274
|
+
var item = this.getItemByFileId(fileId);
|
2275
|
+
qq(item).remove();
|
2276
|
+
},
|
1653
2277
|
_setupDragAndDrop: function() {
|
1654
2278
|
var self = this,
|
1655
2279
|
dropProcessingEl = this._find(this._element, 'dropProcessing'),
|
@@ -1689,7 +2313,7 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1689
2313
|
}
|
1690
2314
|
},
|
1691
2315
|
error: function(code, filename) {
|
1692
|
-
self.
|
2316
|
+
self._itemError(code, filename);
|
1693
2317
|
},
|
1694
2318
|
log: function(message, level) {
|
1695
2319
|
self.log(message, level);
|
@@ -1705,8 +2329,8 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1705
2329
|
return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
|
1706
2330
|
|| (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
|
1707
2331
|
},
|
1708
|
-
|
1709
|
-
qq.FineUploaderBasic.prototype.
|
2332
|
+
_storeForLater: function(id) {
|
2333
|
+
qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
|
1710
2334
|
var item = this.getItemByFileId(id);
|
1711
2335
|
qq(this._find(item, 'spinner')).hide();
|
1712
2336
|
},
|
@@ -1721,15 +2345,15 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1721
2345
|
|
1722
2346
|
return element;
|
1723
2347
|
},
|
1724
|
-
_onSubmit: function(id,
|
2348
|
+
_onSubmit: function(id, name){
|
1725
2349
|
qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
|
1726
|
-
this._addToList(id,
|
2350
|
+
this._addToList(id, name);
|
1727
2351
|
},
|
1728
2352
|
// Update the progress bar & percentage as the file is uploaded
|
1729
|
-
_onProgress: function(id,
|
2353
|
+
_onProgress: function(id, name, loaded, total){
|
1730
2354
|
qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
|
1731
2355
|
|
1732
|
-
var item, progressBar,
|
2356
|
+
var item, progressBar, percent, cancelLink;
|
1733
2357
|
|
1734
2358
|
item = this.getItemByFileId(id);
|
1735
2359
|
progressBar = this._find(item, 'progressBar');
|
@@ -1742,24 +2366,20 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1742
2366
|
qq(progressBar).hide();
|
1743
2367
|
qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
|
1744
2368
|
|
1745
|
-
// If last byte was sent,
|
1746
|
-
|
2369
|
+
// If last byte was sent, display total file size
|
2370
|
+
this._displayFileSize(id);
|
1747
2371
|
}
|
1748
2372
|
else {
|
1749
|
-
// If still uploading, display percentage
|
1750
|
-
|
2373
|
+
// If still uploading, display percentage - total size is actually the total request(s) size
|
2374
|
+
this._displayFileSize(id, loaded, total);
|
1751
2375
|
|
1752
2376
|
qq(progressBar).css({display: 'block'});
|
1753
2377
|
}
|
1754
2378
|
|
1755
2379
|
// Update progress bar element
|
1756
2380
|
qq(progressBar).css({width: percent + '%'});
|
1757
|
-
|
1758
|
-
size = this._find(item, 'size');
|
1759
|
-
qq(size).css({display: 'inline'});
|
1760
|
-
qq(size).setText(text);
|
1761
2381
|
},
|
1762
|
-
_onComplete: function(id,
|
2382
|
+
_onComplete: function(id, name, result, xhr){
|
1763
2383
|
qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
|
1764
2384
|
|
1765
2385
|
var item = this.getItemByFileId(id);
|
@@ -1774,7 +2394,11 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1774
2394
|
}
|
1775
2395
|
qq(this._find(item, 'spinner')).hide();
|
1776
2396
|
|
1777
|
-
if (result.success){
|
2397
|
+
if (result.success) {
|
2398
|
+
if (this._isDeletePossible()) {
|
2399
|
+
this._showDeleteLink(id);
|
2400
|
+
}
|
2401
|
+
|
1778
2402
|
qq(item).addClass(this._classes.success);
|
1779
2403
|
if (this._classes.successIcon) {
|
1780
2404
|
this._find(item, 'finished').style.display = "inline-block";
|
@@ -1792,14 +2416,17 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1792
2416
|
this._controlFailureTextDisplay(item, result);
|
1793
2417
|
}
|
1794
2418
|
},
|
1795
|
-
_onUpload: function(id,
|
2419
|
+
_onUpload: function(id, name){
|
1796
2420
|
qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
|
1797
2421
|
|
1798
|
-
|
1799
|
-
|
2422
|
+
this._showSpinner(id);
|
2423
|
+
},
|
2424
|
+
_onCancel: function(id, name) {
|
2425
|
+
qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
|
2426
|
+
this._removeFileItem(id);
|
1800
2427
|
},
|
1801
2428
|
_onBeforeAutoRetry: function(id) {
|
1802
|
-
var item, progressBar,
|
2429
|
+
var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
|
1803
2430
|
|
1804
2431
|
qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
|
1805
2432
|
|
@@ -1826,17 +2453,75 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1826
2453
|
},
|
1827
2454
|
//return false if we should not attempt the requested retry
|
1828
2455
|
_onBeforeManualRetry: function(id) {
|
2456
|
+
var item = this.getItemByFileId(id);
|
2457
|
+
|
1829
2458
|
if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
|
1830
|
-
var item = this.getItemByFileId(id);
|
1831
2459
|
this._find(item, 'progressBar').style.width = 0;
|
1832
2460
|
qq(item).removeClass(this._classes.fail);
|
1833
|
-
this.
|
2461
|
+
qq(this._find(item, 'statusText')).clearText();
|
2462
|
+
this._showSpinner(id);
|
1834
2463
|
this._showCancelLink(item);
|
1835
2464
|
return true;
|
1836
2465
|
}
|
1837
|
-
|
2466
|
+
else {
|
2467
|
+
qq(item).addClass(this._classes.retryable);
|
2468
|
+
return false;
|
2469
|
+
}
|
2470
|
+
},
|
2471
|
+
_onSubmitDelete: function(id) {
|
2472
|
+
if (this._isDeletePossible()) {
|
2473
|
+
if (this._options.callbacks.onSubmitDelete(id) !== false) {
|
2474
|
+
if (this._options.deleteFile.forceConfirm) {
|
2475
|
+
this._showDeleteConfirm(id);
|
2476
|
+
}
|
2477
|
+
else {
|
2478
|
+
this._sendDeleteRequest(id);
|
2479
|
+
}
|
2480
|
+
}
|
2481
|
+
}
|
2482
|
+
else {
|
2483
|
+
this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
|
2484
|
+
return false;
|
2485
|
+
}
|
2486
|
+
},
|
2487
|
+
_onDeleteComplete: function(id, xhr, isError) {
|
2488
|
+
qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
|
2489
|
+
|
2490
|
+
var item = this.getItemByFileId(id),
|
2491
|
+
spinnerEl = this._find(item, 'spinner'),
|
2492
|
+
statusTextEl = this._find(item, 'statusText');
|
2493
|
+
|
2494
|
+
qq(spinnerEl).hide();
|
2495
|
+
|
2496
|
+
if (isError) {
|
2497
|
+
qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
|
2498
|
+
this._showDeleteLink(id);
|
2499
|
+
}
|
2500
|
+
else {
|
2501
|
+
this._removeFileItem(id);
|
2502
|
+
}
|
2503
|
+
},
|
2504
|
+
_sendDeleteRequest: function(id) {
|
2505
|
+
var item = this.getItemByFileId(id),
|
2506
|
+
deleteLink = this._find(item, 'deleteButton'),
|
2507
|
+
statusTextEl = this._find(item, 'statusText');
|
2508
|
+
|
2509
|
+
qq(deleteLink).hide();
|
2510
|
+
this._showSpinner(id);
|
2511
|
+
qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
|
2512
|
+
this._deleteHandler.sendDelete(id, this.getUuid(id));
|
1838
2513
|
},
|
1839
|
-
|
2514
|
+
_showDeleteConfirm: function(id) {
|
2515
|
+
var fileName = this._handler.getName(id),
|
2516
|
+
confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
|
2517
|
+
uuid = this.getUuid(id),
|
2518
|
+
self = this;
|
2519
|
+
|
2520
|
+
this._options.showConfirm(confirmMessage, function() {
|
2521
|
+
self._sendDeleteRequest(id);
|
2522
|
+
});
|
2523
|
+
},
|
2524
|
+
_addToList: function(id, name){
|
1840
2525
|
var item = qq.toElement(this._options.fileTemplate);
|
1841
2526
|
if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
|
1842
2527
|
var cancelLink = this._find(item, 'cancel');
|
@@ -1846,15 +2531,36 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1846
2531
|
item.qqFileId = id;
|
1847
2532
|
|
1848
2533
|
var fileElement = this._find(item, 'file');
|
1849
|
-
qq(fileElement).setText(this.
|
2534
|
+
qq(fileElement).setText(this._options.formatFileName(name));
|
1850
2535
|
qq(this._find(item, 'size')).hide();
|
1851
|
-
if (!this._options.multiple)
|
2536
|
+
if (!this._options.multiple) {
|
2537
|
+
this._handler.cancelAll();
|
2538
|
+
this._clearList();
|
2539
|
+
}
|
2540
|
+
|
1852
2541
|
this._listElement.appendChild(item);
|
2542
|
+
|
2543
|
+
if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
|
2544
|
+
this._displayFileSize(id);
|
2545
|
+
}
|
1853
2546
|
},
|
1854
2547
|
_clearList: function(){
|
1855
2548
|
this._listElement.innerHTML = '';
|
1856
2549
|
this.clearStoredFiles();
|
1857
2550
|
},
|
2551
|
+
_displayFileSize: function(id, loadedSize, totalSize) {
|
2552
|
+
var item = this.getItemByFileId(id),
|
2553
|
+
size = this.getSize(id),
|
2554
|
+
sizeForDisplay = this._formatSize(size),
|
2555
|
+
sizeEl = this._find(item, 'size');
|
2556
|
+
|
2557
|
+
if (loadedSize !== undefined && totalSize !== undefined) {
|
2558
|
+
sizeForDisplay = this._formatProgress(loadedSize, totalSize);
|
2559
|
+
}
|
2560
|
+
|
2561
|
+
qq(sizeEl).css({display: 'inline'});
|
2562
|
+
qq(sizeEl).setText(sizeForDisplay);
|
2563
|
+
},
|
1858
2564
|
/**
|
1859
2565
|
* delegate click event for cancel & retry links
|
1860
2566
|
**/
|
@@ -1866,15 +2572,18 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1866
2572
|
e = e || window.event;
|
1867
2573
|
var target = e.target || e.srcElement;
|
1868
2574
|
|
1869
|
-
if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry)){
|
2575
|
+
if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
|
1870
2576
|
qq.preventDefault(e);
|
1871
2577
|
|
1872
2578
|
var item = target.parentNode;
|
1873
|
-
while(item.qqFileId
|
2579
|
+
while(item.qqFileId === undefined) {
|
1874
2580
|
item = target = target.parentNode;
|
1875
2581
|
}
|
1876
2582
|
|
1877
|
-
if (qq(target).hasClass(self._classes.
|
2583
|
+
if (qq(target).hasClass(self._classes.deleteButton)) {
|
2584
|
+
self.deleteFile(item.qqFileId);
|
2585
|
+
}
|
2586
|
+
else if (qq(target).hasClass(self._classes.cancel)) {
|
1878
2587
|
self.cancel(item.qqFileId);
|
1879
2588
|
}
|
1880
2589
|
else {
|
@@ -1924,298 +2633,634 @@ qq.extend(qq.FineUploader.prototype, {
|
|
1924
2633
|
this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
|
1925
2634
|
}
|
1926
2635
|
},
|
1927
|
-
//TODO turn this into a real tooltip, with click trigger (so it is usable on mobile devices). See case #355 for details.
|
1928
2636
|
_showTooltip: function(item, text) {
|
1929
2637
|
item.title = text;
|
1930
2638
|
},
|
1931
|
-
_showSpinner: function(
|
1932
|
-
var
|
2639
|
+
_showSpinner: function(id) {
|
2640
|
+
var item = this.getItemByFileId(id),
|
2641
|
+
spinnerEl = this._find(item, 'spinner');
|
2642
|
+
|
1933
2643
|
spinnerEl.style.display = "inline-block";
|
1934
2644
|
},
|
1935
2645
|
_showCancelLink: function(item) {
|
1936
2646
|
if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
|
1937
2647
|
var cancelLink = this._find(item, 'cancel');
|
1938
|
-
|
2648
|
+
|
2649
|
+
qq(cancelLink).css({display: 'inline'});
|
1939
2650
|
}
|
1940
2651
|
},
|
1941
|
-
|
1942
|
-
var
|
2652
|
+
_showDeleteLink: function(id) {
|
2653
|
+
var item = this.getItemByFileId(id),
|
2654
|
+
deleteLink = this._find(item, 'deleteButton');
|
2655
|
+
|
2656
|
+
qq(deleteLink).css({display: 'inline'});
|
2657
|
+
},
|
2658
|
+
_itemError: function(code, name){
|
2659
|
+
var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
|
2660
|
+
this._options.showMessage(message);
|
2661
|
+
},
|
2662
|
+
_batchError: function(message) {
|
2663
|
+
qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
|
1943
2664
|
this._options.showMessage(message);
|
2665
|
+
},
|
2666
|
+
_setupPastePrompt: function() {
|
2667
|
+
var self = this;
|
2668
|
+
|
2669
|
+
this._options.callbacks.onPasteReceived = function() {
|
2670
|
+
var message = self._options.paste.namePromptMessage,
|
2671
|
+
defaultVal = self._options.paste.defaultName;
|
2672
|
+
|
2673
|
+
return self._options.showPrompt(message, defaultVal);
|
2674
|
+
};
|
1944
2675
|
}
|
1945
2676
|
});
|
1946
|
-
/**
|
1947
|
-
|
1948
|
-
*/
|
1949
|
-
qq.
|
1950
|
-
|
1951
|
-
this._options = {
|
1952
|
-
debug: false,
|
1953
|
-
endpoint: '/upload.php',
|
1954
|
-
paramsInBody: false,
|
1955
|
-
// maximum number of concurrent uploads
|
1956
|
-
maxConnections: 999,
|
1957
|
-
log: function(str, level) {},
|
1958
|
-
onProgress: function(id, fileName, loaded, total){},
|
1959
|
-
onComplete: function(id, fileName, response, xhr){},
|
1960
|
-
onCancel: function(id, fileName){},
|
1961
|
-
onUpload: function(id, fileName, xhr){},
|
1962
|
-
onAutoRetry: function(id, fileName, response, xhr){}
|
2677
|
+
/** Generic class for sending non-upload ajax requests and handling the associated responses **/
|
2678
|
+
//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
|
2679
|
+
/*globals qq, XMLHttpRequest*/
|
2680
|
+
qq.AjaxRequestor = function(o) {
|
2681
|
+
"use strict";
|
1963
2682
|
|
1964
|
-
|
1965
|
-
|
2683
|
+
var log, shouldParamsBeInQueryString,
|
2684
|
+
queue = [],
|
2685
|
+
requestState = [],
|
2686
|
+
options = {
|
2687
|
+
method: 'POST',
|
2688
|
+
maxConnections: 3,
|
2689
|
+
customHeaders: {},
|
2690
|
+
endpointStore: {},
|
2691
|
+
paramsStore: {},
|
2692
|
+
successfulResponseCodes: [200],
|
2693
|
+
demoMode: false,
|
2694
|
+
cors: {
|
2695
|
+
expected: false,
|
2696
|
+
sendCredentials: false
|
2697
|
+
},
|
2698
|
+
log: function(str, level) {},
|
2699
|
+
onSend: function(id) {},
|
2700
|
+
onComplete: function(id, xhr, isError) {},
|
2701
|
+
onCancel: function(id) {}
|
2702
|
+
};
|
1966
2703
|
|
1967
|
-
|
2704
|
+
qq.extend(options, o);
|
2705
|
+
log = options.log;
|
2706
|
+
shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
|
1968
2707
|
|
1969
|
-
this.log = this._options.log;
|
1970
|
-
};
|
1971
|
-
qq.UploadHandlerAbstract.prototype = {
|
1972
|
-
/**
|
1973
|
-
* Adds file or file input to the queue
|
1974
|
-
* @returns id
|
1975
|
-
**/
|
1976
|
-
add: function(file){},
|
1977
|
-
/**
|
1978
|
-
* Sends the file identified by id
|
1979
|
-
*/
|
1980
|
-
upload: function(id){
|
1981
|
-
var len = this._queue.push(id);
|
1982
2708
|
|
1983
|
-
// if too many active uploads, wait...
|
1984
|
-
if (len <= this._options.maxConnections){
|
1985
|
-
this._upload(id);
|
1986
|
-
}
|
1987
|
-
},
|
1988
|
-
retry: function(id) {
|
1989
|
-
var i = qq.indexOf(this._queue, id);
|
1990
|
-
if (i >= 0) {
|
1991
|
-
this._upload(id);
|
1992
|
-
}
|
1993
|
-
else {
|
1994
|
-
this.upload(id);
|
1995
|
-
}
|
1996
|
-
},
|
1997
|
-
/**
|
1998
|
-
* Cancels file upload by id
|
1999
|
-
*/
|
2000
|
-
cancel: function(id){
|
2001
|
-
this.log('Cancelling ' + id);
|
2002
|
-
this._options.paramsStore.remove(id);
|
2003
|
-
this._cancel(id);
|
2004
|
-
this._dequeue(id);
|
2005
|
-
},
|
2006
2709
|
/**
|
2007
|
-
*
|
2710
|
+
* Removes element from queue, sends next request
|
2008
2711
|
*/
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2712
|
+
function dequeue(id) {
|
2713
|
+
var i = qq.indexOf(queue, id),
|
2714
|
+
max = options.maxConnections,
|
2715
|
+
nextId;
|
2716
|
+
|
2717
|
+
delete requestState[id];
|
2718
|
+
queue.splice(i, 1);
|
2719
|
+
|
2720
|
+
if (queue.length >= max && i < max){
|
2721
|
+
nextId = queue[max-1];
|
2722
|
+
sendRequest(nextId);
|
2012
2723
|
}
|
2013
|
-
|
2014
|
-
},
|
2015
|
-
/**
|
2016
|
-
* Returns name of the file identified by id
|
2017
|
-
*/
|
2018
|
-
getName: function(id){},
|
2019
|
-
/**
|
2020
|
-
* Returns size of the file identified by id
|
2021
|
-
*/
|
2022
|
-
getSize: function(id){},
|
2023
|
-
/**
|
2024
|
-
* Returns id of files being uploaded or
|
2025
|
-
* waiting for their turn
|
2026
|
-
*/
|
2027
|
-
getQueue: function(){
|
2028
|
-
return this._queue;
|
2029
|
-
},
|
2030
|
-
reset: function() {
|
2031
|
-
this.log('Resetting upload handler');
|
2032
|
-
this._queue = [];
|
2033
|
-
},
|
2034
|
-
/**
|
2035
|
-
* Actual upload method
|
2036
|
-
*/
|
2037
|
-
_upload: function(id){},
|
2038
|
-
/**
|
2039
|
-
* Actual cancel method
|
2040
|
-
*/
|
2041
|
-
_cancel: function(id){},
|
2042
|
-
/**
|
2043
|
-
* Removes element from queue, starts upload of next
|
2044
|
-
*/
|
2045
|
-
_dequeue: function(id){
|
2046
|
-
var i = qq.indexOf(this._queue, id);
|
2047
|
-
this._queue.splice(i, 1);
|
2724
|
+
}
|
2048
2725
|
|
2049
|
-
|
2726
|
+
function onComplete(id) {
|
2727
|
+
var xhr = requestState[id].xhr,
|
2728
|
+
method = getMethod(),
|
2729
|
+
isError = false;
|
2050
2730
|
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2731
|
+
dequeue(id);
|
2732
|
+
|
2733
|
+
if (!isResponseSuccessful(xhr.status)) {
|
2734
|
+
isError = true;
|
2735
|
+
log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
|
2054
2736
|
}
|
2055
|
-
},
|
2056
|
-
/**
|
2057
|
-
* Determine if the file exists.
|
2058
|
-
*/
|
2059
|
-
isValid: function(id) {}
|
2060
|
-
};
|
2061
|
-
/**
|
2062
|
-
* Class for uploading files using form and iframe
|
2063
|
-
* @inherits qq.UploadHandlerAbstract
|
2064
|
-
*/
|
2065
|
-
qq.UploadHandlerForm = function(o){
|
2066
|
-
qq.UploadHandlerAbstract.apply(this, arguments);
|
2067
2737
|
|
2068
|
-
|
2069
|
-
|
2070
|
-
};
|
2071
|
-
// @inherits qq.UploadHandlerAbstract
|
2072
|
-
qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
|
2738
|
+
options.onComplete(id, xhr, isError);
|
2739
|
+
}
|
2073
2740
|
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2741
|
+
function sendRequest(id) {
|
2742
|
+
var xhr = new XMLHttpRequest(),
|
2743
|
+
method = getMethod(),
|
2744
|
+
params = {},
|
2745
|
+
url;
|
2078
2746
|
|
2079
|
-
|
2747
|
+
options.onSend(id);
|
2080
2748
|
|
2081
|
-
|
2082
|
-
|
2083
|
-
qq(fileInput).remove();
|
2749
|
+
if (options.paramsStore.getParams) {
|
2750
|
+
params = options.paramsStore.getParams(id);
|
2084
2751
|
}
|
2085
2752
|
|
2086
|
-
|
2087
|
-
},
|
2088
|
-
getName: function(id){
|
2089
|
-
// get input value and remove path to normalize
|
2090
|
-
return this._inputs[id].value.replace(/.*(\/|\\)/, "");
|
2091
|
-
},
|
2092
|
-
isValid: function(id) {
|
2093
|
-
return this._inputs[id] !== undefined;
|
2094
|
-
},
|
2095
|
-
reset: function() {
|
2096
|
-
qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments);
|
2097
|
-
this._inputs = {};
|
2098
|
-
this._detach_load_events = {};
|
2099
|
-
},
|
2100
|
-
_cancel: function(id){
|
2101
|
-
this._options.onCancel(id, this.getName(id));
|
2753
|
+
url = createUrl(id, params);
|
2102
2754
|
|
2103
|
-
|
2104
|
-
|
2755
|
+
requestState[id].xhr = xhr;
|
2756
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id);
|
2757
|
+
xhr.open(method, url, true);
|
2105
2758
|
|
2106
|
-
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2111
|
-
iframe.setAttribute('src', 'javascript:false;');
|
2759
|
+
if (options.cors.expected && options.cors.sendCredentials) {
|
2760
|
+
xhr.withCredentials = true;
|
2761
|
+
}
|
2762
|
+
|
2763
|
+
setHeaders(id);
|
2112
2764
|
|
2113
|
-
|
2765
|
+
log('Sending ' + method + " request for " + id);
|
2766
|
+
if (!shouldParamsBeInQueryString && params) {
|
2767
|
+
xhr.send(qq.obj2url(params, ""));
|
2114
2768
|
}
|
2115
|
-
|
2116
|
-
|
2117
|
-
|
2118
|
-
|
2769
|
+
else {
|
2770
|
+
xhr.send();
|
2771
|
+
}
|
2772
|
+
}
|
2119
2773
|
|
2120
|
-
|
2121
|
-
|
2774
|
+
function createUrl(id, params) {
|
2775
|
+
var endpoint = options.endpointStore.getEndpoint(id),
|
2776
|
+
addToPath = requestState[id].addToPath;
|
2777
|
+
|
2778
|
+
if (addToPath !== undefined) {
|
2779
|
+
endpoint += "/" + addToPath;
|
2122
2780
|
}
|
2123
2781
|
|
2124
|
-
|
2782
|
+
if (shouldParamsBeInQueryString && params) {
|
2783
|
+
return qq.obj2url(params, endpoint);
|
2784
|
+
}
|
2785
|
+
else {
|
2786
|
+
return endpoint;
|
2787
|
+
}
|
2788
|
+
}
|
2125
2789
|
|
2126
|
-
|
2127
|
-
var
|
2128
|
-
form.appendChild(input);
|
2790
|
+
function getReadyStateChangeHandler(id) {
|
2791
|
+
var xhr = requestState[id].xhr;
|
2129
2792
|
|
2130
|
-
|
2131
|
-
|
2132
|
-
|
2793
|
+
return function() {
|
2794
|
+
if (xhr.readyState === 4) {
|
2795
|
+
onComplete(id, xhr);
|
2796
|
+
}
|
2797
|
+
};
|
2798
|
+
}
|
2133
2799
|
|
2134
|
-
|
2800
|
+
function setHeaders(id) {
|
2801
|
+
var xhr = requestState[id].xhr,
|
2802
|
+
customHeaders = options.customHeaders;
|
2135
2803
|
|
2136
|
-
|
2137
|
-
|
2138
|
-
self._detach_load_events[id]();
|
2139
|
-
delete self._detach_load_events[id];
|
2140
|
-
qq(iframe).remove();
|
2141
|
-
}, 1);
|
2804
|
+
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
2805
|
+
xhr.setRequestHeader("Cache-Control", "no-cache");
|
2142
2806
|
|
2143
|
-
|
2144
|
-
|
2145
|
-
return;
|
2146
|
-
}
|
2147
|
-
}
|
2148
|
-
self._options.onComplete(id, fileName, response);
|
2149
|
-
self._dequeue(id);
|
2807
|
+
qq.each(customHeaders, function(name, val) {
|
2808
|
+
xhr.setRequestHeader(name, val);
|
2150
2809
|
});
|
2810
|
+
}
|
2151
2811
|
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2812
|
+
function cancelRequest(id) {
|
2813
|
+
var xhr = requestState[id].xhr,
|
2814
|
+
method = getMethod();
|
2155
2815
|
|
2156
|
-
|
2157
|
-
|
2158
|
-
|
2159
|
-
|
2160
|
-
this._detach_load_events[iframe.id] = qq(iframe).attach('load', function(){
|
2161
|
-
self.log('Received response for ' + iframe.id);
|
2816
|
+
if (xhr) {
|
2817
|
+
xhr.onreadystatechange = null;
|
2818
|
+
xhr.abort();
|
2819
|
+
dequeue(id);
|
2162
2820
|
|
2163
|
-
|
2164
|
-
|
2165
|
-
// event fires
|
2166
|
-
if (!iframe.parentNode){
|
2167
|
-
return;
|
2168
|
-
}
|
2821
|
+
log('Cancelled ' + method + " for " + id);
|
2822
|
+
options.onCancel(id);
|
2169
2823
|
|
2170
|
-
|
2171
|
-
|
2172
|
-
if (iframe.contentDocument &&
|
2173
|
-
iframe.contentDocument.body &&
|
2174
|
-
iframe.contentDocument.body.innerHTML == "false"){
|
2175
|
-
// In Opera event is fired second time
|
2176
|
-
// when body.innerHTML changed from false
|
2177
|
-
// to server response approx. after 1 sec
|
2178
|
-
// when we upload file with iframe
|
2179
|
-
return;
|
2180
|
-
}
|
2181
|
-
}
|
2182
|
-
catch (error) {
|
2183
|
-
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
2184
|
-
self.log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
|
2185
|
-
}
|
2824
|
+
return true;
|
2825
|
+
}
|
2186
2826
|
|
2187
|
-
|
2188
|
-
|
2189
|
-
},
|
2190
|
-
/**
|
2191
|
-
* Returns json object received by iframe from server.
|
2192
|
-
*/
|
2193
|
-
_getIframeContentJSON: function(iframe){
|
2194
|
-
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
2195
|
-
try {
|
2196
|
-
// iframe.contentWindow.document - for IE<7
|
2197
|
-
var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
|
2198
|
-
response;
|
2827
|
+
return false;
|
2828
|
+
}
|
2199
2829
|
|
2200
|
-
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
response = eval("(" + innerHTML + ")");
|
2208
|
-
} catch(error){
|
2209
|
-
this.log('Error when attempting to parse form upload response (' + error + ")", 'error');
|
2210
|
-
response = {success: false};
|
2830
|
+
function isResponseSuccessful(responseCode) {
|
2831
|
+
return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
|
2832
|
+
}
|
2833
|
+
|
2834
|
+
function getMethod() {
|
2835
|
+
if (options.demoMode) {
|
2836
|
+
return "GET";
|
2211
2837
|
}
|
2212
2838
|
|
2213
|
-
return
|
2214
|
-
}
|
2839
|
+
return options.method;
|
2840
|
+
}
|
2841
|
+
|
2842
|
+
|
2843
|
+
return {
|
2844
|
+
send: function(id, addToPath) {
|
2845
|
+
requestState[id] = {
|
2846
|
+
addToPath: addToPath
|
2847
|
+
};
|
2848
|
+
|
2849
|
+
var len = queue.push(id);
|
2850
|
+
|
2851
|
+
// if too many active connections, wait...
|
2852
|
+
if (len <= options.maxConnections){
|
2853
|
+
sendRequest(id);
|
2854
|
+
}
|
2855
|
+
},
|
2856
|
+
cancel: function(id) {
|
2857
|
+
return cancelRequest(id);
|
2858
|
+
}
|
2859
|
+
};
|
2860
|
+
};
|
2861
|
+
/** Generic class for sending non-upload ajax requests and handling the associated responses **/
|
2862
|
+
/*globals qq, XMLHttpRequest*/
|
2863
|
+
qq.DeleteFileAjaxRequestor = function(o) {
|
2864
|
+
"use strict";
|
2865
|
+
|
2866
|
+
var requestor,
|
2867
|
+
options = {
|
2868
|
+
endpointStore: {},
|
2869
|
+
maxConnections: 3,
|
2870
|
+
customHeaders: {},
|
2871
|
+
paramsStore: {},
|
2872
|
+
demoMode: false,
|
2873
|
+
cors: {
|
2874
|
+
expected: false,
|
2875
|
+
sendCredentials: false
|
2876
|
+
},
|
2877
|
+
log: function(str, level) {},
|
2878
|
+
onDelete: function(id) {},
|
2879
|
+
onDeleteComplete: function(id, xhr, isError) {}
|
2880
|
+
};
|
2881
|
+
|
2882
|
+
qq.extend(options, o);
|
2883
|
+
|
2884
|
+
requestor = new qq.AjaxRequestor({
|
2885
|
+
method: 'DELETE',
|
2886
|
+
endpointStore: options.endpointStore,
|
2887
|
+
paramsStore: options.paramsStore,
|
2888
|
+
maxConnections: options.maxConnections,
|
2889
|
+
customHeaders: options.customHeaders,
|
2890
|
+
successfulResponseCodes: [200, 202, 204],
|
2891
|
+
demoMode: options.demoMode,
|
2892
|
+
log: options.log,
|
2893
|
+
onSend: options.onDelete,
|
2894
|
+
onComplete: options.onDeleteComplete
|
2895
|
+
});
|
2896
|
+
|
2897
|
+
|
2898
|
+
return {
|
2899
|
+
sendDelete: function(id, uuid) {
|
2900
|
+
requestor.send(id, uuid);
|
2901
|
+
options.log("Submitted delete file request for " + id);
|
2902
|
+
}
|
2903
|
+
};
|
2904
|
+
};
|
2905
|
+
qq.WindowReceiveMessage = function(o) {
|
2906
|
+
var options = {
|
2907
|
+
log: function(message, level) {}
|
2908
|
+
},
|
2909
|
+
callbackWrapperDetachers = {};
|
2910
|
+
|
2911
|
+
qq.extend(options, o);
|
2912
|
+
|
2913
|
+
return {
|
2914
|
+
receiveMessage : function(id, callback) {
|
2915
|
+
var onMessageCallbackWrapper = function(event) {
|
2916
|
+
callback(event.data);
|
2917
|
+
};
|
2918
|
+
|
2919
|
+
if (window.postMessage) {
|
2920
|
+
callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
|
2921
|
+
}
|
2922
|
+
else {
|
2923
|
+
log("iframe message passing not supported in this browser!", "error");
|
2924
|
+
}
|
2925
|
+
},
|
2926
|
+
|
2927
|
+
stopReceivingMessages : function(id) {
|
2928
|
+
if (window.postMessage) {
|
2929
|
+
var detacher = callbackWrapperDetachers[id];
|
2930
|
+
if (detacher) {
|
2931
|
+
detacher();
|
2932
|
+
}
|
2933
|
+
}
|
2934
|
+
}
|
2935
|
+
};
|
2936
|
+
};
|
2937
|
+
/**
|
2938
|
+
* Class for uploading files, uploading itself is handled by child classes
|
2939
|
+
*/
|
2940
|
+
/*globals qq*/
|
2941
|
+
qq.UploadHandler = function(o) {
|
2942
|
+
"use strict";
|
2943
|
+
|
2944
|
+
var queue = [],
|
2945
|
+
options, log, dequeue, handlerImpl;
|
2946
|
+
|
2947
|
+
// Default options, can be overridden by the user
|
2948
|
+
options = {
|
2949
|
+
debug: false,
|
2950
|
+
forceMultipart: true,
|
2951
|
+
paramsInBody: false,
|
2952
|
+
paramsStore: {},
|
2953
|
+
endpointStore: {},
|
2954
|
+
cors: {
|
2955
|
+
expected: false,
|
2956
|
+
sendCredentials: false
|
2957
|
+
},
|
2958
|
+
maxConnections: 3, // maximum number of concurrent uploads
|
2959
|
+
uuidParamName: 'qquuid',
|
2960
|
+
totalFileSizeParamName: 'qqtotalfilesize',
|
2961
|
+
chunking: {
|
2962
|
+
enabled: false,
|
2963
|
+
partSize: 2000000, //bytes
|
2964
|
+
paramNames: {
|
2965
|
+
partIndex: 'qqpartindex',
|
2966
|
+
partByteOffset: 'qqpartbyteoffset',
|
2967
|
+
chunkSize: 'qqchunksize',
|
2968
|
+
totalParts: 'qqtotalparts',
|
2969
|
+
filename: 'qqfilename'
|
2970
|
+
}
|
2971
|
+
},
|
2972
|
+
resume: {
|
2973
|
+
enabled: false,
|
2974
|
+
id: null,
|
2975
|
+
cookiesExpireIn: 7, //days
|
2976
|
+
paramNames: {
|
2977
|
+
resuming: "qqresume"
|
2978
|
+
}
|
2979
|
+
},
|
2980
|
+
blobs: {
|
2981
|
+
paramNames: {
|
2982
|
+
name: 'qqblobname'
|
2983
|
+
}
|
2984
|
+
},
|
2985
|
+
log: function(str, level) {},
|
2986
|
+
onProgress: function(id, fileName, loaded, total){},
|
2987
|
+
onComplete: function(id, fileName, response, xhr){},
|
2988
|
+
onCancel: function(id, fileName){},
|
2989
|
+
onUpload: function(id, fileName){},
|
2990
|
+
onUploadChunk: function(id, fileName, chunkData){},
|
2991
|
+
onAutoRetry: function(id, fileName, response, xhr){},
|
2992
|
+
onResume: function(id, fileName, chunkData){}
|
2993
|
+
|
2994
|
+
};
|
2995
|
+
qq.extend(options, o);
|
2996
|
+
|
2997
|
+
log = options.log;
|
2998
|
+
|
2999
|
+
/**
|
3000
|
+
* Removes element from queue, starts upload of next
|
3001
|
+
*/
|
3002
|
+
dequeue = function(id) {
|
3003
|
+
var i = qq.indexOf(queue, id),
|
3004
|
+
max = options.maxConnections,
|
3005
|
+
nextId;
|
3006
|
+
|
3007
|
+
if (i >= 0) {
|
3008
|
+
queue.splice(i, 1);
|
3009
|
+
|
3010
|
+
if (queue.length >= max && i < max){
|
3011
|
+
nextId = queue[max-1];
|
3012
|
+
handlerImpl.upload(nextId);
|
3013
|
+
}
|
3014
|
+
}
|
3015
|
+
};
|
3016
|
+
|
3017
|
+
if (qq.isXhrUploadSupported()) {
|
3018
|
+
handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
|
3019
|
+
}
|
3020
|
+
else {
|
3021
|
+
handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
|
3022
|
+
}
|
3023
|
+
|
3024
|
+
|
3025
|
+
return {
|
3026
|
+
/**
|
3027
|
+
* Adds file or file input to the queue
|
3028
|
+
* @returns id
|
3029
|
+
**/
|
3030
|
+
add: function(file){
|
3031
|
+
return handlerImpl.add(file);
|
3032
|
+
},
|
3033
|
+
/**
|
3034
|
+
* Sends the file identified by id
|
3035
|
+
*/
|
3036
|
+
upload: function(id){
|
3037
|
+
var len = queue.push(id);
|
3038
|
+
|
3039
|
+
// if too many active uploads, wait...
|
3040
|
+
if (len <= options.maxConnections){
|
3041
|
+
return handlerImpl.upload(id);
|
3042
|
+
}
|
3043
|
+
},
|
3044
|
+
retry: function(id) {
|
3045
|
+
var i = qq.indexOf(queue, id);
|
3046
|
+
if (i >= 0) {
|
3047
|
+
return handlerImpl.upload(id, true);
|
3048
|
+
}
|
3049
|
+
else {
|
3050
|
+
return this.upload(id);
|
3051
|
+
}
|
3052
|
+
},
|
3053
|
+
/**
|
3054
|
+
* Cancels file upload by id
|
3055
|
+
*/
|
3056
|
+
cancel: function(id) {
|
3057
|
+
log('Cancelling ' + id);
|
3058
|
+
options.paramsStore.remove(id);
|
3059
|
+
handlerImpl.cancel(id);
|
3060
|
+
dequeue(id);
|
3061
|
+
},
|
3062
|
+
/**
|
3063
|
+
* Cancels all queued or in-progress uploads
|
3064
|
+
*/
|
3065
|
+
cancelAll: function() {
|
3066
|
+
var self = this,
|
3067
|
+
queueCopy = [];
|
3068
|
+
|
3069
|
+
qq.extend(queueCopy, queue);
|
3070
|
+
qq.each(queueCopy, function(idx, fileId) {
|
3071
|
+
self.cancel(fileId);
|
3072
|
+
});
|
3073
|
+
|
3074
|
+
queue = [];
|
3075
|
+
},
|
3076
|
+
/**
|
3077
|
+
* Returns name of the file identified by id
|
3078
|
+
*/
|
3079
|
+
getName: function(id){
|
3080
|
+
return handlerImpl.getName(id);
|
3081
|
+
},
|
3082
|
+
/**
|
3083
|
+
* Returns size of the file identified by id
|
3084
|
+
*/
|
3085
|
+
getSize: function(id){
|
3086
|
+
if (handlerImpl.getSize) {
|
3087
|
+
return handlerImpl.getSize(id);
|
3088
|
+
}
|
3089
|
+
},
|
3090
|
+
getFile: function(id) {
|
3091
|
+
if (handlerImpl.getFile) {
|
3092
|
+
return handlerImpl.getFile(id);
|
3093
|
+
}
|
3094
|
+
},
|
3095
|
+
/**
|
3096
|
+
* Returns id of files being uploaded or
|
3097
|
+
* waiting for their turn
|
3098
|
+
*/
|
3099
|
+
getQueue: function(){
|
3100
|
+
return queue;
|
3101
|
+
},
|
3102
|
+
reset: function() {
|
3103
|
+
log('Resetting upload handler');
|
3104
|
+
queue = [];
|
3105
|
+
handlerImpl.reset();
|
3106
|
+
},
|
3107
|
+
getUuid: function(id) {
|
3108
|
+
return handlerImpl.getUuid(id);
|
3109
|
+
},
|
3110
|
+
/**
|
3111
|
+
* Determine if the file exists.
|
3112
|
+
*/
|
3113
|
+
isValid: function(id) {
|
3114
|
+
return handlerImpl.isValid(id);
|
3115
|
+
},
|
3116
|
+
getResumableFilesData: function() {
|
3117
|
+
if (handlerImpl.getResumableFilesData) {
|
3118
|
+
return handlerImpl.getResumableFilesData();
|
3119
|
+
}
|
3120
|
+
return [];
|
3121
|
+
}
|
3122
|
+
};
|
3123
|
+
};
|
3124
|
+
/*globals qq, document, setTimeout*/
|
3125
|
+
/*globals clearTimeout*/
|
3126
|
+
qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
|
3127
|
+
"use strict";
|
3128
|
+
|
3129
|
+
var options = o,
|
3130
|
+
inputs = [],
|
3131
|
+
uuids = [],
|
3132
|
+
detachLoadEvents = {},
|
3133
|
+
postMessageCallbackTimers = {},
|
3134
|
+
uploadComplete = uploadCompleteCallback,
|
3135
|
+
log = logCallback,
|
3136
|
+
corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
|
3137
|
+
onloadCallbacks = {},
|
3138
|
+
api;
|
3139
|
+
|
3140
|
+
|
3141
|
+
function detachLoadEvent(id) {
|
3142
|
+
if (detachLoadEvents[id] !== undefined) {
|
3143
|
+
detachLoadEvents[id]();
|
3144
|
+
delete detachLoadEvents[id];
|
3145
|
+
}
|
3146
|
+
}
|
3147
|
+
|
3148
|
+
function registerPostMessageCallback(iframe, callback) {
|
3149
|
+
var id = iframe.id;
|
3150
|
+
|
3151
|
+
onloadCallbacks[uuids[id]] = callback;
|
3152
|
+
|
3153
|
+
detachLoadEvents[id] = qq(iframe).attach('load', function() {
|
3154
|
+
if (inputs[id]) {
|
3155
|
+
log("Received iframe load event for CORS upload request (file id " + id + ")");
|
3156
|
+
|
3157
|
+
postMessageCallbackTimers[id] = setTimeout(function() {
|
3158
|
+
var errorMessage = "No valid message received from loaded iframe for file id " + id;
|
3159
|
+
log(errorMessage, "error");
|
3160
|
+
callback({
|
3161
|
+
error: errorMessage
|
3162
|
+
});
|
3163
|
+
}, 1000);
|
3164
|
+
}
|
3165
|
+
});
|
3166
|
+
|
3167
|
+
corsMessageReceiver.receiveMessage(id, function(message) {
|
3168
|
+
log("Received the following window message: '" + message + "'");
|
3169
|
+
var response = qq.parseJson(message),
|
3170
|
+
uuid = response.uuid,
|
3171
|
+
onloadCallback;
|
3172
|
+
|
3173
|
+
if (uuid && onloadCallbacks[uuid]) {
|
3174
|
+
clearTimeout(postMessageCallbackTimers[id]);
|
3175
|
+
delete postMessageCallbackTimers[id];
|
3176
|
+
|
3177
|
+
detachLoadEvent(id);
|
3178
|
+
|
3179
|
+
onloadCallback = onloadCallbacks[uuid];
|
3180
|
+
|
3181
|
+
delete onloadCallbacks[uuid];
|
3182
|
+
corsMessageReceiver.stopReceivingMessages(id);
|
3183
|
+
onloadCallback(response);
|
3184
|
+
}
|
3185
|
+
else if (!uuid) {
|
3186
|
+
log("'" + message + "' does not contain a UUID - ignoring.");
|
3187
|
+
}
|
3188
|
+
});
|
3189
|
+
}
|
3190
|
+
|
3191
|
+
function attachLoadEvent(iframe, callback) {
|
3192
|
+
/*jslint eqeq: true*/
|
3193
|
+
|
3194
|
+
if (options.cors.expected) {
|
3195
|
+
registerPostMessageCallback(iframe, callback);
|
3196
|
+
}
|
3197
|
+
else {
|
3198
|
+
detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
|
3199
|
+
log('Received response for ' + iframe.id);
|
3200
|
+
|
3201
|
+
// when we remove iframe from dom
|
3202
|
+
// the request stops, but in IE load
|
3203
|
+
// event fires
|
3204
|
+
if (!iframe.parentNode){
|
3205
|
+
return;
|
3206
|
+
}
|
3207
|
+
|
3208
|
+
try {
|
3209
|
+
// fixing Opera 10.53
|
3210
|
+
if (iframe.contentDocument &&
|
3211
|
+
iframe.contentDocument.body &&
|
3212
|
+
iframe.contentDocument.body.innerHTML == "false"){
|
3213
|
+
// In Opera event is fired second time
|
3214
|
+
// when body.innerHTML changed from false
|
3215
|
+
// to server response approx. after 1 sec
|
3216
|
+
// when we upload file with iframe
|
3217
|
+
return;
|
3218
|
+
}
|
3219
|
+
}
|
3220
|
+
catch (error) {
|
3221
|
+
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
3222
|
+
log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
|
3223
|
+
}
|
3224
|
+
|
3225
|
+
callback();
|
3226
|
+
});
|
3227
|
+
}
|
3228
|
+
}
|
3229
|
+
|
3230
|
+
/**
|
3231
|
+
* Returns json object received by iframe from server.
|
3232
|
+
*/
|
3233
|
+
function getIframeContentJson(iframe) {
|
3234
|
+
/*jshint evil: true*/
|
3235
|
+
|
3236
|
+
var response;
|
3237
|
+
|
3238
|
+
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
3239
|
+
try {
|
3240
|
+
// iframe.contentWindow.document - for IE<7
|
3241
|
+
var doc = iframe.contentDocument || iframe.contentWindow.document,
|
3242
|
+
innerHTML = doc.body.innerHTML;
|
3243
|
+
|
3244
|
+
log("converting iframe's innerHTML to JSON");
|
3245
|
+
log("innerHTML = " + innerHTML);
|
3246
|
+
//plain text response may be wrapped in <pre> tag
|
3247
|
+
if (innerHTML && innerHTML.match(/^<pre/i)) {
|
3248
|
+
innerHTML = doc.body.firstChild.firstChild.nodeValue;
|
3249
|
+
}
|
3250
|
+
|
3251
|
+
response = qq.parseJson(innerHTML);
|
3252
|
+
} catch(error){
|
3253
|
+
log('Error when attempting to parse form upload response (' + error + ")", 'error');
|
3254
|
+
response = {success: false};
|
3255
|
+
}
|
3256
|
+
|
3257
|
+
return response;
|
3258
|
+
}
|
3259
|
+
|
2215
3260
|
/**
|
2216
3261
|
* Creates iframe with unique name
|
2217
3262
|
*/
|
2218
|
-
|
3263
|
+
function createIframe(id){
|
2219
3264
|
// We can't use following code as the name attribute
|
2220
3265
|
// won't be properly registered in IE6, and new window
|
2221
3266
|
// on form submit will open
|
@@ -2223,7 +3268,6 @@ qq.extend(qq.UploadHandlerForm.prototype, {
|
|
2223
3268
|
// iframe.setAttribute('name', id);
|
2224
3269
|
|
2225
3270
|
var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
|
2226
|
-
// src="javascript:false;" removes ie6 prompt on https
|
2227
3271
|
|
2228
3272
|
iframe.setAttribute('id', id);
|
2229
3273
|
|
@@ -2231,22 +3275,22 @@ qq.extend(qq.UploadHandlerForm.prototype, {
|
|
2231
3275
|
document.body.appendChild(iframe);
|
2232
3276
|
|
2233
3277
|
return iframe;
|
2234
|
-
}
|
3278
|
+
}
|
3279
|
+
|
2235
3280
|
/**
|
2236
3281
|
* Creates form, that will be submitted to iframe
|
2237
3282
|
*/
|
2238
|
-
|
2239
|
-
|
2240
|
-
|
2241
|
-
// form.setAttribute('method', 'post');
|
2242
|
-
// form.setAttribute('enctype', 'multipart/form-data');
|
2243
|
-
// Because in this case file won't be attached to request
|
2244
|
-
var protocol = this._options.demoMode ? "GET" : "POST",
|
3283
|
+
function createForm(id, iframe){
|
3284
|
+
var params = options.paramsStore.getParams(id),
|
3285
|
+
protocol = options.demoMode ? "GET" : "POST",
|
2245
3286
|
form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>'),
|
2246
|
-
|
3287
|
+
endpoint = options.endpointStore.getEndpoint(id),
|
3288
|
+
url = endpoint;
|
2247
3289
|
|
2248
|
-
|
2249
|
-
|
3290
|
+
params[options.uuidParamName] = uuids[id];
|
3291
|
+
|
3292
|
+
if (!options.paramsInBody) {
|
3293
|
+
url = qq.obj2url(params, endpoint);
|
2250
3294
|
}
|
2251
3295
|
else {
|
2252
3296
|
qq.obj2Inputs(params, form);
|
@@ -2259,172 +3303,740 @@ qq.extend(qq.UploadHandlerForm.prototype, {
|
|
2259
3303
|
|
2260
3304
|
return form;
|
2261
3305
|
}
|
2262
|
-
});
|
2263
|
-
/**
|
2264
|
-
* Class for uploading files using xhr
|
2265
|
-
* @inherits qq.UploadHandlerAbstract
|
2266
|
-
*/
|
2267
|
-
qq.UploadHandlerXhr = function(o){
|
2268
|
-
qq.UploadHandlerAbstract.apply(this, arguments);
|
2269
3306
|
|
2270
|
-
this._files = [];
|
2271
|
-
this._xhrs = [];
|
2272
3307
|
|
2273
|
-
|
2274
|
-
|
2275
|
-
|
3308
|
+
api = {
|
3309
|
+
add: function(fileInput) {
|
3310
|
+
fileInput.setAttribute('name', options.inputName);
|
2276
3311
|
|
2277
|
-
|
2278
|
-
qq.
|
3312
|
+
var id = inputs.push(fileInput) - 1;
|
3313
|
+
uuids[id] = qq.getUniqueId();
|
2279
3314
|
|
2280
|
-
|
2281
|
-
|
2282
|
-
|
2283
|
-
|
2284
|
-
**/
|
2285
|
-
add: function(file){
|
2286
|
-
if (!(file instanceof File)){
|
2287
|
-
throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
|
2288
|
-
}
|
3315
|
+
// remove file input from DOM
|
3316
|
+
if (fileInput.parentNode){
|
3317
|
+
qq(fileInput).remove();
|
3318
|
+
}
|
2289
3319
|
|
2290
|
-
|
2291
|
-
|
2292
|
-
|
2293
|
-
|
2294
|
-
// fix missing name in Safari 4
|
2295
|
-
//NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
|
2296
|
-
return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
2297
|
-
},
|
2298
|
-
getSize: function(id){
|
2299
|
-
var file = this._files[id];
|
2300
|
-
return file.fileSize != null ? file.fileSize : file.size;
|
2301
|
-
},
|
2302
|
-
/**
|
2303
|
-
* Returns uploaded bytes for file identified by id
|
2304
|
-
*/
|
2305
|
-
getLoaded: function(id){
|
2306
|
-
return this._loaded[id] || 0;
|
2307
|
-
},
|
2308
|
-
isValid: function(id) {
|
2309
|
-
return this._files[id] !== undefined;
|
2310
|
-
},
|
2311
|
-
reset: function() {
|
2312
|
-
qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments);
|
2313
|
-
this._files = [];
|
2314
|
-
this._xhrs = [];
|
2315
|
-
this._loaded = [];
|
2316
|
-
},
|
2317
|
-
/**
|
2318
|
-
* Sends the file identified by id to the server
|
2319
|
-
*/
|
2320
|
-
_upload: function(id){
|
2321
|
-
var file = this._files[id],
|
2322
|
-
name = this.getName(id),
|
2323
|
-
size = this.getSize(id),
|
2324
|
-
self = this,
|
2325
|
-
url = this._options.endpoint,
|
2326
|
-
protocol = this._options.demoMode ? "GET" : "POST",
|
2327
|
-
xhr, formData, paramName, key, params;
|
3320
|
+
return id;
|
3321
|
+
},
|
3322
|
+
getName: function(id) {
|
3323
|
+
/*jslint regexp: true*/
|
2328
3324
|
|
2329
|
-
|
3325
|
+
if (api.isValid(id)) {
|
3326
|
+
// get input value and remove path to normalize
|
3327
|
+
return inputs[id].value.replace(/.*(\/|\\)/, "");
|
3328
|
+
}
|
3329
|
+
else {
|
3330
|
+
log(id + " is not a valid item ID.", "error");
|
3331
|
+
}
|
3332
|
+
},
|
3333
|
+
isValid: function(id) {
|
3334
|
+
return inputs[id] !== undefined;
|
3335
|
+
},
|
3336
|
+
reset: function() {
|
3337
|
+
inputs = [];
|
3338
|
+
uuids = [];
|
3339
|
+
detachLoadEvents = {};
|
3340
|
+
},
|
3341
|
+
getUuid: function(id) {
|
3342
|
+
return uuids[id];
|
3343
|
+
},
|
3344
|
+
cancel: function(id) {
|
3345
|
+
options.onCancel(id, this.getName(id));
|
2330
3346
|
|
2331
|
-
|
3347
|
+
delete inputs[id];
|
3348
|
+
delete uuids[id];
|
3349
|
+
delete detachLoadEvents[id];
|
2332
3350
|
|
2333
|
-
|
3351
|
+
if (options.cors.expected) {
|
3352
|
+
clearTimeout(postMessageCallbackTimers[id]);
|
3353
|
+
delete postMessageCallbackTimers[id];
|
3354
|
+
corsMessageReceiver.stopReceivingMessages(id);
|
3355
|
+
}
|
2334
3356
|
|
2335
|
-
|
2336
|
-
if (
|
2337
|
-
|
2338
|
-
|
3357
|
+
var iframe = document.getElementById(id);
|
3358
|
+
if (iframe) {
|
3359
|
+
// to cancel request set src to something else
|
3360
|
+
// we use src="javascript:false;" because it doesn't
|
3361
|
+
// trigger ie6 prompt on https
|
3362
|
+
iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
|
3363
|
+
|
3364
|
+
qq(iframe).remove();
|
2339
3365
|
}
|
2340
|
-
}
|
3366
|
+
},
|
3367
|
+
upload: function(id){
|
3368
|
+
var input = inputs[id],
|
3369
|
+
fileName = api.getName(id),
|
3370
|
+
iframe = createIframe(id),
|
3371
|
+
form;
|
2341
3372
|
|
2342
|
-
|
2343
|
-
|
2344
|
-
self._onComplete(id, xhr);
|
3373
|
+
if (!input){
|
3374
|
+
throw new Error('file with passed id was not added, or already uploaded or cancelled');
|
2345
3375
|
}
|
3376
|
+
|
3377
|
+
options.onUpload(id, this.getName(id));
|
3378
|
+
|
3379
|
+
form = createForm(id, iframe);
|
3380
|
+
form.appendChild(input);
|
3381
|
+
|
3382
|
+
attachLoadEvent(iframe, function(responseFromMessage){
|
3383
|
+
log('iframe loaded');
|
3384
|
+
|
3385
|
+
var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
|
3386
|
+
|
3387
|
+
detachLoadEvent(id);
|
3388
|
+
|
3389
|
+
//we can't remove an iframe if the iframe doesn't belong to the same domain
|
3390
|
+
if (!options.cors.expected) {
|
3391
|
+
qq(iframe).remove();
|
3392
|
+
}
|
3393
|
+
|
3394
|
+
if (!response.success) {
|
3395
|
+
if (options.onAutoRetry(id, fileName, response)) {
|
3396
|
+
return;
|
3397
|
+
}
|
3398
|
+
}
|
3399
|
+
options.onComplete(id, fileName, response);
|
3400
|
+
uploadComplete(id);
|
3401
|
+
});
|
3402
|
+
|
3403
|
+
log('Sending upload request for ' + id);
|
3404
|
+
form.submit();
|
3405
|
+
qq(form).remove();
|
3406
|
+
|
3407
|
+
return id;
|
3408
|
+
}
|
3409
|
+
};
|
3410
|
+
|
3411
|
+
return api;
|
3412
|
+
};
|
3413
|
+
/*globals qq, File, XMLHttpRequest, FormData, Blob*/
|
3414
|
+
qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
|
3415
|
+
"use strict";
|
3416
|
+
|
3417
|
+
var options = o,
|
3418
|
+
uploadComplete = uploadCompleteCallback,
|
3419
|
+
log = logCallback,
|
3420
|
+
fileState = [],
|
3421
|
+
cookieItemDelimiter = "|",
|
3422
|
+
chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
|
3423
|
+
resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
|
3424
|
+
resumeId = getResumeId(),
|
3425
|
+
multipart = options.forceMultipart || options.paramsInBody,
|
3426
|
+
api;
|
3427
|
+
|
3428
|
+
|
3429
|
+
function addChunkingSpecificParams(id, params, chunkData) {
|
3430
|
+
var size = api.getSize(id),
|
3431
|
+
name = api.getName(id);
|
3432
|
+
|
3433
|
+
params[options.chunking.paramNames.partIndex] = chunkData.part;
|
3434
|
+
params[options.chunking.paramNames.partByteOffset] = chunkData.start;
|
3435
|
+
params[options.chunking.paramNames.chunkSize] = chunkData.size;
|
3436
|
+
params[options.chunking.paramNames.totalParts] = chunkData.count;
|
3437
|
+
params[options.totalFileSizeParamName] = size;
|
3438
|
+
|
3439
|
+
/**
|
3440
|
+
* When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
|
3441
|
+
* or an empty string. So, we will need to include the actual file name as a param in this case.
|
3442
|
+
*/
|
3443
|
+
if (multipart) {
|
3444
|
+
params[options.chunking.paramNames.filename] = name;
|
3445
|
+
}
|
3446
|
+
}
|
3447
|
+
|
3448
|
+
function addResumeSpecificParams(params) {
|
3449
|
+
params[options.resume.paramNames.resuming] = true;
|
3450
|
+
}
|
3451
|
+
|
3452
|
+
function getChunk(fileOrBlob, startByte, endByte) {
|
3453
|
+
if (fileOrBlob.slice) {
|
3454
|
+
return fileOrBlob.slice(startByte, endByte);
|
3455
|
+
}
|
3456
|
+
else if (fileOrBlob.mozSlice) {
|
3457
|
+
return fileOrBlob.mozSlice(startByte, endByte);
|
3458
|
+
}
|
3459
|
+
else if (fileOrBlob.webkitSlice) {
|
3460
|
+
return fileOrBlob.webkitSlice(startByte, endByte);
|
3461
|
+
}
|
3462
|
+
}
|
3463
|
+
|
3464
|
+
function getChunkData(id, chunkIndex) {
|
3465
|
+
var chunkSize = options.chunking.partSize,
|
3466
|
+
fileSize = api.getSize(id),
|
3467
|
+
fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
|
3468
|
+
startBytes = chunkSize * chunkIndex,
|
3469
|
+
endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
|
3470
|
+
totalChunks = getTotalChunks(id);
|
3471
|
+
|
3472
|
+
return {
|
3473
|
+
part: chunkIndex,
|
3474
|
+
start: startBytes,
|
3475
|
+
end: endBytes,
|
3476
|
+
count: totalChunks,
|
3477
|
+
blob: getChunk(fileOrBlob, startBytes, endBytes),
|
3478
|
+
size: endBytes - startBytes
|
2346
3479
|
};
|
3480
|
+
}
|
3481
|
+
|
3482
|
+
function getTotalChunks(id) {
|
3483
|
+
var fileSize = api.getSize(id),
|
3484
|
+
chunkSize = options.chunking.partSize;
|
3485
|
+
|
3486
|
+
return Math.ceil(fileSize / chunkSize);
|
3487
|
+
}
|
3488
|
+
|
3489
|
+
function createXhr(id) {
|
3490
|
+
var xhr = new XMLHttpRequest();
|
2347
3491
|
|
2348
|
-
|
3492
|
+
fileState[id].xhr = xhr;
|
3493
|
+
|
3494
|
+
return xhr;
|
3495
|
+
}
|
3496
|
+
|
3497
|
+
function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
|
3498
|
+
var formData = new FormData(),
|
3499
|
+
method = options.demoMode ? "GET" : "POST",
|
3500
|
+
endpoint = options.endpointStore.getEndpoint(id),
|
3501
|
+
url = endpoint,
|
3502
|
+
name = api.getName(id),
|
3503
|
+
size = api.getSize(id),
|
3504
|
+
blobData = fileState[id].blobData;
|
3505
|
+
|
3506
|
+
params[options.uuidParamName] = fileState[id].uuid;
|
3507
|
+
|
3508
|
+
if (multipart) {
|
3509
|
+
params[options.totalFileSizeParamName] = size;
|
3510
|
+
|
3511
|
+
if (blobData) {
|
3512
|
+
/**
|
3513
|
+
* When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
|
3514
|
+
* or an empty string. So, we will need to include the actual file name as a param in this case.
|
3515
|
+
*/
|
3516
|
+
params[options.blobs.paramNames.name] = blobData.name;
|
3517
|
+
}
|
3518
|
+
}
|
2349
3519
|
|
2350
3520
|
//build query string
|
2351
|
-
if (!
|
2352
|
-
|
2353
|
-
|
3521
|
+
if (!options.paramsInBody) {
|
3522
|
+
if (!multipart) {
|
3523
|
+
params[options.inputName] = name;
|
3524
|
+
}
|
3525
|
+
url = qq.obj2url(params, endpoint);
|
2354
3526
|
}
|
2355
3527
|
|
2356
|
-
xhr.open(
|
2357
|
-
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
2358
|
-
xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
|
2359
|
-
xhr.setRequestHeader("Cache-Control", "no-cache");
|
2360
|
-
if (this._options.forceMultipart || this._options.paramsInBody) {
|
2361
|
-
formData = new FormData();
|
3528
|
+
xhr.open(method, url, true);
|
2362
3529
|
|
2363
|
-
|
3530
|
+
if (options.cors.expected && options.cors.sendCredentials) {
|
3531
|
+
xhr.withCredentials = true;
|
3532
|
+
}
|
3533
|
+
|
3534
|
+
if (multipart) {
|
3535
|
+
if (options.paramsInBody) {
|
2364
3536
|
qq.obj2FormData(params, formData);
|
2365
3537
|
}
|
2366
3538
|
|
2367
|
-
formData.append(
|
2368
|
-
|
2369
|
-
}
|
3539
|
+
formData.append(options.inputName, fileOrBlob);
|
3540
|
+
return formData;
|
3541
|
+
}
|
3542
|
+
|
3543
|
+
return fileOrBlob;
|
3544
|
+
}
|
3545
|
+
|
3546
|
+
function setHeaders(id, xhr) {
|
3547
|
+
var extraHeaders = options.customHeaders,
|
3548
|
+
fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
|
3549
|
+
|
3550
|
+
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
3551
|
+
xhr.setRequestHeader("Cache-Control", "no-cache");
|
3552
|
+
|
3553
|
+
if (!multipart) {
|
2370
3554
|
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
2371
3555
|
//NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
|
2372
|
-
xhr.setRequestHeader("X-Mime-Type",
|
3556
|
+
xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
|
2373
3557
|
}
|
2374
3558
|
|
2375
|
-
|
2376
|
-
|
2377
|
-
|
3559
|
+
qq.each(extraHeaders, function(name, val) {
|
3560
|
+
xhr.setRequestHeader(name, val);
|
3561
|
+
});
|
3562
|
+
}
|
3563
|
+
|
3564
|
+
function handleCompletedItem(id, response, xhr) {
|
3565
|
+
var name = api.getName(id),
|
3566
|
+
size = api.getSize(id);
|
3567
|
+
|
3568
|
+
fileState[id].attemptingResume = false;
|
3569
|
+
|
3570
|
+
options.onProgress(id, name, size, size);
|
3571
|
+
|
3572
|
+
options.onComplete(id, name, response, xhr);
|
3573
|
+
delete fileState[id].xhr;
|
3574
|
+
uploadComplete(id);
|
3575
|
+
}
|
3576
|
+
|
3577
|
+
function uploadNextChunk(id) {
|
3578
|
+
var chunkIdx = fileState[id].remainingChunkIdxs[0],
|
3579
|
+
chunkData = getChunkData(id, chunkIdx),
|
3580
|
+
xhr = createXhr(id),
|
3581
|
+
size = api.getSize(id),
|
3582
|
+
name = api.getName(id),
|
3583
|
+
toSend, params;
|
3584
|
+
|
3585
|
+
if (fileState[id].loaded === undefined) {
|
3586
|
+
fileState[id].loaded = 0;
|
3587
|
+
}
|
3588
|
+
|
3589
|
+
if (resumeEnabled && fileState[id].file) {
|
3590
|
+
persistChunkData(id, chunkData);
|
3591
|
+
}
|
3592
|
+
|
3593
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
|
3594
|
+
|
3595
|
+
xhr.upload.onprogress = function(e) {
|
3596
|
+
if (e.lengthComputable) {
|
3597
|
+
var totalLoaded = e.loaded + fileState[id].loaded,
|
3598
|
+
estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
|
3599
|
+
|
3600
|
+
options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
|
2378
3601
|
}
|
3602
|
+
};
|
3603
|
+
|
3604
|
+
options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
|
3605
|
+
|
3606
|
+
params = options.paramsStore.getParams(id);
|
3607
|
+
addChunkingSpecificParams(id, params, chunkData);
|
3608
|
+
|
3609
|
+
if (fileState[id].attemptingResume) {
|
3610
|
+
addResumeSpecificParams(params);
|
2379
3611
|
}
|
2380
3612
|
|
2381
|
-
|
2382
|
-
xhr
|
2383
|
-
},
|
2384
|
-
_onComplete: function(id, xhr){
|
2385
|
-
"use strict";
|
2386
|
-
// the request was aborted/cancelled
|
2387
|
-
if (!this._files[id]) { return; }
|
3613
|
+
toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
|
3614
|
+
setHeaders(id, xhr);
|
2388
3615
|
|
2389
|
-
|
2390
|
-
|
2391
|
-
|
3616
|
+
log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
|
3617
|
+
xhr.send(toSend);
|
3618
|
+
}
|
2392
3619
|
|
2393
|
-
|
3620
|
+
function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
|
3621
|
+
var chunkData = getChunkData(id, chunkIdx),
|
3622
|
+
blobSize = chunkData.size,
|
3623
|
+
overhead = requestSize - blobSize,
|
3624
|
+
size = api.getSize(id),
|
3625
|
+
chunkCount = chunkData.count,
|
3626
|
+
initialRequestOverhead = fileState[id].initialRequestOverhead,
|
3627
|
+
overheadDiff = overhead - initialRequestOverhead;
|
2394
3628
|
|
2395
|
-
|
2396
|
-
this.log("responseText = " + xhr.responseText);
|
3629
|
+
fileState[id].lastRequestOverhead = overhead;
|
2397
3630
|
|
2398
|
-
|
2399
|
-
|
2400
|
-
|
2401
|
-
|
2402
|
-
|
3631
|
+
if (chunkIdx === 0) {
|
3632
|
+
fileState[id].lastChunkIdxProgress = 0;
|
3633
|
+
fileState[id].initialRequestOverhead = overhead;
|
3634
|
+
fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
|
3635
|
+
}
|
3636
|
+
else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
|
3637
|
+
fileState[id].lastChunkIdxProgress = chunkIdx;
|
3638
|
+
fileState[id].estTotalRequestsSize += overheadDiff;
|
3639
|
+
}
|
3640
|
+
|
3641
|
+
return fileState[id].estTotalRequestsSize;
|
3642
|
+
}
|
3643
|
+
|
3644
|
+
function getLastRequestOverhead(id) {
|
3645
|
+
if (multipart) {
|
3646
|
+
return fileState[id].lastRequestOverhead;
|
3647
|
+
}
|
3648
|
+
else {
|
3649
|
+
return 0;
|
3650
|
+
}
|
3651
|
+
}
|
3652
|
+
|
3653
|
+
function handleSuccessfullyCompletedChunk(id, response, xhr) {
|
3654
|
+
var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
|
3655
|
+
chunkData = getChunkData(id, chunkIdx);
|
3656
|
+
|
3657
|
+
fileState[id].attemptingResume = false;
|
3658
|
+
fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
|
3659
|
+
|
3660
|
+
if (fileState[id].remainingChunkIdxs.length > 0) {
|
3661
|
+
uploadNextChunk(id);
|
3662
|
+
}
|
3663
|
+
else {
|
3664
|
+
if (resumeEnabled) {
|
3665
|
+
deletePersistedChunkData(id);
|
2403
3666
|
}
|
2404
|
-
|
2405
|
-
|
3667
|
+
|
3668
|
+
handleCompletedItem(id, response, xhr);
|
3669
|
+
}
|
3670
|
+
}
|
3671
|
+
|
3672
|
+
function isErrorResponse(xhr, response) {
|
3673
|
+
return xhr.status !== 200 || !response.success || response.reset;
|
3674
|
+
}
|
3675
|
+
|
3676
|
+
function parseResponse(xhr) {
|
3677
|
+
var response;
|
3678
|
+
|
3679
|
+
try {
|
3680
|
+
response = qq.parseJson(xhr.responseText);
|
3681
|
+
}
|
3682
|
+
catch(error) {
|
3683
|
+
log('Error when attempting to parse xhr response text (' + error + ')', 'error');
|
2406
3684
|
response = {};
|
2407
3685
|
}
|
2408
3686
|
|
2409
|
-
|
2410
|
-
|
2411
|
-
|
3687
|
+
return response;
|
3688
|
+
}
|
3689
|
+
|
3690
|
+
function handleResetResponse(id) {
|
3691
|
+
log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
|
3692
|
+
|
3693
|
+
if (resumeEnabled) {
|
3694
|
+
deletePersistedChunkData(id);
|
3695
|
+
fileState[id].attemptingResume = false;
|
3696
|
+
}
|
3697
|
+
|
3698
|
+
fileState[id].remainingChunkIdxs = [];
|
3699
|
+
delete fileState[id].loaded;
|
3700
|
+
delete fileState[id].estTotalRequestsSize;
|
3701
|
+
delete fileState[id].initialRequestOverhead;
|
3702
|
+
}
|
3703
|
+
|
3704
|
+
function handleResetResponseOnResumeAttempt(id) {
|
3705
|
+
fileState[id].attemptingResume = false;
|
3706
|
+
log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
|
3707
|
+
handleResetResponse(id);
|
3708
|
+
api.upload(id, true);
|
3709
|
+
}
|
3710
|
+
|
3711
|
+
function handleNonResetErrorResponse(id, response, xhr) {
|
3712
|
+
var name = api.getName(id);
|
3713
|
+
|
3714
|
+
if (options.onAutoRetry(id, name, response, xhr)) {
|
3715
|
+
return;
|
3716
|
+
}
|
3717
|
+
else {
|
3718
|
+
handleCompletedItem(id, response, xhr);
|
3719
|
+
}
|
3720
|
+
}
|
3721
|
+
|
3722
|
+
function onComplete(id, xhr) {
|
3723
|
+
var response;
|
3724
|
+
|
3725
|
+
// the request was aborted/cancelled
|
3726
|
+
if (!fileState[id]) {
|
3727
|
+
return;
|
3728
|
+
}
|
3729
|
+
|
3730
|
+
log("xhr - server response received for " + id);
|
3731
|
+
log("responseText = " + xhr.responseText);
|
3732
|
+
response = parseResponse(xhr);
|
3733
|
+
|
3734
|
+
if (isErrorResponse(xhr, response)) {
|
3735
|
+
if (response.reset) {
|
3736
|
+
handleResetResponse(id);
|
3737
|
+
}
|
3738
|
+
|
3739
|
+
if (fileState[id].attemptingResume && response.reset) {
|
3740
|
+
handleResetResponseOnResumeAttempt(id);
|
3741
|
+
}
|
3742
|
+
else {
|
3743
|
+
handleNonResetErrorResponse(id, response, xhr);
|
2412
3744
|
}
|
2413
3745
|
}
|
3746
|
+
else if (chunkFiles) {
|
3747
|
+
handleSuccessfullyCompletedChunk(id, response, xhr);
|
3748
|
+
}
|
3749
|
+
else {
|
3750
|
+
handleCompletedItem(id, response, xhr);
|
3751
|
+
}
|
3752
|
+
}
|
3753
|
+
|
3754
|
+
function getChunkDataForCallback(chunkData) {
|
3755
|
+
return {
|
3756
|
+
partIndex: chunkData.part,
|
3757
|
+
startByte: chunkData.start + 1,
|
3758
|
+
endByte: chunkData.end,
|
3759
|
+
totalParts: chunkData.count
|
3760
|
+
};
|
3761
|
+
}
|
2414
3762
|
|
2415
|
-
|
3763
|
+
function getReadyStateChangeHandler(id, xhr) {
|
3764
|
+
return function() {
|
3765
|
+
if (xhr.readyState === 4) {
|
3766
|
+
onComplete(id, xhr);
|
3767
|
+
}
|
3768
|
+
};
|
3769
|
+
}
|
2416
3770
|
|
2417
|
-
|
2418
|
-
|
2419
|
-
|
2420
|
-
|
2421
|
-
|
3771
|
+
function persistChunkData(id, chunkData) {
|
3772
|
+
var fileUuid = api.getUuid(id),
|
3773
|
+
lastByteSent = fileState[id].loaded,
|
3774
|
+
initialRequestOverhead = fileState[id].initialRequestOverhead,
|
3775
|
+
estTotalRequestsSize = fileState[id].estTotalRequestsSize,
|
3776
|
+
cookieName = getChunkDataCookieName(id),
|
3777
|
+
cookieValue = fileUuid +
|
3778
|
+
cookieItemDelimiter + chunkData.part +
|
3779
|
+
cookieItemDelimiter + lastByteSent +
|
3780
|
+
cookieItemDelimiter + initialRequestOverhead +
|
3781
|
+
cookieItemDelimiter + estTotalRequestsSize,
|
3782
|
+
cookieExpDays = options.resume.cookiesExpireIn;
|
3783
|
+
|
3784
|
+
qq.setCookie(cookieName, cookieValue, cookieExpDays);
|
3785
|
+
}
|
2422
3786
|
|
2423
|
-
|
3787
|
+
function deletePersistedChunkData(id) {
|
3788
|
+
if (fileState[id].file) {
|
3789
|
+
var cookieName = getChunkDataCookieName(id);
|
3790
|
+
qq.deleteCookie(cookieName);
|
3791
|
+
}
|
3792
|
+
}
|
2424
3793
|
|
2425
|
-
|
2426
|
-
|
2427
|
-
|
3794
|
+
function getPersistedChunkData(id) {
|
3795
|
+
var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
|
3796
|
+
filename = api.getName(id),
|
3797
|
+
sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
|
3798
|
+
|
3799
|
+
if (chunkCookieValue) {
|
3800
|
+
sections = chunkCookieValue.split(cookieItemDelimiter);
|
3801
|
+
|
3802
|
+
if (sections.length === 5) {
|
3803
|
+
uuid = sections[0];
|
3804
|
+
partIndex = parseInt(sections[1], 10);
|
3805
|
+
lastByteSent = parseInt(sections[2], 10);
|
3806
|
+
initialRequestOverhead = parseInt(sections[3], 10);
|
3807
|
+
estTotalRequestsSize = parseInt(sections[4], 10);
|
3808
|
+
|
3809
|
+
return {
|
3810
|
+
uuid: uuid,
|
3811
|
+
part: partIndex,
|
3812
|
+
lastByteSent: lastByteSent,
|
3813
|
+
initialRequestOverhead: initialRequestOverhead,
|
3814
|
+
estTotalRequestsSize: estTotalRequestsSize
|
3815
|
+
};
|
3816
|
+
}
|
3817
|
+
else {
|
3818
|
+
log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
|
3819
|
+
}
|
2428
3820
|
}
|
2429
3821
|
}
|
2430
|
-
|
3822
|
+
|
3823
|
+
function getChunkDataCookieName(id) {
|
3824
|
+
var filename = api.getName(id),
|
3825
|
+
fileSize = api.getSize(id),
|
3826
|
+
maxChunkSize = options.chunking.partSize,
|
3827
|
+
cookieName;
|
3828
|
+
|
3829
|
+
cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
|
3830
|
+
|
3831
|
+
if (resumeId !== undefined) {
|
3832
|
+
cookieName += cookieItemDelimiter + resumeId;
|
3833
|
+
}
|
3834
|
+
|
3835
|
+
return cookieName;
|
3836
|
+
}
|
3837
|
+
|
3838
|
+
function getResumeId() {
|
3839
|
+
if (options.resume.id !== null &&
|
3840
|
+
options.resume.id !== undefined &&
|
3841
|
+
!qq.isFunction(options.resume.id) &&
|
3842
|
+
!qq.isObject(options.resume.id)) {
|
3843
|
+
|
3844
|
+
return options.resume.id;
|
3845
|
+
}
|
3846
|
+
}
|
3847
|
+
|
3848
|
+
function handleFileChunkingUpload(id, retry) {
|
3849
|
+
var name = api.getName(id),
|
3850
|
+
firstChunkIndex = 0,
|
3851
|
+
persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
|
3852
|
+
|
3853
|
+
if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
|
3854
|
+
fileState[id].remainingChunkIdxs = [];
|
3855
|
+
|
3856
|
+
if (resumeEnabled && !retry && fileState[id].file) {
|
3857
|
+
persistedChunkInfoForResume = getPersistedChunkData(id);
|
3858
|
+
if (persistedChunkInfoForResume) {
|
3859
|
+
firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
|
3860
|
+
if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
|
3861
|
+
firstChunkIndex = persistedChunkInfoForResume.part;
|
3862
|
+
fileState[id].uuid = persistedChunkInfoForResume.uuid;
|
3863
|
+
fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
|
3864
|
+
fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
|
3865
|
+
fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
|
3866
|
+
fileState[id].attemptingResume = true;
|
3867
|
+
log('Resuming ' + name + " at partition index " + firstChunkIndex);
|
3868
|
+
}
|
3869
|
+
}
|
3870
|
+
}
|
3871
|
+
|
3872
|
+
for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
|
3873
|
+
fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
|
3874
|
+
}
|
3875
|
+
}
|
3876
|
+
|
3877
|
+
uploadNextChunk(id);
|
3878
|
+
}
|
3879
|
+
|
3880
|
+
function handleStandardFileUpload(id) {
|
3881
|
+
var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
|
3882
|
+
name = api.getName(id),
|
3883
|
+
xhr, params, toSend;
|
3884
|
+
|
3885
|
+
fileState[id].loaded = 0;
|
3886
|
+
|
3887
|
+
xhr = createXhr(id);
|
3888
|
+
|
3889
|
+
xhr.upload.onprogress = function(e){
|
3890
|
+
if (e.lengthComputable){
|
3891
|
+
fileState[id].loaded = e.loaded;
|
3892
|
+
options.onProgress(id, name, e.loaded, e.total);
|
3893
|
+
}
|
3894
|
+
};
|
3895
|
+
|
3896
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
|
3897
|
+
|
3898
|
+
params = options.paramsStore.getParams(id);
|
3899
|
+
toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
|
3900
|
+
setHeaders(id, xhr);
|
3901
|
+
|
3902
|
+
log('Sending upload request for ' + id);
|
3903
|
+
xhr.send(toSend);
|
3904
|
+
}
|
3905
|
+
|
3906
|
+
|
3907
|
+
api = {
|
3908
|
+
/**
|
3909
|
+
* Adds File or Blob to the queue
|
3910
|
+
* Returns id to use with upload, cancel
|
3911
|
+
**/
|
3912
|
+
add: function(fileOrBlobData){
|
3913
|
+
var id;
|
3914
|
+
|
3915
|
+
if (fileOrBlobData instanceof File) {
|
3916
|
+
id = fileState.push({file: fileOrBlobData}) - 1;
|
3917
|
+
}
|
3918
|
+
else if (fileOrBlobData.blob instanceof Blob) {
|
3919
|
+
id = fileState.push({blobData: fileOrBlobData}) - 1;
|
3920
|
+
}
|
3921
|
+
else {
|
3922
|
+
throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
|
3923
|
+
}
|
3924
|
+
|
3925
|
+
fileState[id].uuid = qq.getUniqueId();
|
3926
|
+
return id;
|
3927
|
+
},
|
3928
|
+
getName: function(id){
|
3929
|
+
if (api.isValid(id)) {
|
3930
|
+
var file = fileState[id].file,
|
3931
|
+
blobData = fileState[id].blobData;
|
3932
|
+
|
3933
|
+
if (file) {
|
3934
|
+
// fix missing name in Safari 4
|
3935
|
+
//NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
|
3936
|
+
return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
3937
|
+
}
|
3938
|
+
else {
|
3939
|
+
return blobData.name;
|
3940
|
+
}
|
3941
|
+
}
|
3942
|
+
else {
|
3943
|
+
log(id + " is not a valid item ID.", "error");
|
3944
|
+
}
|
3945
|
+
},
|
3946
|
+
getSize: function(id){
|
3947
|
+
/*jshint eqnull: true*/
|
3948
|
+
var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
|
3949
|
+
|
3950
|
+
if (qq.isFileOrInput(fileOrBlob)) {
|
3951
|
+
return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
|
3952
|
+
}
|
3953
|
+
else {
|
3954
|
+
return fileOrBlob.size;
|
3955
|
+
}
|
3956
|
+
},
|
3957
|
+
getFile: function(id) {
|
3958
|
+
if (fileState[id]) {
|
3959
|
+
return fileState[id].file || fileState[id].blobData.blob;
|
3960
|
+
}
|
3961
|
+
},
|
3962
|
+
/**
|
3963
|
+
* Returns uploaded bytes for file identified by id
|
3964
|
+
*/
|
3965
|
+
getLoaded: function(id){
|
3966
|
+
return fileState[id].loaded || 0;
|
3967
|
+
},
|
3968
|
+
isValid: function(id) {
|
3969
|
+
return fileState[id] !== undefined;
|
3970
|
+
},
|
3971
|
+
reset: function() {
|
3972
|
+
fileState = [];
|
3973
|
+
},
|
3974
|
+
getUuid: function(id) {
|
3975
|
+
return fileState[id].uuid;
|
3976
|
+
},
|
3977
|
+
/**
|
3978
|
+
* Sends the file identified by id to the server
|
3979
|
+
*/
|
3980
|
+
upload: function(id, retry){
|
3981
|
+
var name = this.getName(id);
|
3982
|
+
|
3983
|
+
options.onUpload(id, name);
|
3984
|
+
|
3985
|
+
if (chunkFiles) {
|
3986
|
+
handleFileChunkingUpload(id, retry);
|
3987
|
+
}
|
3988
|
+
else {
|
3989
|
+
handleStandardFileUpload(id);
|
3990
|
+
}
|
3991
|
+
},
|
3992
|
+
cancel: function(id){
|
3993
|
+
var xhr = fileState[id].xhr;
|
3994
|
+
|
3995
|
+
options.onCancel(id, this.getName(id));
|
3996
|
+
|
3997
|
+
if (xhr) {
|
3998
|
+
xhr.onreadystatechange = null;
|
3999
|
+
xhr.abort();
|
4000
|
+
}
|
4001
|
+
|
4002
|
+
if (resumeEnabled) {
|
4003
|
+
deletePersistedChunkData(id);
|
4004
|
+
}
|
4005
|
+
|
4006
|
+
delete fileState[id];
|
4007
|
+
},
|
4008
|
+
getResumableFilesData: function() {
|
4009
|
+
var matchingCookieNames = [],
|
4010
|
+
resumableFilesData = [];
|
4011
|
+
|
4012
|
+
if (chunkFiles && resumeEnabled) {
|
4013
|
+
if (resumeId === undefined) {
|
4014
|
+
matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
|
4015
|
+
cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
|
4016
|
+
}
|
4017
|
+
else {
|
4018
|
+
matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
|
4019
|
+
cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
|
4020
|
+
cookieItemDelimiter + resumeId + "="));
|
4021
|
+
}
|
4022
|
+
|
4023
|
+
qq.each(matchingCookieNames, function(idx, cookieName) {
|
4024
|
+
var cookiesNameParts = cookieName.split(cookieItemDelimiter);
|
4025
|
+
var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
|
4026
|
+
|
4027
|
+
resumableFilesData.push({
|
4028
|
+
name: decodeURIComponent(cookiesNameParts[1]),
|
4029
|
+
size: cookiesNameParts[2],
|
4030
|
+
uuid: cookieValueParts[0],
|
4031
|
+
partIdx: cookieValueParts[1]
|
4032
|
+
});
|
4033
|
+
});
|
4034
|
+
|
4035
|
+
return resumableFilesData;
|
4036
|
+
}
|
4037
|
+
return [];
|
4038
|
+
}
|
4039
|
+
};
|
4040
|
+
|
4041
|
+
return api;
|
4042
|
+
};
|