fileuploader-rails 3.0.0.1 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  module Fileuploader
2
2
  module Rails
3
- VERSION = "3.0.0.1"
3
+ VERSION = "3.1.1"
4
4
  end
5
5
  end
File without changes
@@ -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);
@@ -850,69 +1038,459 @@ qq.FineUploaderBasic.prototype = {
850
1038
  try {
851
1039
  return callback.apply(self, args);
852
1040
  }
853
- catch (exception) {
854
- self.log("Caught exception in '" + name + "' callback - " + exception, 'error');
1041
+ catch (exception) {
1042
+ self.log("Caught exception in '" + name + "' callback - " + exception, 'error');
1043
+ }
1044
+ }
1045
+
1046
+ for (var prop in this._options.callbacks) {
1047
+ (function() {
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);
1053
+ }
1054
+ }());
1055
+ }
1056
+ },
1057
+ _parseFileName: function(file) {
1058
+ var name;
1059
+
1060
+ if (file.value){
1061
+ // it is a file input
1062
+ // get input value and remove path to normalize
1063
+ name = file.value.replace(/.*(\/|\\)/, "");
1064
+ } else {
1065
+ // fix missing properties in Safari 4 and firefox 11.0a2
1066
+ name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
1067
+ }
1068
+
1069
+ return name;
1070
+ },
1071
+ _parseFileSize: function(file) {
1072
+ var size;
1073
+
1074
+ if (!file.value){
1075
+ // fix missing properties in Safari 4 and firefox 11.0a2
1076
+ size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
1077
+ }
1078
+
1079
+ return size;
1080
+ },
1081
+ _getValidationDescriptor: function(file) {
1082
+ var name, size, fileDescriptor;
1083
+
1084
+ fileDescriptor = {};
1085
+ name = this._parseFileName(file);
1086
+ size = this._parseFileSize(file);
1087
+
1088
+ fileDescriptor.name = name;
1089
+ if (size) {
1090
+ fileDescriptor.size = size;
1091
+ }
1092
+
1093
+ return fileDescriptor;
1094
+ },
1095
+ _getValidationDescriptors: function(files) {
1096
+ var index, fileDescriptors;
1097
+
1098
+ fileDescriptors = [];
1099
+
1100
+ for (index = 0; index < files.length; index++) {
1101
+ fileDescriptors.push(files[index]);
1102
+ }
1103
+
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
+ }
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
855
1439
  }
856
- }
857
1440
 
858
- for (var prop in this._options.callbacks) {
859
- (function() {
860
- var oldCallback = self._options.callbacks[prop];
861
- self._options.callbacks[prop] = function() {
862
- return safeCallback(prop, oldCallback, arguments);
1441
+ e.stopPropagation();
1442
+ e.preventDefault();
1443
+ });
1444
+
1445
+ disposeSupport.attach(element, 'dragenter', function(e){
1446
+ if (!isOrSetDropDisabled()) {
1447
+ if (!isValidFileDrag(e)) {
1448
+ return;
863
1449
  }
864
- }());
865
- }
866
- },
867
- _parseFileName: function(file) {
868
- var name;
1450
+ options.onEnter(e);
1451
+ }
1452
+ });
869
1453
 
870
- if (file.value){
871
- // it is a file input
872
- // get input value and remove path to normalize
873
- name = file.value.replace(/.*(\/|\\)/, "");
874
- } else {
875
- // fix missing properties in Safari 4 and firefox 11.0a2
876
- name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
877
- }
1454
+ disposeSupport.attach(element, 'dragleave', function(e){
1455
+ if (!isValidFileDrag(e)) {
1456
+ return;
1457
+ }
878
1458
 
879
- return name;
880
- },
881
- _parseFileSize: function(file) {
882
- var size;
1459
+ options.onLeave(e);
883
1460
 
884
- if (!file.value){
885
- // fix missing properties in Safari 4 and firefox 11.0a2
886
- size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
887
- }
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
+ }
888
1466
 
889
- return size;
890
- },
891
- _getValidationDescriptor: function(file) {
892
- var name, size, fileDescriptor;
1467
+ options.onLeaveNotDescendants(e);
1468
+ });
893
1469
 
894
- fileDescriptor = {};
895
- name = this._parseFileName(file);
896
- size = this._parseFileSize(file);
1470
+ disposeSupport.attach(element, 'drop', function(e){
1471
+ if (!isOrSetDropDisabled()) {
1472
+ if (!isValidFileDrag(e)) {
1473
+ return;
1474
+ }
897
1475
 
898
- fileDescriptor.name = name;
899
- if (size) {
900
- fileDescriptor.size = size;
901
- }
1476
+ e.preventDefault();
1477
+ options.onDrop(e);
1478
+ }
1479
+ });
1480
+ }
902
1481
 
