fileuploader-rails 3.4.1 → 3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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));