fileuploader-rails 3.0.0.1 → 3.1.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.
@@ -8,8 +8,7 @@
8
8
  *
9
9
  * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
10
10
  */
11
-
12
- var qq = qq || {};
11
+ /*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest*/
13
12
  var qq = function(element) {
14
13
  "use strict";
15
14
 
@@ -42,13 +41,14 @@ var qq = function(element) {
42
41
 
43
42
  contains: function(descendant) {
44
43
  // compareposition returns false in this case
45
- if (element == descendant) {
44
+ if (element === descendant) {
46
45
  return true;
47
46
  }
48
47
 
49
48
  if (element.contains){
50
49
  return element.contains(descendant);
51
50
  } else {
51
+ /*jslint bitwise: true*/
52
52
  return !!(descendant.compareDocumentPosition(element) & 8);
53
53
  }
54
54
  },
@@ -71,8 +71,8 @@ var qq = function(element) {
71
71
  * Fixes opacity in IE6-8.
72
72
  */
73
73
  css: function(styles) {
74
- if (styles.opacity != null){
75
- if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
74
+ if (styles.opacity !== null){
75
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
76
76
  styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
77
77
  }
78
78
  }
@@ -100,19 +100,20 @@ var qq = function(element) {
100
100
  },
101
101
 
102
102
  getByClass: function(className) {
103
+ var candidates,
104
+ result = [];
105
+
103
106
  if (element.querySelectorAll){
104
107
  return element.querySelectorAll('.' + className);
105
108
  }
106
109
 
107
- var result = [];
108
- var candidates = element.getElementsByTagName("*");
109
- var len = candidates.length;
110
+ candidates = element.getElementsByTagName("*");
110
111
 
111
- for (var i = 0; i < len; i++){
112
- if (qq(candidates[i]).hasClass(className)){
113
- result.push(candidates[i]);
112
+ qq.each(candidates, function(idx, val) {
113
+ if (qq(val).hasClass(className)){
114
+ result.push(val);
114
115
  }
115
- }
116
+ });
116
117
  return result;
117
118
  },
118
119
 
@@ -121,7 +122,7 @@ var qq = function(element) {
121
122
  child = element.firstChild;
122
123
 
123
124
  while (child){
124
- if (child.nodeType == 1){
125
+ if (child.nodeType === 1){
125
126
  children.push(child);
126
127
  }
127
128
  child = child.nextSibling;
@@ -143,6 +144,8 @@ var qq = function(element) {
143
144
  };
144
145
 
145
146
  qq.log = function(message, level) {
147
+ "use strict";
148
+
146
149
  if (window.console) {
147
150
  if (!level || level === 'info') {
148
151
  window.console.log(message);
@@ -164,22 +167,64 @@ qq.isObject = function(variable) {
164
167
  return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
165
168
  };
166
169
 
167
- qq.extend = function (first, second, extendNested) {
170
+ qq.isFunction = function(variable) {
168
171
  "use strict";
169
- var prop;
170
- for (prop in second) {
171
- if (second.hasOwnProperty(prop)) {
172
- if (extendNested && qq.isObject(second[prop])) {
173
- if (first[prop] === undefined) {
174
- first[prop] = {};
175
- }
176
- qq.extend(first[prop], second[prop], true);
172
+ return typeof(variable) === "function";
173
+ };
174
+
175
+ qq.isFileOrInput = function(maybeFileOrInput) {
176
+ "use strict";
177
+ if (window.File && maybeFileOrInput instanceof File) {
178
+ return true;
179
+ }
180
+ else if (window.HTMLInputElement) {
181
+ if (maybeFileOrInput instanceof HTMLInputElement) {
182
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
183
+ return true;
177
184
  }
178
- else {
179
- first[prop] = second[prop];
185
+ }
186
+ }
187
+ else if (maybeFileOrInput.tagName) {
188
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
189
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
190
+ return true;
180
191
  }
181
192
  }
182
193
  }
194
+
195
+ return false;
196
+ };
197
+
198
+ qq.isXhrUploadSupported = function() {
199
+ "use strict";
200
+ var input = document.createElement('input');
201
+ input.type = 'file';
202
+
203
+ return (
204
+ input.multiple !== undefined &&
205
+ typeof File !== "undefined" &&
206
+ typeof FormData !== "undefined" &&
207
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
208
+ };
209
+
210
+ qq.isFolderDropSupported = function(dataTransfer) {
211
+ "use strict";
212
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
213
+ };
214
+
215
+ qq.extend = function (first, second, extendNested) {
216
+ "use strict";
217
+ qq.each(second, function(prop, val) {
218
+ if (extendNested && qq.isObject(val)) {
219
+ if (first[prop] === undefined) {
220
+ first[prop] = {};
221
+ }
222
+ qq.extend(first[prop], val, true);
223
+ }
224
+ else {
225
+ first[prop] = val;
226
+ }
227
+ });
183
228
  };
184
229
 
185
230
  /**
@@ -187,15 +232,21 @@ qq.extend = function (first, second, extendNested) {
187
232
  * @param {Number} [from] The index at which to begin the search
188
233
  */
189
234
  qq.indexOf = function(arr, elt, from){
190
- if (arr.indexOf) return arr.indexOf(elt, from);
235
+ "use strict";
236
+
237
+ if (arr.indexOf) {
238
+ return arr.indexOf(elt, from);
239
+ }
191
240
 
192
241
  from = from || 0;
193
242
  var len = arr.length;
194
243
 
195
- if (from < 0) from += len;
244
+ if (from < 0) {
245
+ from += len;
246
+ }
196
247
 
197
- for (; from < len; from++){
198
- if (from in arr && arr[from] === elt){
248
+ for (null; from < len; from+=1){
249
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
199
250
  return from;
200
251
  }
201
252
  }
@@ -203,24 +254,48 @@ qq.indexOf = function(arr, elt, from){
203
254
  };
204
255
 
205
256
  qq.getUniqueId = (function(){
206
- var id = 0;
207
- return function(){ return id++; };
208
- })();
257
+ "use strict";
258
+
259
+ var id = -1;
260
+ return function(){
261
+ id += 1;
262
+ return id;
263
+ };
264
+ }());
209
265
 
210
266
  //
211
267
  // Browsers and platforms detection
212
268
 
213
- qq.ie = function(){ return navigator.userAgent.indexOf('MSIE') != -1; }
214
- qq.ie10 = function(){ return navigator.userAgent.indexOf('MSIE 10') != -1; }
215
- qq.safari = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf("Apple") != -1; }
216
- qq.chrome = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf('Google') != -1; }
217
- qq.firefox = function(){ return (navigator.userAgent.indexOf('Mozilla') != -1 && navigator.vendor != undefined && navigator.vendor == ''); }
218
- qq.windows = function(){ return navigator.platform == "Win32"; }
269
+ qq.ie = function(){
270
+ "use strict";
271
+ return navigator.userAgent.indexOf('MSIE') !== -1;
272
+ };
273
+ qq.ie10 = function(){
274
+ "use strict";
275
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
276
+ };
277
+ qq.safari = function(){
278
+ "use strict";
279
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
280
+ };
281
+ qq.chrome = function(){
282
+ "use strict";
283
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
284
+ };
285
+ qq.firefox = function(){
286
+ "use strict";
287
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
288
+ };
289
+ qq.windows = function(){
290
+ "use strict";
291
+ return navigator.platform === "Win32";
292
+ };
219
293
 
220
294
  //
221
295
  // Events
222
296
 
223
297
  qq.preventDefault = function(e){
298
+ "use strict";
224
299
  if (e.preventDefault){
225
300
  e.preventDefault();
226
301
  } else{
@@ -233,6 +308,7 @@ qq.preventDefault = function(e){
233
308
  * Uses innerHTML to create an element
234
309
  */
235
310
  qq.toElement = (function(){
311
+ "use strict";
236
312
  var div = document.createElement('div');
237
313
  return function(html){
238
314
  div.innerHTML = html;
@@ -240,7 +316,23 @@ qq.toElement = (function(){
240
316
  div.removeChild(element);
241
317
  return element;
242
318
  };
243
- })();
319
+ }());
320
+
321
+ //key and value are passed to callback for each item in the object or array
322
+ qq.each = function(obj, callback) {
323
+ "use strict";
324
+ var key, retVal;
325
+ if (obj) {
326
+ for (key in obj) {
327
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
328
+ retVal = callback(key, obj[key]);
329
+ if (retVal === false) {
330
+ break;
331
+ }
332
+ }
333
+ }
334
+ }
335
+ };
244
336
 
245
337
  /**
246
338
  * obj2url() takes a json-object as argument and generates
@@ -259,15 +351,17 @@ qq.toElement = (function(){
259
351
  * @return String encoded querystring
260
352
  */
261
353
  qq.obj2url = function(obj, temp, prefixDone){
262
- var uristrings = [],
263
- prefix = '&',
264
- add = function(nextObj, i){
354
+ "use strict";
355
+ var i, len,
356
+ uristrings = [],
357
+ prefix = '&',
358
+ add = function(nextObj, i){
265
359
  var nextTemp = temp
266
360
  ? (/\[\]$/.test(temp)) // prevent double-encoding
267
361
  ? temp
268
362
  : temp+'['+i+']'
269
363
  : i;
270
- if ((nextTemp != 'undefined') && (i != 'undefined')) {
364
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
271
365
  uristrings.push(
272
366
  (typeof nextObj === 'object')
273
367
  ? qq.obj2url(nextObj, nextTemp, true)
@@ -282,15 +376,17 @@ qq.obj2url = function(obj, temp, prefixDone){
282
376
  prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
283
377
  uristrings.push(temp);
284
378
  uristrings.push(qq.obj2url(obj));
285
- } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
379
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
286
380
  // we wont use a for-in-loop on an array (performance)
287
- for (var i = 0, len = obj.length; i < len; ++i){
381
+ for (i = -1, len = obj.length; i < len; i+=1){
288
382
  add(obj[i], i);
289
383
  }
290
- } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
384
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
291
385
  // for anything else but a scalar, we will use for-in-loop
292
- for (var i in obj){
293
- add(obj[i], i);
386
+ for (i in obj){
387
+ if (obj.hasOwnProperty(i)) {
388
+ add(obj[i], i);
389
+ }
294
390
  }
295
391
  } else {
296
392
  uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
@@ -305,29 +401,81 @@ qq.obj2url = function(obj, temp, prefixDone){
305
401
  }
306
402
  };
307
403
 
404
+ qq.obj2FormData = function(obj, formData, arrayKeyName) {
405
+ "use strict";
406
+ if (!formData) {
407
+ formData = new FormData();
408
+ }
409
+
410
+ qq.each(obj, function(key, val) {
411
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
412
+
413
+ if (qq.isObject(val)) {
414
+ qq.obj2FormData(val, formData, key);
415
+ }
416
+ else if (qq.isFunction(val)) {
417
+ formData.append(encodeURIComponent(key), encodeURIComponent(val()));
418
+ }
419
+ else {
420
+ formData.append(encodeURIComponent(key), encodeURIComponent(val));
421
+ }
422
+ });
423
+
424
+ return formData;
425
+ };
426
+
427
+ qq.obj2Inputs = function(obj, form) {
428
+ "use strict";
429
+ var input;
430
+
431
+ if (!form) {
432
+ form = document.createElement('form');
433
+ }
434
+
435
+ qq.obj2FormData(obj, {
436
+ append: function(key, val) {
437
+ input = document.createElement('input');
438
+ input.setAttribute('name', key);
439
+ input.setAttribute('value', val);
440
+ form.appendChild(input);
441
+ }
442
+ });
443
+
444
+ return form;
445
+ };
446
+
308
447
  /**
309
448
  * A generic module which supports object disposing in dispose() method.
310
449
  * */
311
- qq.DisposeSupport = {
312
- _disposers: [],
450
+ qq.DisposeSupport = function() {
451
+ "use strict";
452
+ var disposers = [];
313
453
 
314
- /** Run all registered disposers */
315
- dispose: function() {
316
- var disposer;
317
- while (disposer = this._disposers.shift()) {
318
- disposer();
319
- }
320
- },
454
+ return {
455
+ /** Run all registered disposers */
456
+ dispose: function() {
457
+ var disposer;
458
+ do {
459
+ disposer = disposers.shift();
460
+ if (disposer) {
461
+ disposer();
462
+ }
463
+ }
464
+ while (disposer);
465
+ },
321
466
 
322
- /** Add disposer to the collection */
323
- addDisposer: function(disposeFunction) {
324
- this._disposers.push(disposeFunction);
325
- },
467
+ /** Attach event handler and register de-attacher as a disposer */
468
+ attach: function() {
469
+ var args = arguments;
470
+ /*jslint undef:true*/
471
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
472
+ },
326
473
 
327
- /** Attach event handler and register de-attacher as a disposer */
328
- _attach: function() {
329
- this.addDisposer(qq(arguments[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
330
- }
474
+ /** Add disposer to the collection */
475
+ addDisposer: function(disposeFunction) {
476
+ disposers.push(disposeFunction);
477
+ }
478
+ };
331
479
  };
332
480
  qq.UploadButton = function(o){
333
481
  this._options = {
@@ -343,7 +491,7 @@ qq.UploadButton = function(o){
343
491
  };
344
492
 
345
493
  qq.extend(this._options, o);
346
- qq.extend(this, qq.DisposeSupport);
494
+ this._disposeSupport = new qq.DisposeSupport();
347
495
 
348
496
  this._element = this._options.element;
349
497
 
@@ -404,20 +552,20 @@ qq.UploadButton.prototype = {
404
552
  this._element.appendChild(input);
405
553
 
406
554
  var self = this;
407
- this._attach(input, 'change', function(){
555
+ this._disposeSupport.attach(input, 'change', function(){
408
556
  self._options.onChange(input);
409
557
  });
410
558
 
411
- this._attach(input, 'mouseover', function(){
559
+ this._disposeSupport.attach(input, 'mouseover', function(){
412
560
  qq(self._element).addClass(self._options.hoverClass);
413
561
  });
414
- this._attach(input, 'mouseout', function(){
562
+ this._disposeSupport.attach(input, 'mouseout', function(){
415
563
  qq(self._element).removeClass(self._options.hoverClass);
416
564
  });
417
- this._attach(input, 'focus', function(){
565
+ this._disposeSupport.attach(input, 'focus', function(){
418
566
  qq(self._element).addClass(self._options.focusClass);
419
567
  });
420
- this._attach(input, 'blur', function(){
568
+ this._disposeSupport.attach(input, 'blur', function(){
421
569
  qq(self._element).removeClass(self._options.focusClass);
422
570
  });
423
571
 
@@ -443,6 +591,7 @@ qq.FineUploaderBasic = function(o){
443
591
  request: {
444
592
  endpoint: '/server/upload',
445
593
  params: {},
594
+ paramsInBody: false,
446
595
  customHeaders: {},
447
596
  forceMultipart: false,
448
597
  inputName: 'qqfile'
@@ -477,12 +626,16 @@ qq.FineUploaderBasic = function(o){
477
626
  maxAutoAttempts: 3,
478
627
  autoAttemptDelay: 5,
479
628
  preventRetryResponseProperty: 'preventRetry'
629
+ },
630
+ classes: {
631
+ buttonHover: 'qq-upload-button-hover',
632
+ buttonFocus: 'qq-upload-button-focus'
480
633
  }
481
634
  };
482
635
 
483
636
  qq.extend(this._options, o, true);
484
637
  this._wrapCallbacks();
485
- qq.extend(this, qq.DisposeSupport);
638
+ this._disposeSupport = new qq.DisposeSupport();
486
639
 
487
640
  // number of files being uploaded
488
641
  this._filesInProgress = 0;
@@ -493,6 +646,8 @@ qq.FineUploaderBasic = function(o){
493
646
  this._retryTimeouts = [];
494
647
  this._preventRetries = [];
495
648
 
649
+ this._paramsStore = this._createParamsStore();
650
+
496
651
  this._handler = this._createUploadHandler();
497
652
 
498
653
  if (this._options.button){
@@ -512,8 +667,13 @@ qq.FineUploaderBasic.prototype = {
512
667
 
513
668
  }
514
669
  },
515
- setParams: function(params){
516
- this._options.request.params = params;
670
+ setParams: function(params, fileId){
671
+ if (fileId === undefined) {
672
+ this._options.request.params = params;
673
+ }
674
+ else {
675
+ this._paramsStore.setParams(params, fileId);
676
+ }
517
677
  },
518
678
  getInProgress: function(){
519
679
  return this._filesInProgress;
@@ -522,7 +682,7 @@ qq.FineUploaderBasic.prototype = {
522
682
  "use strict";
523
683
  while(this._storedFileIds.length) {
524
684
  this._filesInProgress++;
525
- this._handler.upload(this._storedFileIds.shift(), this._options.request.params);
685
+ this._handler.upload(this._storedFileIds.shift());
526
686
  }
527
687
  },
528
688
  clearStoredFiles: function(){
@@ -549,27 +709,55 @@ qq.FineUploaderBasic.prototype = {
549
709
  this._retryTimeouts = [];
550
710
  this._preventRetries = [];
551
711
  this._button.reset();
712
+ this._paramsStore.reset();
713
+ },
714
+ addFiles: function(filesOrInputs) {
715
+ var self = this,
716
+ verifiedFilesOrInputs = [],
717
+ index, fileOrInput;
718
+
719
+ if (filesOrInputs) {
720
+ if (!window.FileList || !(filesOrInputs instanceof FileList)) {
721
+ filesOrInputs = [].concat(filesOrInputs);
722
+ }
723
+
724
+ for (index = 0; index < filesOrInputs.length; index+=1) {
725
+ fileOrInput = filesOrInputs[index];
726
+
727
+ if (qq.isFileOrInput(fileOrInput)) {
728
+ verifiedFilesOrInputs.push(fileOrInput);
729
+ }
730
+ else {
731
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
732
+ }
733
+ }
734
+
735
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
736
+ this._uploadFileList(verifiedFilesOrInputs);
737
+ }
552
738
  },
553
739
  _createUploadButton: function(element){
554
740
  var self = this;
555
741
 
556
742
  var button = new qq.UploadButton({
557
743
  element: element,
558
- multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
744
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
559
745
  acceptFiles: this._options.validation.acceptFiles,
560
746
  onChange: function(input){
561
747
  self._onInputChange(input);
562
- }
748
+ },
749
+ hoverClass: this._options.classes.buttonHover,
750
+ focusClass: this._options.classes.buttonFocus
563
751
  });
564
752
 
565
- this.addDisposer(function() { button.dispose(); });
753
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
566
754
  return button;
567
755
  },
568
756
  _createUploadHandler: function(){
569
757
  var self = this,
570
758
  handlerClass;
571
759
 
572
- if(qq.UploadHandlerXhr.isSupported()){
760
+ if(qq.isXhrUploadSupported()){
573
761
  handlerClass = 'UploadHandlerXhr';
574
762
  } else {
575
763
  handlerClass = 'UploadHandlerForm';
@@ -584,6 +772,8 @@ qq.FineUploaderBasic.prototype = {
584
772
  inputName: this._options.request.inputName,
585
773
  demoMode: this._options.demoMode,
586
774
  log: this.log,
775
+ paramsInBody: this._options.request.paramsInBody,
776
+ paramsStore: this._paramsStore,
587
777
  onProgress: function(id, fileName, loaded, total){
588
778
  self._onProgress(id, fileName, loaded, total);
589
779
  self._options.callbacks.onProgress(id, fileName, loaded, total);
@@ -625,7 +815,7 @@ qq.FineUploaderBasic.prototype = {
625
815
  _preventLeaveInProgress: function(){
626
816
  var self = this;
627
817
 
628
- this._attach(window, 'beforeunload', function(e){
818
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
629
819
  if (!self._filesInProgress){return;}
630
820
 
631
821
  var e = e || window.event;
@@ -661,10 +851,10 @@ qq.FineUploaderBasic.prototype = {
661
851
  },
662
852
  _onInputChange: function(input){
663
853
  if (this._handler instanceof qq.UploadHandlerXhr){
664
- this._uploadFileList(input.files);
854
+ this.addFiles(input.files);
665
855
  } else {
666
856
  if (this._validateFile(input)){
667
- this._uploadFile(input);
857
+ this.addFiles(input);
668
858
  }
669
859
  }
670
860
  this._button.reset();
@@ -726,9 +916,7 @@ qq.FineUploaderBasic.prototype = {
726
916
  var validationDescriptors, index, batchInvalid;
727
917
 
728
918
  validationDescriptors = this._getValidationDescriptors(files);
729
- if (validationDescriptors.length > 1) {
730
- batchInvalid = this._options.callbacks.onValidate(validationDescriptors) === false;
731
- }
919
+ batchInvalid = this._options.callbacks.onValidate(validationDescriptors) === false;
732
920
 
733
921
  if (!batchInvalid) {
734
922
  if (files.length > 0) {
@@ -754,7 +942,7 @@ qq.FineUploaderBasic.prototype = {
754
942
  if (this._options.callbacks.onSubmit(id, fileName) !== false){
755
943
  this._onSubmit(id, fileName);
756
944
  if (this._options.autoUpload) {
757
- this._handler.upload(id, this._options.request.params);
945
+ this._handler.upload(id);
758
946
  }
759
947
  else {
760
948
  this._storeFileForLater(id);
@@ -857,9 +1045,11 @@ qq.FineUploaderBasic.prototype = {
857
1045
 
858
1046
  for (var prop in this._options.callbacks) {
859
1047
  (function() {
860
- var oldCallback = self._options.callbacks[prop];
861
- self._options.callbacks[prop] = function() {
862
- return safeCallback(prop, oldCallback, arguments);
1048
+ var callbackName, callbackFunc;
1049
+ callbackName = prop;
1050
+ callbackFunc = self._options.callbacks[callbackName];
1051
+ self._options.callbacks[callbackName] = function() {
1052
+ return safeCallback(callbackName, callbackFunc, arguments);
863
1053
  }
864
1054
  }());
865
1055
  }
@@ -912,7 +1102,395 @@ qq.FineUploaderBasic.prototype = {
912
1102
  }
913
1103
 
914
1104
  return fileDescriptors;
1105
+ },
1106
+ _createParamsStore: function() {
1107
+ var paramsStore = {},
1108
+ self = this;
1109
+
1110
+ return {
1111
+ setParams: function(params, fileId) {
1112
+ var paramsCopy = {};
1113
+ qq.extend(paramsCopy, params);
1114
+ paramsStore[fileId] = paramsCopy;
1115
+ },
1116
+
1117
+ getParams: function(fileId) {
1118
+ var paramsCopy = {};
1119
+
1120
+ if (fileId !== undefined && paramsStore[fileId]) {
1121
+ qq.extend(paramsCopy, paramsStore[fileId]);
1122
+ }
1123
+ else {
1124
+ qq.extend(paramsCopy, self._options.request.params);
1125
+ }
1126
+
1127
+ return paramsCopy;
1128
+ },
1129
+
1130
+ remove: function(fileId) {
1131
+ return delete paramsStore[fileId];
1132
+ },
1133
+
1134
+ reset: function() {
1135
+ paramsStore = {};
1136
+ }
1137
+ }
1138
+ }
1139
+ };
1140
+ /*globals qq, document*/
1141
+ qq.DragAndDrop = function(o) {
1142
+ "use strict";
1143
+
1144
+ var options, dz, dirPending,
1145
+ droppedFiles = [],
1146
+ droppedEntriesCount = 0,
1147
+ droppedEntriesParsedCount = 0,
1148
+ disposeSupport = new qq.DisposeSupport();
1149
+
1150
+ options = {
1151
+ dropArea: null,
1152
+ extraDropzones: [],
1153
+ hideDropzones: true,
1154
+ multiple: true,
1155
+ classes: {
1156
+ dropActive: null
1157
+ },
1158
+ callbacks: {
1159
+ dropProcessing: function(isProcessing, files) {},
1160
+ error: function(code, filename) {},
1161
+ log: function(message, level) {}
1162
+ }
1163
+ };
1164
+
1165
+ qq.extend(options, o);
1166
+
1167
+ function maybeUploadDroppedFiles() {
1168
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
1169
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
1170
+ dz.dropDisabled(false);
1171
+ options.callbacks.dropProcessing(false, droppedFiles);
1172
+ }
1173
+ }
1174
+ function addDroppedFile(file) {
1175
+ droppedFiles.push(file);
1176
+ droppedEntriesParsedCount+=1;
1177
+ maybeUploadDroppedFiles();
1178
+ }
1179
+
1180
+ function traverseFileTree(entry) {
1181
+ var dirReader, i;
1182
+
1183
+ droppedEntriesCount+=1;
1184
+
1185
+ if (entry.isFile) {
1186
+ entry.file(function(file) {
1187
+ addDroppedFile(file);
1188
+ });
1189
+ }
1190
+ else if (entry.isDirectory) {
1191
+ dirPending = true;
1192
+ dirReader = entry.createReader();
1193
+ dirReader.readEntries(function(entries) {
1194
+ droppedEntriesParsedCount+=1;
1195
+ for (i = 0; i < entries.length; i+=1) {
1196
+ traverseFileTree(entries[i]);
1197
+ }
1198
+
1199
+ dirPending = false;
1200
+
1201
+ if (!entries.length) {
1202
+ maybeUploadDroppedFiles();
1203
+ }
1204
+ });
1205
+ }
1206
+ }
1207
+
1208
+ function handleDataTransfer(dataTransfer) {
1209
+ var i, items, entry;
1210
+
1211
+ options.callbacks.dropProcessing(true);
1212
+ dz.dropDisabled(true);
1213
+
1214
+ if (dataTransfer.files.length > 1 && !options.multiple) {
1215
+ options.callbacks.error('tooManyFilesError', "");
1216
+ }
1217
+ else {
1218
+ droppedFiles = [];
1219
+ droppedEntriesCount = 0;
1220
+ droppedEntriesParsedCount = 0;
1221
+
1222
+ if (qq.isFolderDropSupported(dataTransfer)) {
1223
+ items = dataTransfer.items;
1224
+
1225
+ for (i = 0; i < items.length; i+=1) {
1226
+ entry = items[i].webkitGetAsEntry();
1227
+ if (entry) {
1228
+ //due to a bug in Chrome's File System API impl - #149735
1229
+ if (entry.isFile) {
1230
+ droppedFiles.push(items[i].getAsFile());
1231
+ if (i === items.length-1) {
1232
+ maybeUploadDroppedFiles();
1233
+ }
1234
+ }
1235
+
1236
+ else {
1237
+ traverseFileTree(entry);
1238
+ }
1239
+ }
1240
+ }
1241
+ }
1242
+ else {
1243
+ options.callbacks.dropProcessing(false, dataTransfer.files);
1244
+ dz.dropDisabled(false);
1245
+ }
1246
+ }
1247
+ }
1248
+
1249
+ function setupDropzone(dropArea){
1250
+ dz = new qq.UploadDropZone({
1251
+ element: dropArea,
1252
+ onEnter: function(e){
1253
+ qq(dropArea).addClass(options.classes.dropActive);
1254
+ e.stopPropagation();
1255
+ },
1256
+ onLeaveNotDescendants: function(e){
1257
+ qq(dropArea).removeClass(options.classes.dropActive);
1258
+ },
1259
+ onDrop: function(e){
1260
+ if (options.hideDropzones) {
1261
+ qq(dropArea).hide();
1262
+ }
1263
+ qq(dropArea).removeClass(options.classes.dropActive);
1264
+
1265
+ handleDataTransfer(e.dataTransfer);
1266
+ }
1267
+ });
1268
+
1269
+ disposeSupport.addDisposer(function() {
1270
+ dz.dispose();
1271
+ });
1272
+
1273
+ if (options.hideDropzones) {
1274
+ qq(dropArea).hide();
1275
+ }
1276
+ }
1277
+
1278
+ function isFileDrag(dragEvent) {
1279
+ var fileDrag;
1280
+
1281
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
1282
+ if (val === 'Files') {
1283
+ fileDrag = true;
1284
+ return false;
1285
+ }
1286
+ });
1287
+
1288
+ return fileDrag;
1289
+ }
1290
+
1291
+ function setupDragDrop(){
1292
+ if (options.dropArea) {
1293
+ options.extraDropzones.push(options.dropArea);
1294
+ }
1295
+
1296
+ var i, dropzones = options.extraDropzones;
1297
+
1298
+ for (i=0; i < dropzones.length; i+=1){
1299
+ setupDropzone(dropzones[i]);
1300
+ }
1301
+
1302
+ // IE <= 9 does not support the File API used for drag+drop uploads
1303
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
1304
+ disposeSupport.attach(document, 'dragenter', function(e) {
1305
+ if (!dz.dropDisabled() && isFileDrag(e)) {
1306
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
1307
+ return;
1308
+ }
1309
+
1310
+ options.dropArea.style.display = 'block';
1311
+ for (i=0; i < dropzones.length; i+=1) {
1312
+ dropzones[i].style.display = 'block';
1313
+ }
1314
+ }
1315
+ });
1316
+ }
1317
+ disposeSupport.attach(document, 'dragleave', function(e){
1318
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
1319
+ for (i=0; i < dropzones.length; i+=1) {
1320
+ qq(dropzones[i]).hide();
1321
+ }
1322
+ }
1323
+ });
1324
+ disposeSupport.attach(document, 'drop', function(e){
1325
+ if (options.hideDropzones) {
1326
+ for (i=0; i < dropzones.length; i+=1) {
1327
+ qq(dropzones[i]).hide();
1328
+ }
1329
+ }
1330
+ e.preventDefault();
1331
+ });
1332
+ }
1333
+
1334
+ return {
1335
+ setup: function() {
1336
+ setupDragDrop();
1337
+ },
1338
+
1339
+ setupExtraDropzone: function(element) {
1340
+ options.extraDropzones.push(element);
1341
+ setupDropzone(element);
1342
+ },
1343
+
1344
+ removeExtraDropzone: function(element) {
1345
+ var i, dzs = options.extraDropzones;
1346
+ for(i in dzs) {
1347
+ if (dzs[i] === element) {
1348
+ return dzs.splice(i, 1);
1349
+ }
1350
+ }
1351
+ },
1352
+
1353
+ dispose: function() {
1354
+ disposeSupport.dispose();
1355
+ dz.dispose();
1356
+ }
1357
+ };
1358
+ };
1359
+
1360
+
1361
+ qq.UploadDropZone = function(o){
1362
+ "use strict";
1363
+
1364
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
1365
+
1366
+ options = {
1367
+ element: null,
1368
+ onEnter: function(e){},
1369
+ onLeave: function(e){},
1370
+ // is not fired when leaving element by hovering descendants
1371
+ onLeaveNotDescendants: function(e){},
1372
+ onDrop: function(e){}
1373
+ };
1374
+
1375
+ qq.extend(options, o);
1376
+ element = options.element;
1377
+
1378
+ function dragover_should_be_canceled(){
1379
+ return qq.safari() || (qq.firefox() && qq.windows());
1380
+ }
1381
+
1382
+ function disableDropOutside(e){
1383
+ // run only once for all instances
1384
+ if (!dropOutsideDisabled ){
1385
+
1386
+ // for these cases we need to catch onDrop to reset dropArea
1387
+ if (dragover_should_be_canceled){
1388
+ disposeSupport.attach(document, 'dragover', function(e){
1389
+ e.preventDefault();
1390
+ });
1391
+ } else {
1392
+ disposeSupport.attach(document, 'dragover', function(e){
1393
+ if (e.dataTransfer){
1394
+ e.dataTransfer.dropEffect = 'none';
1395
+ e.preventDefault();
1396
+ }
1397
+ });
1398
+ }
1399
+
1400
+ dropOutsideDisabled = true;
1401
+ }
915
1402
  }
1403
+
1404
+ function isValidFileDrag(e){
1405
+ // e.dataTransfer currently causing IE errors
1406
+ // IE9 does NOT support file API, so drag-and-drop is not possible
1407
+ if (qq.ie() && !qq.ie10()) {
1408
+ return false;
1409
+ }
1410
+
1411
+ var effectTest, dt = e.dataTransfer,
1412
+ // do not check dt.types.contains in webkit, because it crashes safari 4
1413
+ isSafari = qq.safari();
1414
+
1415
+ // dt.effectAllowed is none in Safari 5
1416
+ // dt.types.contains check is for firefox
1417
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
1418
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
1419
+ }
1420
+
1421
+ function isOrSetDropDisabled(isDisabled) {
1422
+ if (isDisabled !== undefined) {
1423
+ preventDrop = isDisabled;
1424
+ }
1425
+ return preventDrop;
1426
+ }
1427
+
1428
+ function attachEvents(){
1429
+ disposeSupport.attach(element, 'dragover', function(e){
1430
+ if (!isValidFileDrag(e)) {
1431
+ return;
1432
+ }
1433
+
1434
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
1435
+ if (effect === 'move' || effect === 'linkMove'){
1436
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
1437
+ } else {
1438
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
1439
+ }
1440
+
1441
+ e.stopPropagation();
1442
+ e.preventDefault();
1443
+ });
1444
+
1445
+ disposeSupport.attach(element, 'dragenter', function(e){
1446
+ if (!isOrSetDropDisabled()) {
1447
+ if (!isValidFileDrag(e)) {
1448
+ return;
1449
+ }
1450
+ options.onEnter(e);
1451
+ }
1452
+ });
1453
+
1454
+ disposeSupport.attach(element, 'dragleave', function(e){
1455
+ if (!isValidFileDrag(e)) {
1456
+ return;
1457
+ }
1458
+
1459
+ options.onLeave(e);
1460
+
1461
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
1462
+ // do not fire when moving a mouse over a descendant
1463
+ if (qq(this).contains(relatedTarget)) {
1464
+ return;
1465
+ }
1466
+
1467
+ options.onLeaveNotDescendants(e);
1468
+ });
1469
+
1470
+ disposeSupport.attach(element, 'drop', function(e){
1471
+ if (!isOrSetDropDisabled()) {
1472
+ if (!isValidFileDrag(e)) {
1473
+ return;
1474
+ }
1475
+
1476
+ e.preventDefault();
1477
+ options.onDrop(e);
1478
+ }
1479
+ });
1480
+ }
1481
+
1482
+ disableDropOutside();
1483
+ attachEvents();
1484
+
1485
+ return {
1486
+ dropDisabled: function(isDisabled) {
1487
+ return isOrSetDropDisabled(isDisabled);
1488
+ },
1489
+
1490
+ dispose: function() {
1491
+ disposeSupport.dispose();
1492
+ }
1493
+ };
916
1494
  };
917
1495
  /**
918
1496
  * Class that creates upload widget with drag-and-drop and file list
@@ -937,12 +1515,14 @@ qq.FineUploader = function(o){
937
1515
  retryButton: 'Retry',
938
1516
  failUpload: 'Upload failed',
939
1517
  dragZone: 'Drop files here to upload',
1518
+ dropProcessing: 'Processing dropped files...',
940
1519
  formatProgress: "{percent}% of {total_size}",
941
1520
  waitingForResponse: "Processing..."
942
1521
  },
943
1522
  template: '<div class="qq-uploader">' +
944
1523
  ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '<div class="qq-upload-drop-area"><span>{dragZoneText}</span></div>' : '') +
945
1524
  (!this._options.button ? '<div class="qq-upload-button"><div>{uploadButtonText}</div></div>' : '') +
1525
+ '<span class="qq-drop-processing"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></span>' +
946
1526
  (!this._options.listElement ? '<ul class="qq-upload-list"></ul>' : '') +
947
1527
  '</div>',
948
1528
 
@@ -958,7 +1538,6 @@ qq.FineUploader = function(o){
958
1538
  '<span class="qq-upload-status-text">{statusText}</span>' +
959
1539
  '</li>',
960
1540
  classes: {
961
- // used to get elements from templates
962
1541
  button: 'qq-upload-button',
963
1542
  drop: 'qq-upload-drop-area',
964
1543
  dropActive: 'qq-upload-drop-area-active',
@@ -975,13 +1554,14 @@ qq.FineUploader = function(o){
975
1554
  retry: 'qq-upload-retry',
976
1555
  statusText: 'qq-upload-status-text',
977
1556
 
978
- // added to list item <li> when upload completes
979
- // used in css to hide progress spinner
980
1557
  success: 'qq-upload-success',
981
1558
  fail: 'qq-upload-fail',
982
1559
 
983
1560
  successIcon: null,
984
- failIcon: null
1561
+ failIcon: null,
1562
+
1563
+ dropProcessing: 'qq-drop-processing',
1564
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
985
1565
  },
986
1566
  failedUploadTextDisplay: {
987
1567
  mode: 'default', //default, custom, or none
@@ -1010,6 +1590,7 @@ qq.FineUploader = function(o){
1010
1590
  // same for the Cancel button and Fail message text
1011
1591
  this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
1012
1592
  this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
1593
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
1013
1594
  this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
1014
1595
  this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
1015
1596
  this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
@@ -1025,7 +1606,8 @@ qq.FineUploader = function(o){
1025
1606
  }
1026
1607
 
1027
1608
  this._bindCancelAndRetryEvents();
1028
- this._setupDragDrop();
1609
+
1610
+ this._dnd = this._setupDragAndDrop();
1029
1611
  };
1030
1612
 
1031
1613
  // inherit from Basic Uploader
@@ -1037,11 +1619,10 @@ qq.extend(qq.FineUploader.prototype, {
1037
1619
  this._listElement.innerHTML = "";
1038
1620
  },
1039
1621
  addExtraDropzone: function(element){
1040
- this._setupExtraDropzone(element);
1622
+ this._dnd.setupExtraDropzone(element);
1041
1623
  },
1042
1624
  removeExtraDropzone: function(element){
1043
- var dzs = this._options.dragAndDrop.extraDropzones;
1044
- for(var i in dzs) if (dzs[i] === element) return this._options.dragAndDrop.extraDropzones.splice(i,1);
1625
+ return this._dnd.removeExtraDropzone(element);
1045
1626
  },
1046
1627
  getItemByFileId: function(id){
1047
1628
  var item = this._listElement.firstChild;
@@ -1053,6 +1634,11 @@ qq.extend(qq.FineUploader.prototype, {
1053
1634
  item = item.nextSibling;
1054
1635
  }
1055
1636
  },
1637
+ cancel: function(fileId) {
1638
+ qq.FineUploaderBasic.prototype.cancel.apply(this, arguments);
1639
+ var item = this.getItemByFileId(fileId);
1640
+ qq(item).remove();
1641
+ },
1056
1642
  reset: function() {
1057
1643
  qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
1058
1644
  this._element.innerHTML = this._options.template;
@@ -1061,7 +1647,59 @@ qq.extend(qq.FineUploader.prototype, {
1061
1647
  this._button = this._createUploadButton(this._find(this._element, 'button'));
1062
1648
  }
1063
1649
  this._bindCancelAndRetryEvents();
1064
- this._setupDragDrop();
1650
+ this._dnd.dispose();
1651
+ this._dnd = this._setupDragAndDrop();
1652
+ },
1653
+ _setupDragAndDrop: function() {
1654
+ var self = this,
1655
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
1656
+ dnd, preventSelectFiles, defaultDropAreaEl;
1657
+
1658
+ preventSelectFiles = function(event) {
1659
+ event.preventDefault();
1660
+ };
1661
+
1662
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
1663
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
1664
+ }
1665
+
1666
+ dnd = new qq.DragAndDrop({
1667
+ dropArea: defaultDropAreaEl,
1668
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
1669
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
1670
+ multiple: this._options.multiple,
1671
+ classes: {
1672
+ dropActive: this._options.classes.dropActive
1673
+ },
1674
+ callbacks: {
1675
+ dropProcessing: function(isProcessing, files) {
1676
+ var input = self._button.getInput();
1677
+
1678
+ if (isProcessing) {
1679
+ qq(dropProcessingEl).css({display: 'block'});
1680
+ qq(input).attach('click', preventSelectFiles);
1681
+ }
1682
+ else {
1683
+ qq(dropProcessingEl).hide();
1684
+ qq(input).detach('click', preventSelectFiles);
1685
+ }
1686
+
1687
+ if (files) {
1688
+ self.addFiles(files);
1689
+ }
1690
+ },
1691
+ error: function(code, filename) {
1692
+ self._error(code, filename);
1693
+ },
1694
+ log: function(message, level) {
1695
+ self.log(message, level);
1696
+ }
1697
+ }
1698
+ });
1699
+
1700
+ dnd.setup();
1701
+
1702
+ return dnd;
1065
1703
  },
1066
1704
  _leaving_document_out: function(e){
1067
1705
  return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
@@ -1083,88 +1721,6 @@ qq.extend(qq.FineUploader.prototype, {
1083
1721
 
1084
1722
  return element;
1085
1723
  },
1086
- _setupExtraDropzone: function(element){
1087
- this._options.dragAndDrop.extraDropzones.push(element);
1088
- this._setupDropzone(element);
1089
- },
1090
- _setupDropzone: function(dropArea){
1091
- var self = this;
1092
-
1093
- var dz = new qq.UploadDropZone({
1094
- element: dropArea,
1095
- onEnter: function(e){
1096
- qq(dropArea).addClass(self._classes.dropActive);
1097
- e.stopPropagation();
1098
- },
1099
- onLeave: function(e){
1100
- //e.stopPropagation();
1101
- },
1102
- onLeaveNotDescendants: function(e){
1103
- qq(dropArea).removeClass(self._classes.dropActive);
1104
- },
1105
- onDrop: function(e){
1106
- if (self._options.dragAndDrop.hideDropzones) {
1107
- qq(dropArea).hide();
1108
- }
1109
-
1110
- qq(dropArea).removeClass(self._classes.dropActive);
1111
- if (e.dataTransfer.files.length > 1 && !self._options.multiple) {
1112
- self._error('tooManyFilesError', "");
1113
- }
1114
- else {
1115
- self._uploadFileList(e.dataTransfer.files);
1116
- }
1117
- }
1118
- });
1119
-
1120
- this.addDisposer(function() { dz.dispose(); });
1121
-
1122
- if (this._options.dragAndDrop.hideDropzones) {
1123
- qq(dropArea).hide();
1124
- }
1125
- },
1126
- _setupDragDrop: function(){
1127
- var self, dropArea;
1128
-
1129
- self = this;
1130
-
1131
- if (!this._options.dragAndDrop.disableDefaultDropzone) {
1132
- dropArea = this._find(this._element, 'drop');
1133
- this._options.dragAndDrop.extraDropzones.push(dropArea);
1134
- }
1135
-
1136
- var dropzones = this._options.dragAndDrop.extraDropzones;
1137
- var i;
1138
- for (i=0; i < dropzones.length; i++){
1139
- this._setupDropzone(dropzones[i]);
1140
- }
1141
-
1142
- // IE <= 9 does not support the File API used for drag+drop uploads
1143
- if (!this._options.dragAndDrop.disableDefaultDropzone && (!qq.ie() || qq.ie10())) {
1144
- this._attach(document, 'dragenter', function(e){
1145
- if (qq(dropArea).hasClass(self._classes.dropDisabled)) return;
1146
-
1147
- dropArea.style.display = 'block';
1148
- for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'block'; }
1149
-
1150
- });
1151
- }
1152
- this._attach(document, 'dragleave', function(e){
1153
- if (self._options.dragAndDrop.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
1154
- for (i=0; i < dropzones.length; i++) {
1155
- qq(dropzones[i]).hide();
1156
- }
1157
- }
1158
- });
1159
- qq(document).attach('drop', function(e){
1160
- if (self._options.dragAndDrop.hideDropzones) {
1161
- for (i=0; i < dropzones.length; i++) {
1162
- qq(dropzones[i]).hide();
1163
- }
1164
- }
1165
- e.preventDefault();
1166
- });
1167
- },
1168
1724
  _onSubmit: function(id, fileName){
1169
1725
  qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
1170
1726
  this._addToList(id, fileName);
@@ -1213,7 +1769,7 @@ qq.extend(qq.FineUploader.prototype, {
1213
1769
  qq(item).removeClass(this._classes.retrying);
1214
1770
  qq(this._find(item, 'progressBar')).hide();
1215
1771
 
1216
- if (!this._options.disableCancelForFormUploads || qq.UploadHandlerXhr.isSupported()) {
1772
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
1217
1773
  qq(this._find(item, 'cancel')).hide();
1218
1774
  }
1219
1775
  qq(this._find(item, 'spinner')).hide();
@@ -1282,7 +1838,7 @@ qq.extend(qq.FineUploader.prototype, {
1282
1838
  },
1283
1839
  _addToList: function(id, fileName){
1284
1840
  var item = qq.toElement(this._options.fileTemplate);
1285
- if (this._options.disableCancelForFormUploads && !qq.UploadHandlerXhr.isSupported()) {
1841
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
1286
1842
  var cancelLink = this._find(item, 'cancel');
1287
1843
  qq(cancelLink).remove();
1288
1844
  }
@@ -1306,7 +1862,7 @@ qq.extend(qq.FineUploader.prototype, {
1306
1862
  var self = this,
1307
1863
  list = this._listElement;
1308
1864
 
1309
- this._attach(list, 'click', function(e){
1865
+ this._disposeSupport.attach(list, 'click', function(e){
1310
1866
  e = e || window.event;
1311
1867
  var target = e.target || e.srcElement;
1312
1868
 
@@ -1320,7 +1876,6 @@ qq.extend(qq.FineUploader.prototype, {
1320
1876
 
1321
1877
  if (qq(target).hasClass(self._classes.cancel)) {
1322
1878
  self.cancel(item.qqFileId);
1323
- qq(item).remove();
1324
1879
  }
1325
1880
  else {
1326
1881
  qq(item).removeClass(self._classes.retryable);
@@ -1378,7 +1933,7 @@ qq.extend(qq.FineUploader.prototype, {
1378
1933
  spinnerEl.style.display = "inline-block";
1379
1934
  },
1380
1935
  _showCancelLink: function(item) {
1381
- if (!this._options.disableCancelForFormUploads || qq.UploadHandlerXhr.isSupported()) {
1936
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
1382
1937
  var cancelLink = this._find(item, 'cancel');
1383
1938
  cancelLink.style.display = 'inline';
1384
1939
  }
@@ -1388,107 +1943,6 @@ qq.extend(qq.FineUploader.prototype, {
1388
1943
  this._options.showMessage(message);
1389
1944
  }
1390
1945
  });
1391
-
1392
- qq.UploadDropZone = function(o){
1393
- this._options = {
1394
- element: null,
1395
- onEnter: function(e){},
1396
- onLeave: function(e){},
1397
- // is not fired when leaving element by hovering descendants
1398
- onLeaveNotDescendants: function(e){},
1399
- onDrop: function(e){}
1400
- };
1401
- qq.extend(this._options, o);
1402
- qq.extend(this, qq.DisposeSupport);
1403
-
1404
- this._element = this._options.element;
1405
-
1406
- this._disableDropOutside();
1407
- this._attachEvents();
1408
- };
1409
-
1410
- qq.UploadDropZone.prototype = {
1411
- _dragover_should_be_canceled: function(){
1412
- return qq.safari() || (qq.firefox() && qq.windows());
1413
- },
1414
- _disableDropOutside: function(e){
1415
- // run only once for all instances
1416
- if (!qq.UploadDropZone.dropOutsideDisabled ){
1417
-
1418
- // for these cases we need to catch onDrop to reset dropArea
1419
- if (this._dragover_should_be_canceled){
1420
- qq(document).attach('dragover', function(e){
1421
- e.preventDefault();
1422
- });
1423
- } else {
1424
- qq(document).attach('dragover', function(e){
1425
- if (e.dataTransfer){
1426
- e.dataTransfer.dropEffect = 'none';
1427
- e.preventDefault();
1428
- }
1429
- });
1430
- }
1431
-
1432
- qq.UploadDropZone.dropOutsideDisabled = true;
1433
- }
1434
- },
1435
- _attachEvents: function(){
1436
- var self = this;
1437
-
1438
- self._attach(self._element, 'dragover', function(e){
1439
- if (!self._isValidFileDrag(e)) return;
1440
-
1441
- var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
1442
- if (effect == 'move' || effect == 'linkMove'){
1443
- e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
1444
- } else {
1445
- e.dataTransfer.dropEffect = 'copy'; // for Chrome
1446
- }
1447
-
1448
- e.stopPropagation();
1449
- e.preventDefault();
1450
- });
1451
-
1452
- self._attach(self._element, 'dragenter', function(e){
1453
- if (!self._isValidFileDrag(e)) return;
1454
-
1455
- self._options.onEnter(e);
1456
- });
1457
-
1458
- self._attach(self._element, 'dragleave', function(e){
1459
- if (!self._isValidFileDrag(e)) return;
1460
-
1461
- self._options.onLeave(e);
1462
-
1463
- var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
1464
- // do not fire when moving a mouse over a descendant
1465
- if (qq(this).contains(relatedTarget)) return;
1466
-
1467
- self._options.onLeaveNotDescendants(e);
1468
- });
1469
-
1470
- self._attach(self._element, 'drop', function(e){
1471
- if (!self._isValidFileDrag(e)) return;
1472
-
1473
- e.preventDefault();
1474
- self._options.onDrop(e);
1475
- });
1476
- },
1477
- _isValidFileDrag: function(e){
1478
- // e.dataTransfer currently causing IE errors
1479
- // IE9 does NOT support file API, so drag-and-drop is not possible
1480
- if (qq.ie() && !qq.ie10()) return false;
1481
-
1482
- var dt = e.dataTransfer,
1483
- // do not check dt.types.contains in webkit, because it crashes safari 4
1484
- isSafari = qq.safari();
1485
-
1486
- // dt.effectAllowed is none in Safari 5
1487
- // dt.types.contains check is for firefox
1488
- var effectTest = qq.ie10() ? true : dt.effectAllowed != 'none';
1489
- return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
1490
- }
1491
- };
1492
1946
  /**
1493
1947
  * Class for uploading files, uploading itself is handled by child classes
1494
1948
  */
@@ -1497,6 +1951,7 @@ qq.UploadHandlerAbstract = function(o){
1497
1951
  this._options = {
1498
1952
  debug: false,
1499
1953
  endpoint: '/upload.php',
1954
+ paramsInBody: false,
1500
1955
  // maximum number of concurrent uploads
1501
1956
  maxConnections: 999,
1502
1957
  log: function(str, level) {},
@@ -1510,8 +1965,6 @@ qq.UploadHandlerAbstract = function(o){
1510
1965
  qq.extend(this._options, o);
1511
1966
 
1512
1967
  this._queue = [];
1513
- // params for files in queue
1514
- this._params = [];
1515
1968
 
1516
1969
  this.log = this._options.log;
1517
1970
  };
@@ -1522,27 +1975,23 @@ qq.UploadHandlerAbstract.prototype = {
1522
1975
  **/
1523
1976
  add: function(file){},
1524
1977
  /**
1525
- * Sends the file identified by id and additional query params to the server
1978
+ * Sends the file identified by id
1526
1979
  */
1527
- upload: function(id, params){
1980
+ upload: function(id){
1528
1981
  var len = this._queue.push(id);
1529
1982
 
1530
- var copy = {};
1531
- qq.extend(copy, params);
1532
- this._params[id] = copy;
1533
-
1534
1983
  // if too many active uploads, wait...
1535
1984
  if (len <= this._options.maxConnections){
1536
- this._upload(id, this._params[id]);
1985
+ this._upload(id);
1537
1986
  }
1538
1987
  },
1539
1988
  retry: function(id) {
1540
1989
  var i = qq.indexOf(this._queue, id);
1541
1990
  if (i >= 0) {
1542
- this._upload(id, this._params[id]);
1991
+ this._upload(id);
1543
1992
  }
1544
1993
  else {
1545
- this.upload(id, this._params[id]);
1994
+ this.upload(id);
1546
1995
  }
1547
1996
  },
1548
1997
  /**
@@ -1550,6 +1999,7 @@ qq.UploadHandlerAbstract.prototype = {
1550
1999
  */
1551
2000
  cancel: function(id){
1552
2001
  this.log('Cancelling ' + id);
2002
+ this._options.paramsStore.remove(id);
1553
2003
  this._cancel(id);
1554
2004
  this._dequeue(id);
1555
2005
  },
@@ -1580,7 +2030,6 @@ qq.UploadHandlerAbstract.prototype = {
1580
2030
  reset: function() {
1581
2031
  this.log('Resetting upload handler');
1582
2032
  this._queue = [];
1583
- this._params = [];
1584
2033
  },
1585
2034
  /**
1586
2035
  * Actual upload method
@@ -1601,7 +2050,7 @@ qq.UploadHandlerAbstract.prototype = {
1601
2050
 
1602
2051
  if (this._queue.length >= max && i < max){
1603
2052
  var nextId = this._queue[max-1];
1604
- this._upload(nextId, this._params[nextId]);
2053
+ this._upload(nextId);
1605
2054
  }
1606
2055
  },
1607
2056
  /**
@@ -1625,7 +2074,7 @@ qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
1625
2074
  qq.extend(qq.UploadHandlerForm.prototype, {
1626
2075
  add: function(fileInput){
1627
2076
  fileInput.setAttribute('name', this._options.inputName);
1628
- var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
2077
+ var id = qq.getUniqueId();
1629
2078
 
1630
2079
  this._inputs[id] = fileInput;
1631
2080
 
@@ -1664,7 +2113,7 @@ qq.extend(qq.UploadHandlerForm.prototype, {
1664
2113
  qq(iframe).remove();
1665
2114
  }
1666
2115
  },
1667
- _upload: function(id, params){
2116
+ _upload: function(id){
1668
2117
  this._options.onUpload(id, this.getName(id), false);
1669
2118
  var input = this._inputs[id];
1670
2119
 
@@ -1673,10 +2122,9 @@ qq.extend(qq.UploadHandlerForm.prototype, {
1673
2122
  }
1674
2123
 
1675
2124
  var fileName = this.getName(id);
1676
- params[this._options.inputName] = fileName;
1677
2125
 
1678
2126
  var iframe = this._createIframe(id);
1679
- var form = this._createForm(iframe, params);
2127
+ var form = this._createForm(iframe, this._options.paramsStore.getParams(id));
1680
2128
  form.appendChild(input);
1681
2129
 
1682
2130
  var self = this;
@@ -1793,12 +2241,18 @@ qq.extend(qq.UploadHandlerForm.prototype, {
1793
2241
  // form.setAttribute('method', 'post');
1794
2242
  // form.setAttribute('enctype', 'multipart/form-data');
1795
2243
  // Because in this case file won't be attached to request
1796
- var protocol = this._options.demoMode ? "GET" : "POST"
1797
- var form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>');
2244
+ var protocol = this._options.demoMode ? "GET" : "POST",
2245
+ form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>'),
2246
+ url = this._options.endpoint;
1798
2247
 
1799
- var queryString = qq.obj2url(params, this._options.endpoint);
2248
+ if (!this._options.paramsInBody) {
2249
+ url = qq.obj2url(params, this._options.endpoint);
2250
+ }
2251
+ else {
2252
+ qq.obj2Inputs(params, form);
2253
+ }
1800
2254
 
1801
- form.setAttribute('action', queryString);
2255
+ form.setAttribute('action', url);
1802
2256
  form.setAttribute('target', iframe.name);
1803
2257
  form.style.display = 'none';
1804
2258
  document.body.appendChild(form);
@@ -1820,18 +2274,6 @@ qq.UploadHandlerXhr = function(o){
1820
2274
  this._loaded = [];
1821
2275
  };
1822
2276
 
1823
- // static method
1824
- qq.UploadHandlerXhr.isSupported = function(){
1825
- var input = document.createElement('input');
1826
- input.type = 'file';
1827
-
1828
- return (
1829
- 'multiple' in input &&
1830
- typeof File != "undefined" &&
1831
- typeof FormData != "undefined" &&
1832
- typeof (new XMLHttpRequest()).upload != "undefined" );
1833
- };
1834
-
1835
2277
  // @inherits qq.UploadHandlerAbstract
1836
2278
  qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
1837
2279
 
@@ -1873,20 +2315,22 @@ qq.extend(qq.UploadHandlerXhr.prototype, {
1873
2315
  this._loaded = [];
1874
2316
  },
1875
2317
  /**
1876
- * Sends the file identified by id and additional query params to the server
1877
- * @param {Object} params name-value string pairs
2318
+ * Sends the file identified by id to the server
1878
2319
  */
1879
- _upload: function(id, params){
1880
- this._options.onUpload(id, this.getName(id), true);
1881
-
2320
+ _upload: function(id){
1882
2321
  var file = this._files[id],
1883
2322
  name = this.getName(id),
1884
- size = this.getSize(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;
2328
+
2329
+ this._options.onUpload(id, this.getName(id), true);
1885
2330
 
1886
2331
  this._loaded[id] = 0;
1887
2332
 
1888
- var xhr = this._xhrs[id] = new XMLHttpRequest();
1889
- var self = this;
2333
+ xhr = this._xhrs[id] = new XMLHttpRequest();
1890
2334
 
1891
2335
  xhr.upload.onprogress = function(e){
1892
2336
  if (e.lengthComputable){
@@ -1896,33 +2340,43 @@ qq.extend(qq.UploadHandlerXhr.prototype, {
1896
2340
  };
1897
2341
 
1898
2342
  xhr.onreadystatechange = function(){
1899
- if (xhr.readyState == 4){
2343
+ if (xhr.readyState === 4){
1900
2344
  self._onComplete(id, xhr);
1901
2345
  }
1902
2346
  };
1903
2347
 
1904
- // build query string
1905
- params = params || {};
1906
- params[this._options.inputName] = name;
1907
- var queryString = qq.obj2url(params, this._options.endpoint);
2348
+ params = this._options.paramsStore.getParams(id);
2349
+
2350
+ //build query string
2351
+ if (!this._options.paramsInBody) {
2352
+ params[this._options.inputName] = name;
2353
+ url = qq.obj2url(params, this._options.endpoint);
2354
+ }
1908
2355
 
1909
- var protocol = this._options.demoMode ? "GET" : "POST";
1910
- xhr.open(protocol, queryString, true);
2356
+ xhr.open(protocol, url, true);
1911
2357
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
1912
2358
  xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
1913
2359
  xhr.setRequestHeader("Cache-Control", "no-cache");
1914
- if (this._options.forceMultipart) {
1915
- var formData = new FormData();
2360
+ if (this._options.forceMultipart || this._options.paramsInBody) {
2361
+ formData = new FormData();
2362
+
2363
+ if (this._options.paramsInBody) {
2364
+ qq.obj2FormData(params, formData);
2365
+ }
2366
+
1916
2367
  formData.append(this._options.inputName, file);
1917
2368
  file = formData;
1918
2369
  } else {
1919
2370
  xhr.setRequestHeader("Content-Type", "application/octet-stream");
1920
2371
  //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
1921
- xhr.setRequestHeader("X-Mime-Type",file.type );
2372
+ xhr.setRequestHeader("X-Mime-Type", file.type);
1922
2373
  }
2374
+
1923
2375
  for (key in this._options.customHeaders){
1924
- xhr.setRequestHeader(key, this._options.customHeaders[key]);
1925
- };
2376
+ if (this._options.customHeaders.hasOwnProperty(key)) {
2377
+ xhr.setRequestHeader(key, this._options.customHeaders[key]);
2378
+ }
2379
+ }
1926
2380
 
1927
2381
  this.log('Sending upload request for ' + id);
1928
2382
  xhr.send(file);