903
- return fileDescriptor;
904
- },
905
- _getValidationDescriptors: function(files) {
906
- var index, fileDescriptors;
1482
+ disableDropOutside();
1483
+ attachEvents();
907
1484
 
908
- fileDescriptors = [];
1485
+ return {
1486
+ dropDisabled: function(isDisabled) {
1487
+ return isOrSetDropDisabled(isDisabled);
1488
+ },
909
1489
 
910
- for (index = 0; index < files.length; index++) {
911
- fileDescriptors.push(files[index]);
1490
+ dispose: function() {
1491
+ disposeSupport.dispose();
912
1492
  }
913
-
914
- return fileDescriptors;
915
- }
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);
@@ -1974,16 +2428,17 @@ qq.extend(qq.UploadHandlerXhr.prototype, {
1974
2428
  }
1975
2429
  }
1976
2430
  });
2431
+ /*globals jQuery, qq*/
1977
2432
  (function($) {
1978
2433
  "use strict";
1979
- var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformOptions, isValidCommand,
2434
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
1980
2435
  delegateCommand;
1981
2436
 
1982
2437
  pluginOptions = ['uploaderType'];
1983
2438
 
1984
2439
  init = function (options) {
1985
2440
  if (options) {
1986
- var xformedOpts = transformOptions(options);
2441
+ var xformedOpts = transformVariables(options);
1987
2442
  addCallbacks(xformedOpts);
1988
2443
 
1989
2444
  if (pluginOption('uploaderType') === 'basic') {
@@ -2045,7 +2500,7 @@ qq.extend(qq.UploadHandlerXhr.prototype, {
2045
2500
  };
2046
2501
 
2047
2502
  //transform jQuery objects into HTMLElements, and pass along all other option properties
2048
- transformOptions = function(source, dest) {
2503
+ transformVariables = function(source, dest) {
2049
2504
  var xformed, arrayVals;
2050
2505
 
2051
2506
  if (dest === undefined) {
@@ -2069,7 +2524,7 @@ qq.extend(qq.UploadHandlerXhr.prototype, {
2069
2524
  }
2070
2525
  else if ($.isPlainObject(val)) {
2071
2526
  xformed[prop] = {};
2072
- transformOptions(val, xformed[prop]);
2527
+ transformVariables(val, xformed[prop]);
2073
2528
  }
2074
2529
  else if ($.isArray(val)) {
2075
2530
  arrayVals = [];
@@ -2102,20 +2557,39 @@ qq.extend(qq.UploadHandlerXhr.prototype, {
2102
2557
  //assuming we have already verified that this is a valid command, call the associated function in the underlying
2103
2558
  // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
2104
2559
  delegateCommand = function(command) {
2105
- return uploader()[command].apply(uploader(), Array.prototype.slice.call(arguments, 1));
2560
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
2561
+
2562
+ transformVariables(origArgs, xformedArgs);
2563
+
2564
+ return uploader()[command].apply(uploader(), xformedArgs);
2106
2565
  };
2107
2566
 
2108
2567
  $.fn.fineUploader = function(optionsOrCommand) {
2109
- $el = this;
2568
+ var self = this, selfArgs = arguments, retVals = [];
2110
2569
 
2111
- if (uploader() && isValidCommand(optionsOrCommand)) {
2112
- return delegateCommand.apply(this, arguments);
2113
- }
2114
- else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
2115
- return init.apply(this, arguments);
2570
+ this.each(function(index, el) {
2571
+ $el = $(el);
2572
+
2573
+ if (uploader() && isValidCommand(optionsOrCommand)) {
2574
+ retVals.push(delegateCommand.apply(self, selfArgs));
2575
+
2576
+ if (self.length === 1) {
2577
+ return false;
2578
+ }
2579
+ }
2580
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
2581
+ init.apply(self, selfArgs);
2582
+ }
2583
+ else {
2584
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
2585
+ }
2586
+ });
2587
+
2588
+ if (retVals.length === 1) {
2589
+ return retVals[0];
2116
2590
  }
2117
- else {
2118
- $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
2591
+ else if (retVals.length > 1) {
2592
+ return retVals;
2119
2593
  }
2120
2594
 
2121
2595
  return this;