fileuploader-rails 3.4.1 → 3.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,12 +1,18 @@
1
1
  # Fine Uploader 3.4.1 for Rails
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/fileuploader-rails.png)][gem]
4
+ [![Dependency Status](https://gemnasium.com/lebedev-yury/fileuploader-rails.png?travis)][gemnasium]
5
+
6
+ [gem]: https://rubygems.org/gems/fileuploader-rails
7
+ [gemnasium]: https://gemnasium.com/lebedev-yury/fileuploader-rails
8
+
3
9
  ### Fineuploader plugin is free for open source projects only under the GPLv3 license.
4
10
 
5
11
  [Fineuploader](http://fineuploader.com/) is a Javascript plugin written by [Andrew Valums](http://github.com/valums/), and actively developed by [Ray Nicholus](http://lnkd.in/Nkhx2C). This plugin uses an XMLHttpRequest (AJAX) for uploading multiple files with a progress-bar in FF3.6+, Safari4+, Chrome and falls back to hidden-iframe-based upload in other browsers (namely IE), providing good user experience everywhere. It does not use Flash, jQuery, or any external libraries.
6
12
 
7
13
  This gem integrates this fantastic plugin with Rails 3.1+ Asset Pipeline.
8
14
 
9
- [Plugin documentation](https://github.com/valums/file-uploader/blob/master/readme.md)
15
+ [Plugin documentation](https://github.com/Widen/fine-uploader/blob/master/readme.md)
10
16
 
11
17
  ## Installing Gem
12
18
 
@@ -28,5 +34,9 @@ Require the stylesheet file to app/assets/stylesheets/application.css
28
34
 
29
35
  *= require fineuploader
30
36
 
37
+ ## Handling uploaded files
38
+
39
+ [Handling raw file uploads in Rails](https://github.com/lebedev-yury/fileuploader-rails/wiki/Handling-raw-file-uploads-in-Rails)
40
+
31
41
  ## Thanks
32
42
  Thanks to [Andrew Valums](http://github.com/valums/) and [Ray Nicholus](http://lnkd.in/Nkhx2C) for this plugin.
@@ -1,5 +1,5 @@
1
1
  module Fileuploader
2
2
  module Rails
3
- VERSION = "3.4.1"
3
+ VERSION = "3.5"
4
4
  end
5
5
  end
@@ -186,7 +186,7 @@ qq.trimStr = function(string) {
186
186
 
187
187
  qq.isFileOrInput = function(maybeFileOrInput) {
188
188
  "use strict";
189
- if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
189
+ if (window.File && maybeFileOrInput instanceof File) {
190
190
  return true;
191
191
  }
192
192
  else if (window.HTMLInputElement) {
@@ -209,7 +209,7 @@ qq.isFileOrInput = function(maybeFileOrInput) {
209
209
 
210
210
  qq.isBlob = function(maybeBlob) {
211
211
  "use strict";
212
- return window.Blob && maybeBlob instanceof Blob;
212
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
213
213
  };
214
214
 
215
215
  qq.isXhrUploadSupported = function() {
@@ -233,7 +233,7 @@ qq.isFileChunkingSupported = function() {
233
233
  "use strict";
234
234
  return !qq.android() && //android's impl of Blob.slice is broken
235
235
  qq.isXhrUploadSupported() &&
236
- (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
236
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
237
237
  };
238
238
 
239
239
  qq.extend = function (first, second, extendNested) {
@@ -581,12 +581,90 @@ qq.DisposeSupport = function() {
581
581
  }
582
582
  };
583
583
  };
584
+ qq.supportedFeatures = (function() {
585
+ var supportsUploading,
586
+ supportsAjaxFileUploading,
587
+ supportsFolderDrop,
588
+ supportsChunking,
589
+ supportsResume,
590
+ supportsUploadViaPaste,
591
+ supportsUploadCors,
592
+ supportsDeleteFileCors;
593
+
594
+
595
+ function testSupportsFileInputElement() {
596
+ var supported = true,
597
+ tempInput;
598
+
599
+ try {
600
+ tempInput = document.createElement('input');
601
+ tempInput.type = 'file';
602
+ qq(tempInput).hide();
603
+
604
+ if(tempInput.disabled) {
605
+ supported = false;
606
+ }
607
+ }
608
+ catch(ex) {
609
+ supported = false;
610
+ }
611
+
612
+ return supported;
613
+ }
614
+
615
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
616
+ function isChrome21OrHigher() {
617
+ return qq.chrome() &&
618
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
619
+ }
620
+
621
+ //only way to test for complete Clipboard API support at this time
622
+ function isChrome14OrHigher() {
623
+ return qq.chrome() &&
624
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
625
+ }
626
+
627
+
628
+ supportsUploading = testSupportsFileInputElement();
629
+
630
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
631
+
632
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
633
+
634
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
635
+
636
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
637
+
638
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
639
+
640
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
641
+
642
+ supportsDeleteFileCors = supportsAjaxFileUploading;
643
+
644
+
645
+ return {
646
+ uploading: supportsUploading,
647
+ ajaxUploading: supportsAjaxFileUploading,
648
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
649
+ folderDrop: supportsFolderDrop,
650
+ chunking: supportsChunking,
651
+ resume: supportsResume,
652
+ uploadCustomHeaders: supportsAjaxFileUploading,
653
+ uploadNonMultipart: supportsAjaxFileUploading,
654
+ itemSizeValidation: supportsAjaxFileUploading,
655
+ uploadViaPaste: supportsUploadViaPaste,
656
+ progressBar: supportsAjaxFileUploading,
657
+ uploadCors: supportsUploadCors,
658
+ deleteFileCors: supportsDeleteFileCors
659
+ }
660
+
661
+ }());
584
662
  /*globals qq*/
585
663
  qq.Promise = function() {
586
664
  "use strict";
587
665
 
588
666
  var successValue, failureValue,
589
- successCallback, failureCallback,
667
+ successCallback, failureCallback, doneCallback,
590
668
  state = 0;
591
669
 
592
670
  return {
@@ -601,6 +679,19 @@ qq.Promise = function() {
601
679
  else if (onSuccess) {
602
680
  onSuccess(successValue);
603
681
  }
682
+
683
+ return this;
684
+ },
685
+
686
+ done: function(callback) {
687
+ if (state === 0) {
688
+ doneCallback = callback;
689
+ }
690
+ else {
691
+ callback();
692
+ }
693
+
694
+ return this;
604
695
  },
605
696
 
606
697
  success: function(val) {
@@ -611,6 +702,10 @@ qq.Promise = function() {
611
702
  successCallback(val);
612
703
  }
613
704
 
705
+ if(doneCallback) {
706
+ doneCallback();
707
+ }
708
+
614
709
  return this;
615
710
  },
616
711
 
@@ -622,6 +717,10 @@ qq.Promise = function() {
622
717
  failureCallback(val);
623
718
  }
624
719
 
720
+ if(doneCallback) {
721
+ doneCallback();
722
+ }
723
+
625
724
  return this;
626
725
  }
627
726
  };
@@ -810,7 +909,7 @@ qq.FineUploaderBasic = function(o){
810
909
  callbacks: {
811
910
  onSubmit: function(id, name){},
812
911
  onSubmitted: function(id, name){},
813
- onComplete: function(id, name, responseJSON){},
912
+ onComplete: function(id, name, responseJSON, maybeXhr){},
814
913
  onCancel: function(id, name){},
815
914
  onUpload: function(id, name){},
816
915
  onUploadChunk: function(id, name, chunkData){},
@@ -824,9 +923,7 @@ qq.FineUploaderBasic = function(o){
824
923
  onSubmitDelete: function(id) {},
825
924
  onDelete: function(id){},
826
925
  onDeleteComplete: function(id, xhr, isError){},
827
- onPasteReceived: function(blob) {
828
- return new qq.Promise().success();
829
- }
926
+ onPasteReceived: function(blob) {}
830
927
  },
831
928
  messages: {
832
929
  typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
@@ -875,6 +972,7 @@ qq.FineUploaderBasic = function(o){
875
972
  return fileOrBlobName;
876
973
  },
877
974
  text: {
975
+ defaultResponseError: "Upload failure reason unknown",
878
976
  sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
879
977
  },
880
978
  deleteFile : {
@@ -908,7 +1006,9 @@ qq.FineUploaderBasic = function(o){
908
1006
  this._autoRetries = [];
909
1007
  this._retryTimeouts = [];
910
1008
  this._preventRetries = [];
911
- this._netFilesUploadedOrQueued = 0;
1009
+
1010
+ this._netUploadedOrQueued = 0;
1011
+ this._netUploaded = 0;
912
1012
 
913
1013
  this._paramsStore = this._createParamsStore("request");
914
1014
  this._deleteFileParamsStore = this._createParamsStore("deleteFile");
@@ -967,9 +1067,12 @@ qq.FineUploaderBasic.prototype = {
967
1067
  this._endpointStore.setEndpoint(endpoint, id);
968
1068
  }
969
1069
  },
970
- getInProgress: function(){
1070
+ getInProgress: function() {
971
1071
  return this._filesInProgress.length;
972
1072
  },
1073
+ getNetUploads: function() {
1074
+ return this._netUploaded;
1075
+ },
973
1076
  uploadStoredFiles: function(){
974
1077
  "use strict";
975
1078
  var idToUpload;
@@ -985,7 +1088,7 @@ qq.FineUploaderBasic.prototype = {
985
1088
  },
986
1089
  retry: function(id) {
987
1090
  if (this._onBeforeManualRetry(id)) {
988
- this._netFilesUploadedOrQueued++;
1091
+ this._netUploadedOrQueued++;
989
1092
  this._handler.retry(id);
990
1093
  return true;
991
1094
  }
@@ -1018,24 +1121,25 @@ qq.FineUploaderBasic.prototype = {
1018
1121
  this._button.reset();
1019
1122
  this._paramsStore.reset();
1020
1123
  this._endpointStore.reset();
1021
- this._netFilesUploadedOrQueued = 0;
1124
+ this._netUploadedOrQueued = 0;
1125
+ this._netUploaded = 0;
1022
1126
 
1023
1127
  if (this._pasteHandler) {
1024
1128
  this._pasteHandler.reset();
1025
1129
  }
1026
1130
  },
1027
- addFiles: function(filesBlobDataOrInputs) {
1131
+ addFiles: function(filesDataOrInputs, params, endpoint) {
1028
1132
  var self = this,
1029
1133
  verifiedFilesOrInputs = [],
1030
1134
  index, fileOrInput;
1031
1135
 
1032
- if (filesBlobDataOrInputs) {
1033
- if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
1034
- filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
1136
+ if (filesDataOrInputs) {
1137
+ if (!window.FileList || !(filesDataOrInputs instanceof FileList)) {
1138
+ filesDataOrInputs = [].concat(filesDataOrInputs);
1035
1139
  }
1036
1140
 
1037
- for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
1038
- fileOrInput = filesBlobDataOrInputs[index];
1141
+ for (index = 0; index < filesDataOrInputs.length; index+=1) {
1142
+ fileOrInput = filesDataOrInputs[index];
1039
1143
 
1040
1144
  if (qq.isFileOrInput(fileOrInput)) {
1041
1145
  verifiedFilesOrInputs.push(fileOrInput);
@@ -1046,10 +1150,10 @@ qq.FineUploaderBasic.prototype = {
1046
1150
  }
1047
1151
 
1048
1152
  this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
1049
- this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
1153
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs, params, endpoint);
1050
1154
  }
1051
1155
  },
1052
- addBlobs: function(blobDataOrArray) {
1156
+ addBlobs: function(blobDataOrArray, params, endpoint) {
1053
1157
  if (blobDataOrArray) {
1054
1158
  var blobDataArray = [].concat(blobDataOrArray),
1055
1159
  verifiedBlobDataList = [],
@@ -1070,7 +1174,7 @@ qq.FineUploaderBasic.prototype = {
1070
1174
  }
1071
1175
  });
1072
1176
 
1073
- this._uploadFileOrBlobDataList(verifiedBlobDataList);
1177
+ this._uploadFileOrBlobDataList(verifiedBlobDataList, params, endpoint);
1074
1178
  }
1075
1179
  else {
1076
1180
  this.log("undefined or non-array parameter passed into addBlobs", "error");
@@ -1103,15 +1207,12 @@ qq.FineUploaderBasic.prototype = {
1103
1207
  this._deleteFileEndpointStore.setEndpoint(endpoint, id);
1104
1208
  }
1105
1209
  },
1106
- getPromissoryCallbackNames: function() {
1107
- return ["onPasteReceived"];
1108
- },
1109
1210
  _createUploadButton: function(element){
1110
1211
  var self = this;
1111
1212
 
1112
1213
  var button = new qq.UploadButton({
1113
1214
  element: element,
1114
- multiple: this._options.multiple && qq.isXhrUploadSupported(),
1215
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
1115
1216
  acceptFiles: this._options.validation.acceptFiles,
1116
1217
  onChange: function(input){
1117
1218
  self._onInputChange(input);
@@ -1151,7 +1252,7 @@ qq.FineUploaderBasic.prototype = {
1151
1252
  },
1152
1253
  onComplete: function(id, name, result, xhr){
1153
1254
  self._onComplete(id, name, result, xhr);
1154
- self._options.callbacks.onComplete(id, name, result);
1255
+ self._options.callbacks.onComplete(id, name, result, xhr);
1155
1256
  },
1156
1257
  onCancel: function(id, name){
1157
1258
  self._onCancel(id, name);
@@ -1221,10 +1322,10 @@ qq.FineUploaderBasic.prototype = {
1221
1322
  self.log(str, level);
1222
1323
  },
1223
1324
  pasteReceived: function(blob) {
1224
- var pasteReceivedCallback = self._options.callbacks.onPasteReceived,
1225
- promise = pasteReceivedCallback(blob);
1325
+ var callback = self._options.callbacks.onPasteReceived,
1326
+ promise = callback(blob);
1226
1327
 
1227
- if (promise.then) {
1328
+ if (promise && promise.then) {
1228
1329
  promise.then(function(successData) {
1229
1330
  self._handlePasteSuccess(blob, successData);
1230
1331
  }, function(failureData) {
@@ -1232,7 +1333,7 @@ qq.FineUploaderBasic.prototype = {
1232
1333
  });
1233
1334
  }
1234
1335
  else {
1235
- self.log("Promise contract not fulfilled in pasteReceived callback handler! Ignoring pasted item.", "error");
1336
+ self._handlePasteSuccess(blob);
1236
1337
  }
1237
1338
  }
1238
1339
  }
@@ -1268,7 +1369,7 @@ qq.FineUploaderBasic.prototype = {
1268
1369
  });
1269
1370
  },
1270
1371
  _onSubmit: function(id, name) {
1271
- this._netFilesUploadedOrQueued++;
1372
+ this._netUploadedOrQueued++;
1272
1373
 
1273
1374
  if (this._options.autoUpload) {
1274
1375
  this._filesInProgress.push(id);
@@ -1278,14 +1379,17 @@ qq.FineUploaderBasic.prototype = {
1278
1379
  },
1279
1380
  _onComplete: function(id, name, result, xhr) {
1280
1381
  if (!result.success) {
1281
- this._netFilesUploadedOrQueued--;
1382
+ this._netUploadedOrQueued--;
1383
+ }
1384
+ else {
1385
+ this._netUploaded++;
1282
1386
  }
1283
1387
 
1284
1388
  this._removeFromFilesInProgress(id);
1285
1389
  this._maybeParseAndSendUploadError(id, name, result, xhr);
1286
1390
  },
1287
1391
  _onCancel: function(id, name){
1288
- this._netFilesUploadedOrQueued--;
1392
+ this._netUploadedOrQueued--;
1289
1393
 
1290
1394
  this._removeFromFilesInProgress(id);
1291
1395
 
@@ -1298,10 +1402,7 @@ qq.FineUploaderBasic.prototype = {
1298
1402
  },
1299
1403
  _isDeletePossible: function() {
1300
1404
  return (this._options.deleteFile.enabled &&
1301
- (!this._options.cors.expected ||
1302
- (this._options.cors.expected && (qq.ie10() || !qq.ie()))
1303
- )
1304
- );
1405
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
1305
1406
  },
1306
1407
  _onSubmitDelete: function(id) {
1307
1408
  if (this._isDeletePossible()) {
@@ -1324,7 +1425,8 @@ qq.FineUploaderBasic.prototype = {
1324
1425
  this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
1325
1426
  }
1326
1427
  else {
1327
- this._netFilesUploadedOrQueued--;
1428
+ this._netUploadedOrQueued--;
1429
+ this._netUploaded--;
1328
1430
  this.log("Delete request for '" + name + "' has succeeded.");
1329
1431
  }
1330
1432
  },
@@ -1336,7 +1438,7 @@ qq.FineUploaderBasic.prototype = {
1336
1438
  },
1337
1439
  _onUpload: function(id, name){},
1338
1440
  _onInputChange: function(input){
1339
- if (qq.isXhrUploadSupported()){
1441
+ if (qq.supportedFeatures.ajaxUploading){
1340
1442
  this.addFiles(input.files);
1341
1443
  } else {
1342
1444
  this.addFiles(input);
@@ -1377,7 +1479,7 @@ qq.FineUploaderBasic.prototype = {
1377
1479
  return false;
1378
1480
  }
1379
1481
 
1380
- if (itemLimit > 0 && this._netFilesUploadedOrQueued+1 > itemLimit) {
1482
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
1381
1483
  this._itemError("retryFailTooManyItems", "");
1382
1484
  return false;
1383
1485
  }
@@ -1398,12 +1500,12 @@ qq.FineUploaderBasic.prototype = {
1398
1500
  this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
1399
1501
  }
1400
1502
  else {
1401
- var errorReason = response.error ? response.error : "Upload failure reason unknown";
1503
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
1402
1504
  this._options.callbacks.onError(id, name, errorReason, xhr);
1403
1505
  }
1404
1506
  }
1405
1507
  },
1406
- _uploadFileOrBlobDataList: function(fileOrBlobDataList){
1508
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList, params, endpoint) {
1407
1509
  var index,
1408
1510
  validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
1409
1511
  batchValid = this._isBatchValid(validationDescriptors);
@@ -1412,7 +1514,7 @@ qq.FineUploaderBasic.prototype = {
1412
1514
  if (fileOrBlobDataList.length > 0) {
1413
1515
  for (index = 0; index < fileOrBlobDataList.length; index++){
1414
1516
  if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
1415
- this._upload(fileOrBlobDataList[index]);
1517
+ this._upload(fileOrBlobDataList[index], params, endpoint);
1416
1518
  } else {
1417
1519
  if (this._options.validation.stopOnFirstInvalidFile){
1418
1520
  return;
@@ -1425,10 +1527,18 @@ qq.FineUploaderBasic.prototype = {
1425
1527
  }
1426
1528
  }
1427
1529
  },
1428
- _upload: function(blobOrFileContainer){
1530
+ _upload: function(blobOrFileContainer, params, endpoint) {
1429
1531
  var id = this._handler.add(blobOrFileContainer);
1430
1532
  var name = this._handler.getName(id);
1431
1533
 
1534
+ if (params) {
1535
+ this.setParams(params, id);
1536
+ }
1537
+
1538
+ if (endpoint) {
1539
+ this.setEndpoint(endpoint, id);
1540
+ }
1541
+
1432
1542
  if (this._options.callbacks.onSubmit(id, name) !== false) {
1433
1543
  this._onSubmit(id, name);
1434
1544
  this._options.callbacks.onSubmitted(id, name);
@@ -1448,7 +1558,7 @@ qq.FineUploaderBasic.prototype = {
1448
1558
  //first, defer the check to the callback (ask the integrator)
1449
1559
  var errorMessage,
1450
1560
  itemLimit = this._options.validation.itemLimit,
1451
- proposedNetFilesUploadedOrQueued = this._netFilesUploadedOrQueued + validationDescriptors.length,
1561
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length,
1452
1562
  batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
1453
1563
 
1454
1564
  //if the callback hasn't rejected the batch, run some internal tests on the batch next
@@ -1500,10 +1610,12 @@ qq.FineUploaderBasic.prototype = {
1500
1610
 
1501
1611
  return true;
1502
1612
  },
1503
- _itemError: function(code, name) {
1613
+ _itemError: function(code, nameOrNames) {
1504
1614
  var message = this._options.messages[code],
1505
1615
  allowedExtensions = [],
1506
- extensionsForMessage;
1616
+ names = [].concat(nameOrNames),
1617
+ name = names[0],
1618
+ extensionsForMessage, placeholderMatch;
1507
1619
 
1508
1620
  function r(name, replacement){ message = message.replace(name, replacement); }
1509
1621
 
@@ -1524,6 +1636,13 @@ qq.FineUploaderBasic.prototype = {
1524
1636
  r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
1525
1637
  r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
1526
1638
 
1639
+ placeholderMatch = message.match(/(\{\w+\})/g);
1640
+ if (placeholderMatch !== null) {
1641
+ qq.each(placeholderMatch, function(idx, placeholder) {
1642
+ r(placeholder, names[idx]);
1643
+ });
1644
+ }
1645
+
1527
1646
  this._options.callbacks.onError(null, name, message);
1528
1647
 
1529
1648
  return message;
@@ -1633,7 +1752,7 @@ qq.FineUploaderBasic.prototype = {
1633
1752
  size = this._parseFileOrBlobDataSize(fileOrBlobData);
1634
1753
 
1635
1754
  fileDescriptor.name = name;
1636
- if (size) {
1755
+ if (size !== undefined) {
1637
1756
  fileDescriptor.size = size;
1638
1757
  }
1639
1758
 
@@ -1715,85 +1834,87 @@ qq.FineUploaderBasic.prototype = {
1715
1834
  qq.DragAndDrop = function(o) {
1716
1835
  "use strict";
1717
1836
 
1718
- var options, dz, dirPending,
1837
+ var options, dz,
1719
1838
  droppedFiles = [],
1720
- droppedEntriesCount = 0,
1721
- droppedEntriesParsedCount = 0,
1722
1839
  disposeSupport = new qq.DisposeSupport();
1723
1840
 
1724
1841
  options = {
1725
- dropArea: null,
1726
- extraDropzones: [],
1727
- hideDropzones: true,
1728
- multiple: true,
1842
+ dropZoneElements: [],
1843
+ hideDropZonesBeforeEnter: false,
1844
+ allowMultipleItems: true,
1729
1845
  classes: {
1730
1846
  dropActive: null
1731
1847
  },
1732
- callbacks: {
1733
- dropProcessing: function(isProcessing, files) {},
1734
- error: function(code, filename) {},
1735
- log: function(message, level) {}
1736
- }
1848
+ callbacks: new qq.DragAndDrop.callbacks()
1737
1849
  };
1738
1850
 
1739
- qq.extend(options, o);
1851
+ qq.extend(options, o, true);
1740
1852
 
1741
- function maybeUploadDroppedFiles() {
1742
- if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
1743
- options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
1744
- dz.dropDisabled(false);
1745
- options.callbacks.dropProcessing(false, droppedFiles);
1746
- }
1747
- }
1748
- function addDroppedFile(file) {
1749
- droppedFiles.push(file);
1750
- droppedEntriesParsedCount+=1;
1751
- maybeUploadDroppedFiles();
1853
+ setupDragDrop();
1854
+
1855
+ function uploadDroppedFiles(files) {
1856
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
1857
+ dz.dropDisabled(false);
1858
+ options.callbacks.processingDroppedFilesComplete(files);
1752
1859
  }
1753
1860
 
1754
1861
  function traverseFileTree(entry) {
1755
- var dirReader, i;
1756
-
1757
- droppedEntriesCount+=1;
1862
+ var dirReader, i,
1863
+ parseEntryPromise = new qq.Promise();
1758
1864
 
1759
1865
  if (entry.isFile) {
1760
1866
  entry.file(function(file) {
1761
- addDroppedFile(file);
1867
+ droppedFiles.push(file);
1868
+ parseEntryPromise.success();
1869
+ },
1870
+ function(fileError) {
1871
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
1872
+ parseEntryPromise.failure();
1762
1873
  });
1763
1874
  }
1764
1875
  else if (entry.isDirectory) {
1765
- dirPending = true;
1766
1876
  dirReader = entry.createReader();
1767
1877
  dirReader.readEntries(function(entries) {
1768
- droppedEntriesParsedCount+=1;
1878
+ var entriesLeft = entries.length;
1879
+
1769
1880
  for (i = 0; i < entries.length; i+=1) {
1770
- traverseFileTree(entries[i]);
1771
- }
1881
+ traverseFileTree(entries[i]).done(function() {
1882
+ entriesLeft-=1;
1772
1883
 
1773
- dirPending = false;
1884
+ if (entriesLeft === 0) {
1885
+ parseEntryPromise.success();
1886
+ }
1887
+ });
1888
+ }
1774
1889
 
1775
1890
  if (!entries.length) {
1776
- maybeUploadDroppedFiles();
1891
+ parseEntryPromise.success();
1777
1892
  }
1893
+ }, function(fileError) {
1894
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
1895
+ parseEntryPromise.failure();
1778
1896
  });
1779
1897
  }
1898
+
1899
+ return parseEntryPromise;
1780
1900
  }
1781
1901
 
1782
1902
  function handleDataTransfer(dataTransfer) {
1783
- var i, items, entry;
1903
+ var i, items, entry,
1904
+ pendingFolderPromises = [],
1905
+ handleDataTransferPromise = new qq.Promise();
1784
1906
 
1785
- options.callbacks.dropProcessing(true);
1907
+ options.callbacks.processingDroppedFiles();
1786
1908
  dz.dropDisabled(true);
1787
1909
 
1788
- if (dataTransfer.files.length > 1 && !options.multiple) {
1789
- options.callbacks.dropProcessing(false);
1790
- options.callbacks.error('tooManyFilesError', "");
1910
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
1911
+ options.callbacks.processingDroppedFilesComplete([]);
1912
+ options.callbacks.dropError('tooManyFilesError', "");
1791
1913
  dz.dropDisabled(false);
1914
+ handleDataTransferPromise.failure();
1792
1915
  }
1793
1916
  else {
1794
1917
  droppedFiles = [];
1795
- droppedEntriesCount = 0;
1796
- droppedEntriesParsedCount = 0;
1797
1918
 
1798
1919
  if (qq.isFolderDropSupported(dataTransfer)) {
1799
1920
  items = dataTransfer.items;
@@ -1804,22 +1925,29 @@ qq.DragAndDrop = function(o) {
1804
1925
  //due to a bug in Chrome's File System API impl - #149735
1805
1926
  if (entry.isFile) {
1806
1927
  droppedFiles.push(items[i].getAsFile());
1807
- if (i === items.length-1) {
1808
- maybeUploadDroppedFiles();
1809
- }
1810
1928
  }
1811
1929
 
1812
1930
  else {
1813
- traverseFileTree(entry);
1931
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
1932
+ pendingFolderPromises.pop();
1933
+ if (pendingFolderPromises.length === 0) {
1934
+ handleDataTransferPromise.success();
1935
+ }
1936
+ }));
1814
1937
  }
1815
1938
  }
1816
1939
  }
1817
1940
  }
1818
1941
  else {
1819
- options.callbacks.dropProcessing(false, dataTransfer.files);
1820
- dz.dropDisabled(false);
1942
+ droppedFiles = dataTransfer.files;
1943
+ }
1944
+
1945
+ if (pendingFolderPromises.length === 0) {
1946
+ handleDataTransferPromise.success();
1821
1947
  }
1822
1948
  }
1949
+
1950
+ return handleDataTransferPromise;
1823
1951
  }
1824
1952
 
1825
1953
  function setupDropzone(dropArea){
@@ -1833,12 +1961,14 @@ qq.DragAndDrop = function(o) {
1833
1961
  qq(dropArea).removeClass(options.classes.dropActive);
1834
1962
  },
1835
1963
  onDrop: function(e){
1836
- if (options.hideDropzones) {
1964
+ if (options.hideDropZonesBeforeEnter) {
1837
1965
  qq(dropArea).hide();
1838
1966
  }
1839
1967
  qq(dropArea).removeClass(options.classes.dropActive);
1840
1968
 
1841
- handleDataTransfer(e.dataTransfer);
1969
+ handleDataTransfer(e.dataTransfer).done(function() {
1970
+ uploadDroppedFiles(droppedFiles);
1971
+ });
1842
1972
  }
1843
1973
  });
1844
1974
 
@@ -1846,7 +1976,7 @@ qq.DragAndDrop = function(o) {
1846
1976
  dz.dispose();
1847
1977
  });
1848
1978
 
1849
- if (options.hideDropzones) {
1979
+ if (options.hideDropZonesBeforeEnter) {
1850
1980
  qq(dropArea).hide();
1851
1981
  }
1852
1982
  }
@@ -1865,60 +1995,49 @@ qq.DragAndDrop = function(o) {
1865
1995
  }
1866
1996
 
1867
1997
  function setupDragDrop(){
1868
- if (options.dropArea) {
1869
- options.extraDropzones.push(options.dropArea);
1870
- }
1871
-
1872
- var i, dropzones = options.extraDropzones;
1998
+ var dropZones = options.dropZoneElements;
1873
1999
 
1874
- for (i=0; i < dropzones.length; i+=1){
1875
- setupDropzone(dropzones[i]);
1876
- }
2000
+ qq.each(dropZones, function(idx, dropZone) {
2001
+ setupDropzone(dropZone);
2002
+ })
1877
2003
 
1878
2004
  // IE <= 9 does not support the File API used for drag+drop uploads
1879
- if (options.dropArea && (!qq.ie() || qq.ie10())) {
2005
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
1880
2006
  disposeSupport.attach(document, 'dragenter', function(e) {
1881
2007
  if (!dz.dropDisabled() && isFileDrag(e)) {
1882
- if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
1883
- return;
1884
- }
1885
-
1886
- options.dropArea.style.display = 'block';
1887
- for (i=0; i < dropzones.length; i+=1) {
1888
- dropzones[i].style.display = 'block';
1889
- }
2008
+ qq.each(dropZones, function(idx, dropZone) {
2009
+ qq(dropZone).css({display: 'block'});
2010
+ });
1890
2011
  }
1891
2012
  });
1892
2013
  }
1893
2014
  disposeSupport.attach(document, 'dragleave', function(e){
1894
- if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
1895
- for (i=0; i < dropzones.length; i+=1) {
1896
- qq(dropzones[i]).hide();
1897
- }
2015
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
2016
+ qq.each(dropZones, function(idx, dropZone) {
2017
+ qq(dropZone).hide();
2018
+ });
1898
2019
  }
1899
2020
  });
1900
2021
  disposeSupport.attach(document, 'drop', function(e){
1901
- if (options.hideDropzones) {
1902
- for (i=0; i < dropzones.length; i+=1) {
1903
- qq(dropzones[i]).hide();
1904
- }
2022
+ if (options.hideDropZonesBeforeEnter) {
2023
+ qq.each(dropZones, function(idx, dropZone) {
2024
+ qq(dropZone).hide();
2025
+ });
1905
2026
  }
1906
2027
  e.preventDefault();
1907
2028
  });
1908
2029
  }
1909
2030
 
1910
2031
  return {
1911
- setup: function() {
1912
- setupDragDrop();
1913
- },
1914
-
1915
2032
  setupExtraDropzone: function(element) {
1916
- options.extraDropzones.push(element);
2033
+ options.dropZoneElements.push(element);
1917
2034
  setupDropzone(element);
1918
2035
  },
1919
2036
 
1920
- removeExtraDropzone: function(element) {
1921
- var i, dzs = options.extraDropzones;
2037
+ removeDropzone: function(element) {
2038
+ var i,
2039
+ dzs = options.dropZoneElements;
2040
+
1922
2041
  for(i in dzs) {
1923
2042
  if (dzs[i] === element) {
1924
2043
  return dzs.splice(i, 1);
@@ -1933,6 +2052,18 @@ qq.DragAndDrop = function(o) {
1933
2052
  };
1934
2053
  };
1935
2054
 
2055
+ qq.DragAndDrop.callbacks = function() {
2056
+ return {
2057
+ processingDroppedFiles: function() {},
2058
+ processingDroppedFilesComplete: function(files) {},
2059
+ dropError: function(code, errorSpecifics) {
2060
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
2061
+ },
2062
+ dropLog: function(message, level) {
2063
+ qq.log(message, level);
2064
+ }
2065
+ }
2066
+ }
1936
2067
 
1937
2068
  qq.UploadDropZone = function(o){
1938
2069
  "use strict";
@@ -2119,7 +2250,6 @@ qq.FineUploader = function(o){
2119
2250
  button: 'qq-upload-button',
2120
2251
  drop: 'qq-upload-drop-area',
2121
2252
  dropActive: 'qq-upload-drop-area-active',
2122
- dropDisabled: 'qq-upload-drop-area-disabled',
2123
2253
  list: 'qq-upload-list',
2124
2254
  progressBar: 'qq-progress-bar',
2125
2255
  file: 'qq-upload-file',
@@ -2149,7 +2279,8 @@ qq.FineUploader = function(o){
2149
2279
  enableTooltip: true
2150
2280
  },
2151
2281
  messages: {
2152
- tooManyFilesError: "You may only drop one file"
2282
+ tooManyFilesError: "You may only drop one file",
2283
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
2153
2284
  },
2154
2285
  retry: {
2155
2286
  showAutoRetryNote: true,
@@ -2204,34 +2335,40 @@ qq.FineUploader = function(o){
2204
2335
 
2205
2336
  // overwrite options with user supplied
2206
2337
  qq.extend(this._options, o, true);
2207
- this._wrapCallbacks();
2208
-
2209
- // overwrite the upload button text if any
2210
- // same for the Cancel button and Fail message text
2211
- this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
2212
- this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
2213
- this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
2214
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
2215
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
2216
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
2217
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
2218
2338
 
2219
- this._element = this._options.element;
2220
- this._element.innerHTML = this._options.template;
2221
- this._listElement = this._options.listElement || this._find(this._element, 'list');
2339
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
2340
+ this._options.element.innerHTML = "<div>" + this._options.messages.unsupportedBrowser + "</div>"
2341
+ }
2342
+ else {
2343
+ this._wrapCallbacks();
2344
+
2345
+ // overwrite the upload button text if any
2346
+ // same for the Cancel button and Fail message text
2347
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
2348
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
2349
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
2350
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
2351
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
2352
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
2353
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
2354
+
2355
+ this._element = this._options.element;
2356
+ this._element.innerHTML = this._options.template;
2357
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
2222
2358
 
2223
- this._classes = this._options.classes;
2359
+ this._classes = this._options.classes;
2224
2360
 
2225
- if (!this._button) {
2226
- this._button = this._createUploadButton(this._find(this._element, 'button'));
2227
- }
2361
+ if (!this._button) {
2362
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
2363
+ }
2228
2364
 
2229
- this._bindCancelAndRetryEvents();
2365
+ this._bindCancelAndRetryEvents();
2230
2366
 
2231
- this._dnd = this._setupDragAndDrop();
2367
+ this._dnd = this._setupDragAndDrop();
2232
2368
 
2233
- if (this._options.paste.targetElement && this._options.paste.promptForName) {
2234
- this._setupPastePrompt();
2369
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
2370
+ this._setupPastePrompt();
2371
+ }
2235
2372
  }
2236
2373
  };
2237
2374
 
@@ -2247,7 +2384,7 @@ qq.extend(qq.FineUploader.prototype, {
2247
2384
  this._dnd.setupExtraDropzone(element);
2248
2385
  },
2249
2386
  removeExtraDropzone: function(element){
2250
- return this._dnd.removeExtraDropzone(element);
2387
+ return this._dnd.removeDropzone(element);
2251
2388
  },
2252
2389
  getItemByFileId: function(id){
2253
2390
  var item = this._listElement.firstChild;
@@ -2277,53 +2414,49 @@ qq.extend(qq.FineUploader.prototype, {
2277
2414
  _setupDragAndDrop: function() {
2278
2415
  var self = this,
2279
2416
  dropProcessingEl = this._find(this._element, 'dropProcessing'),
2280
- dnd, preventSelectFiles, defaultDropAreaEl;
2417
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
2418
+ preventSelectFiles;
2281
2419
 
2282
2420
  preventSelectFiles = function(event) {
2283
2421
  event.preventDefault();
2284
2422
  };
2285
2423
 
2286
2424
  if (!this._options.dragAndDrop.disableDefaultDropzone) {
2287
- defaultDropAreaEl = this._find(this._options.element, 'drop');
2425
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
2288
2426
  }
2289
2427
 
2290
- dnd = new qq.DragAndDrop({
2291
- dropArea: defaultDropAreaEl,
2292
- extraDropzones: this._options.dragAndDrop.extraDropzones,
2293
- hideDropzones: this._options.dragAndDrop.hideDropzones,
2294
- multiple: this._options.multiple,
2428
+ return new qq.DragAndDrop({
2429
+ dropZoneElements: dropZoneElements,
2430
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
2431
+ allowMultipleItems: this._options.multiple,
2295
2432
  classes: {
2296
2433
  dropActive: this._options.classes.dropActive
2297
2434
  },
2298
2435
  callbacks: {
2299
- dropProcessing: function(isProcessing, files) {
2436
+ processingDroppedFiles: function() {
2300
2437
  var input = self._button.getInput();
2301
2438
 
2302
- if (isProcessing) {
2303
- qq(dropProcessingEl).css({display: 'block'});
2304
- qq(input).attach('click', preventSelectFiles);
2305
- }
2306
- else {
2307
- qq(dropProcessingEl).hide();
2308
- qq(input).detach('click', preventSelectFiles);
2309
- }
2439
+ qq(dropProcessingEl).css({display: 'block'});
2440
+ qq(input).attach('click', preventSelectFiles);
2441
+ },
2442
+ processingDroppedFilesComplete: function(files) {
2443
+ var input = self._button.getInput();
2444
+
2445
+ qq(dropProcessingEl).hide();
2446
+ qq(input).detach('click', preventSelectFiles);
2310
2447
 
2311
2448
  if (files) {
2312
2449
  self.addFiles(files);
2313
2450
  }
2314
2451
  },
2315
- error: function(code, filename) {
2316
- self._itemError(code, filename);
2452
+ dropError: function(code, errorData) {
2453
+ self._itemError(code, errorData);
2317
2454
  },
2318
- log: function(message, level) {
2455
+ dropLog: function(message, level) {
2319
2456
  self.log(message, level);
2320
2457
  }
2321
2458
  }
2322
2459
  });
2323
-
2324
- dnd.setup();
2325
-
2326
- return dnd;
2327
2460
  },
2328
2461
  _leaving_document_out: function(e){
2329
2462
  return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
@@ -2389,7 +2522,7 @@ qq.extend(qq.FineUploader.prototype, {
2389
2522
  qq(item).removeClass(this._classes.retrying);
2390
2523
  qq(this._find(item, 'progressBar')).hide();
2391
2524
 
2392
- if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
2525
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
2393
2526
  qq(this._find(item, 'cancel')).hide();
2394
2527
  }
2395
2528
  qq(this._find(item, 'spinner')).hide();
@@ -2523,7 +2656,7 @@ qq.extend(qq.FineUploader.prototype, {
2523
2656
  },
2524
2657
  _addToList: function(id, name){
2525
2658
  var item = qq.toElement(this._options.fileTemplate);
2526
- if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
2659
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
2527
2660
  var cancelLink = this._find(item, 'cancel');
2528
2661
  qq(cancelLink).remove();
2529
2662
  }
@@ -2540,7 +2673,7 @@ qq.extend(qq.FineUploader.prototype, {
2540
2673
 
2541
2674
  this._listElement.appendChild(item);
2542
2675
 
2543
- if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
2676
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
2544
2677
  this._displayFileSize(id);
2545
2678
  }
2546
2679
  },
@@ -2577,7 +2710,7 @@ qq.extend(qq.FineUploader.prototype, {
2577
2710
 
2578
2711
  var item = target.parentNode;
2579
2712
  while(item.qqFileId === undefined) {
2580
- item = target = target.parentNode;
2713
+ item = item.parentNode;
2581
2714
  }
2582
2715
 
2583
2716
  if (qq(target).hasClass(self._classes.deleteButton)) {
@@ -2643,7 +2776,7 @@ qq.extend(qq.FineUploader.prototype, {
2643
2776
  spinnerEl.style.display = "inline-block";
2644
2777
  },
2645
2778
  _showCancelLink: function(item) {
2646
- if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
2779
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
2647
2780
  var cancelLink = this._find(item, 'cancel');
2648
2781
 
2649
2782
  qq(cancelLink).css({display: 'inline'});
@@ -3014,7 +3147,7 @@ qq.UploadHandler = function(o) {
3014
3147
  }
3015
3148
  };
3016
3149
 
3017
- if (qq.isXhrUploadSupported()) {
3150
+ if (qq.supportedFeatures.ajaxUploading) {
3018
3151
  handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
3019
3152
  }
3020
3153
  else {
@@ -3419,8 +3552,8 @@ qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
3419
3552
  log = logCallback,
3420
3553
  fileState = [],
3421
3554
  cookieItemDelimiter = "|",
3422
- chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
3423
- resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
3555
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
3556
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
3424
3557
  resumeId = getResumeId(),
3425
3558
  multipart = options.forceMultipart || options.paramsInBody,
3426
3559
  api;
@@ -3915,7 +4048,7 @@ qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
3915
4048
  if (fileOrBlobData instanceof File) {
3916
4049
  id = fileState.push({file: fileOrBlobData}) - 1;
3917
4050
  }
3918
- else if (fileOrBlobData.blob instanceof Blob) {
4051
+ else if (qq.isBlob(fileOrBlobData.blob)) {
3919
4052
  id = fileState.push({blobData: fileOrBlobData}) - 1;
3920
4053
  }
3921
4054
  else {
@@ -4106,16 +4239,9 @@ qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
4106
4239
  $callbackEl = $el;
4107
4240
 
4108
4241
  callbacks[prop] = function() {
4109
- var origFunc = func,
4110
- args = Array.prototype.slice.call(arguments),
4111
- jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
4242
+ var args = Array.prototype.slice.call(arguments);
4112
4243
 
4113
- if (jqueryHandlerResult === undefined &&
4114
- $.inArray(prop, uploaderInst.getPromissoryCallbackNames()) >= 0) {
4115
- return origFunc();
4116
- }
4117
-
4118
- return jqueryHandlerResult;
4244
+ return $callbackEl.triggerHandler(name, args);
4119
4245
  };
4120
4246
  });
4121
4247
  };
@@ -4217,3 +4343,148 @@ qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
4217
4343
  };
4218
4344
 
4219
4345
  }(jQuery));
4346
+ /*globals jQuery, qq*/
4347
+ (function($) {
4348
+ "use strict";
4349
+ var rootDataKey = "fineUploaderDnd",
4350
+ $el;
4351
+
4352
+ function init (options) {
4353
+ if (!options) {
4354
+ options = {};
4355
+ }
4356
+
4357
+ options.dropZoneElements = [$el];
4358
+ var xformedOpts = transformVariables(options);
4359
+ addCallbacks(xformedOpts);
4360
+ dnd(new qq.DragAndDrop(xformedOpts));
4361
+
4362
+ return $el;
4363
+ };
4364
+
4365
+ function dataStore(key, val) {
4366
+ var data = $el.data(rootDataKey);
4367
+
4368
+ if (val) {
4369
+ if (data === undefined) {
4370
+ data = {};
4371
+ }
4372
+ data[key] = val;
4373
+ $el.data(rootDataKey, data);
4374
+ }
4375
+ else {
4376
+ if (data === undefined) {
4377
+ return null;
4378
+ }
4379
+ return data[key];
4380
+ }
4381
+ };
4382
+
4383
+ function dnd(instanceToStore) {
4384
+ return dataStore('dndInstance', instanceToStore);
4385
+ };
4386
+
4387
+ function addCallbacks(transformedOpts) {
4388
+ var callbacks = transformedOpts.callbacks = {},
4389
+ dndInst = new qq.FineUploaderBasic();
4390
+
4391
+ $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {
4392
+ var name = prop,
4393
+ $callbackEl;
4394
+
4395
+ $callbackEl = $el;
4396
+
4397
+ callbacks[prop] = function() {
4398
+ var args = Array.prototype.slice.call(arguments),
4399
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
4400
+
4401
+ return jqueryHandlerResult;
4402
+ };
4403
+ });
4404
+ };
4405
+
4406
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
4407
+ function transformVariables(source, dest) {
4408
+ var xformed, arrayVals;
4409
+
4410
+ if (dest === undefined) {
4411
+ xformed = {};
4412
+ }
4413
+ else {
4414
+ xformed = dest;
4415
+ }
4416
+
4417
+ $.each(source, function(prop, val) {
4418
+ if (val instanceof $) {
4419
+ xformed[prop] = val[0];
4420
+ }
4421
+ else if ($.isPlainObject(val)) {
4422
+ xformed[prop] = {};
4423
+ transformVariables(val, xformed[prop]);
4424
+ }
4425
+ else if ($.isArray(val)) {
4426
+ arrayVals = [];
4427
+ $.each(val, function(idx, arrayVal) {
4428
+ if (arrayVal instanceof $) {
4429
+ $.merge(arrayVals, arrayVal);
4430
+ }
4431
+ else {
4432
+ arrayVals.push(arrayVal);
4433
+ }
4434
+ });
4435
+ xformed[prop] = arrayVals;
4436
+ }
4437
+ else {
4438
+ xformed[prop] = val;
4439
+ }
4440
+ });
4441
+
4442
+ if (dest === undefined) {
4443
+ return xformed;
4444
+ }
4445
+ };
4446
+
4447
+ function isValidCommand(command) {
4448
+ return $.type(command) === "string" &&
4449
+ command === "dispose" &&
4450
+ dnd()[command] !== undefined;
4451
+ };
4452
+
4453
+ function delegateCommand(command) {
4454
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
4455
+ transformVariables(origArgs, xformedArgs);
4456
+ return dnd()[command].apply(dnd(), xformedArgs);
4457
+ };
4458
+
4459
+ $.fn.fineUploaderDnd = function(optionsOrCommand) {
4460
+ var self = this, selfArgs = arguments, retVals = [];
4461
+
4462
+ this.each(function(index, el) {
4463
+ $el = $(el);
4464
+
4465
+ if (dnd() && isValidCommand(optionsOrCommand)) {
4466
+ retVals.push(delegateCommand.apply(self, selfArgs));
4467
+
4468
+ if (self.length === 1) {
4469
+ return false;
4470
+ }
4471
+ }
4472
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
4473
+ init.apply(self, selfArgs);
4474
+ }
4475
+ else {
4476
+ $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module.");
4477
+ }
4478
+ });
4479
+
4480
+ if (retVals.length === 1) {
4481
+ return retVals[0];
4482
+ }
4483
+ else if (retVals.length > 1) {
4484
+ return retVals;
4485
+ }
4486
+
4487
+ return this;
4488
+ };
4489
+
4490
+ }(jQuery));