s3_cors_fileupload 0.1.5 → 0.2.0.pre1

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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.document +4 -3
  3. data/.gitignore +3 -7
  4. data/CHANGELOG.md +9 -1
  5. data/Gemfile +11 -11
  6. data/Gemfile.lock +66 -74
  7. data/README.md +4 -3
  8. data/Rakefile +7 -9
  9. data/lib/generators/s3_cors_fileupload/install/templates/s3_uploads.js +16 -22
  10. data/lib/generators/s3_cors_fileupload/install/templates/s3_uploads_controller.rb +1 -1
  11. data/lib/generators/s3_cors_fileupload/install/templates/source_file.rb +2 -2
  12. data/lib/generators/s3_cors_fileupload/install/templates/views/erb/_template_download.html.erb +2 -2
  13. data/lib/generators/s3_cors_fileupload/install/templates/views/erb/_template_upload.html.erb +4 -4
  14. data/lib/generators/s3_cors_fileupload/install/templates/views/erb/_template_uploaded.html.erb +1 -1
  15. data/lib/generators/s3_cors_fileupload/install/templates/views/erb/index.html.erb +2 -2
  16. data/lib/generators/s3_cors_fileupload/install/templates/views/haml/_template_download.html.haml +2 -2
  17. data/lib/generators/s3_cors_fileupload/install/templates/views/haml/_template_upload.html.haml +4 -4
  18. data/lib/generators/s3_cors_fileupload/install/templates/views/haml/_template_uploaded.html.haml +2 -2
  19. data/lib/generators/s3_cors_fileupload/install/templates/views/haml/index.html.haml +2 -2
  20. data/lib/s3_cors_fileupload/rails/config.rb +0 -1
  21. data/lib/s3_cors_fileupload/rails/form_helper.rb +2 -4
  22. data/lib/s3_cors_fileupload/rails/policy_helper.rb +2 -2
  23. data/lib/s3_cors_fileupload/version.rb +3 -3
  24. data/s3_cors_fileupload.gemspec +12 -15
  25. data/spec/dummy/Rakefile +7 -0
  26. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  27. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  28. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  29. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  30. data/spec/dummy/app/models/.gitkeep +0 -0
  31. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  32. data/spec/dummy/config.ru +4 -0
  33. data/spec/dummy/config/amazon_s3.yml +17 -0
  34. data/spec/dummy/config/application.rb +64 -0
  35. data/spec/dummy/config/boot.rb +10 -0
  36. data/spec/dummy/config/database.yml +25 -0
  37. data/spec/dummy/config/environment.rb +5 -0
  38. data/spec/dummy/config/environments/development.rb +37 -0
  39. data/spec/dummy/config/environments/production.rb +67 -0
  40. data/spec/dummy/config/environments/test.rb +37 -0
  41. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  42. data/spec/dummy/config/initializers/inflections.rb +15 -0
  43. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  44. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  45. data/spec/dummy/config/initializers/session_store.rb +8 -0
  46. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  47. data/spec/dummy/config/locales/en.yml +5 -0
  48. data/spec/dummy/config/routes.rb +58 -0
  49. data/spec/dummy/db/.gitkeep +0 -0
  50. data/spec/dummy/lib/assets/.gitkeep +0 -0
  51. data/spec/dummy/log/.gitkeep +0 -0
  52. data/spec/dummy/public/404.html +26 -0
  53. data/spec/dummy/public/422.html +26 -0
  54. data/spec/dummy/public/500.html +25 -0
  55. data/spec/dummy/public/favicon.ico +0 -0
  56. data/spec/dummy/script/rails +6 -0
  57. data/spec/lib/generators/install/install_generator_spec.rb +160 -0
  58. data/spec/lib/s3_cors_fileupload/rails/config_spec.rb +43 -0
  59. data/spec/{s3_cors_fileupload → lib/s3_cors_fileupload}/version_spec.rb +0 -0
  60. data/spec/spec_helper.rb +11 -2
  61. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload-ui.js +117 -43
  62. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload.js +187 -92
  63. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.iframe-transport.js +20 -7
  64. data/vendor/assets/javascripts/s3_cors_fileupload/vendor/jquery.ui.widget.js +72 -53
  65. data/vendor/assets/javascripts/s3_cors_fileupload/vendor/load-image.js +225 -37
  66. data/vendor/assets/stylesheets/jquery.fileupload-ui.css.erb +6 -8
  67. metadata +78 -21
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload Plugin 5.19
2
+ * jQuery File Upload Plugin 5.26
3
3
  * https://github.com/blueimp/jQuery-File-Upload
4
4
  *
5
5
  * Copyright 2010, Sebastian Tschan
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  /*jslint nomen: true, unparam: true, regexp: true */
13
- /*global define, window, document, Blob, FormData, location */
13
+ /*global define, window, document, File, Blob, FormData, location */
14
14
 
15
15
  (function (factory) {
16
16
  'use strict';
@@ -112,6 +112,8 @@
112
112
  progressInterval: 100,
113
113
  // Interval in milliseconds to calculate progress bitrate:
114
114
  bitrateInterval: 500,
115
+ // By default, uploads are started automatically when adding files:
116
+ autoUpload: true,
115
117
 
116
118
  // Additional form data to be sent along with the file uploads can be set
117
119
  // using this option, which accepts an array of objects with name and
@@ -136,37 +138,66 @@
136
138
  // handlers using jQuery's Deferred callbacks:
137
139
  // data.submit().done(func).fail(func).always(func);
138
140
  add: function (e, data) {
139
- data.submit();
141
+ if (data.autoUpload || (data.autoUpload !== false &&
142
+ ($(this).data('blueimp-fileupload') ||
143
+ $(this).data('fileupload')).options.autoUpload)) {
144
+ data.submit();
145
+ }
140
146
  },
141
147
 
142
148
  // Other callbacks:
149
+
143
150
  // Callback for the submit event of each file upload:
144
151
  // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
152
+
145
153
  // Callback for the start of each file upload request:
146
154
  // send: function (e, data) {}, // .bind('fileuploadsend', func);
155
+
147
156
  // Callback for successful uploads:
148
157
  // done: function (e, data) {}, // .bind('fileuploaddone', func);
158
+
149
159
  // Callback for failed (abort or error) uploads:
150
160
  // fail: function (e, data) {}, // .bind('fileuploadfail', func);
161
+
151
162
  // Callback for completed (success, abort or error) requests:
152
163
  // always: function (e, data) {}, // .bind('fileuploadalways', func);
164
+
153
165
  // Callback for upload progress events:
154
166
  // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
167
+
155
168
  // Callback for global upload progress events:
156
169
  // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
170
+
157
171
  // Callback for uploads start, equivalent to the global ajaxStart event:
158
172
  // start: function (e) {}, // .bind('fileuploadstart', func);
173
+
159
174
  // Callback for uploads stop, equivalent to the global ajaxStop event:
160
175
  // stop: function (e) {}, // .bind('fileuploadstop', func);
176
+
161
177
  // Callback for change events of the fileInput(s):
162
178
  // change: function (e, data) {}, // .bind('fileuploadchange', func);
179
+
163
180
  // Callback for paste events to the pasteZone(s):
164
181
  // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
182
+
165
183
  // Callback for drop events of the dropZone(s):
166
184
  // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
185
+
167
186
  // Callback for dragover events of the dropZone(s):
168
187
  // dragover: function (e) {}, // .bind('fileuploaddragover', func);
169
188
 
189
+ // Callback for the start of each chunk upload request:
190
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
191
+
192
+ // Callback for successful chunk uploads:
193
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
194
+
195
+ // Callback for failed (abort or error) chunk uploads:
196
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
197
+
198
+ // Callback for completed (success, abort or error) chunk upload requests:
199
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
200
+
170
201
  // The plugin options are used as settings object for the ajax calls.
171
202
  // The following are jQuery ajax settings required for the file uploads:
172
203
  processData: false,
@@ -209,10 +240,10 @@
209
240
  if (typeof options.formData === 'function') {
210
241
  return options.formData(options.form);
211
242
  }
212
- if ($.isArray(options.formData)) {
243
+ if ($.isArray(options.formData)) {
213
244
  return options.formData;
214
245
  }
215
- if (options.formData) {
246
+ if (options.formData) {
216
247
  formData = [];
217
248
  $.each(options.formData, function (name, value) {
218
249
  formData.push({name: name, value: value});
@@ -230,10 +261,17 @@
230
261
  return total;
231
262
  },
232
263
 
264
+ _initProgressObject: function (obj) {
265
+ obj._progress = {
266
+ loaded: 0,
267
+ total: 0,
268
+ bitrate: 0
269
+ };
270
+ },
271
+
233
272
  _onProgress: function (e, data) {
234
273
  if (e.lengthComputable) {
235
274
  var now = +(new Date()),
236
- total,
237
275
  loaded;
238
276
  if (data._time && data.progressInterval &&
239
277
  (now - data._time < data.progressInterval) &&
@@ -241,16 +279,19 @@
241
279
  return;
242
280
  }
243
281
  data._time = now;
244
- total = data.total || this._getTotal(data.files);
245
- loaded = parseInt(
246
- e.loaded / e.total * (data.chunkSize || total),
247
- 10
282
+ loaded = Math.floor(
283
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
248
284
  ) + (data.uploadedBytes || 0);
249
- this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
250
- data.lengthComputable = true;
251
- data.loaded = loaded;
252
- data.total = total;
253
- data.bitrate = data._bitrateTimer.getBitrate(
285
+ // Add the difference from the previously loaded state
286
+ // to the global loaded counter:
287
+ this._progress.loaded += (loaded - data._progress.loaded);
288
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
289
+ now,
290
+ this._progress.loaded,
291
+ data.bitrateInterval
292
+ );
293
+ data._progress.loaded = data.loaded = loaded;
294
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
254
295
  now,
255
296
  loaded,
256
297
  data.bitrateInterval
@@ -261,16 +302,7 @@
261
302
  this._trigger('progress', e, data);
262
303
  // Trigger a global progress event for all current file uploads,
263
304
  // including ajax calls queued for sequential file uploads:
264
- this._trigger('progressall', e, {
265
- lengthComputable: true,
266
- loaded: this._loaded,
267
- total: this._total,
268
- bitrate: this._bitrateTimer.getBitrate(
269
- now,
270
- this._loaded,
271
- data.bitrateInterval
272
- )
273
- });
305
+ this._trigger('progressall', e, this._progress);
274
306
  }
275
307
  },
276
308
 
@@ -305,10 +337,8 @@
305
337
  options.headers['Content-Range'] = options.contentRange;
306
338
  }
307
339
  if (!multipart) {
308
- // For cross domain requests, the X-File-Name header
309
- // must be allowed via Access-Control-Allow-Headers
310
- // or removed using the beforeSend callback:
311
- options.headers['X-File-Name'] = file.name;
340
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
341
+ encodeURI(file.name) + '"';
312
342
  options.contentType = file.type;
313
343
  options.data = options.blob || file;
314
344
  } else if ($.support.xhrFormDataFileUpload) {
@@ -341,18 +371,17 @@
341
371
  });
342
372
  }
343
373
  if (options.blob) {
344
- // For cross domain requests, the X-File-* headers
345
- // must be allowed via Access-Control-Allow-Headers
346
- // or removed using the beforeSend callback:
347
- options.headers['X-File-Name'] = file.name;
348
- options.headers['X-File-Type'] = file.type;
374
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
375
+ encodeURI(file.name) + '"';
349
376
  formData.append(paramName, options.blob, file.name);
350
377
  } else {
351
378
  $.each(options.files, function (index, file) {
352
- // File objects are also Blob instances.
379
+ // Files are also Blob instances, but some browsers
380
+ // (Firefox 3.6) support the File API but not Blobs.
353
381
  // This check allows the tests to run with
354
382
  // dummy objects:
355
- if (file instanceof Blob) {
383
+ if ((window.Blob && file instanceof Blob) ||
384
+ (window.File && file instanceof File)) {
356
385
  formData.append(
357
386
  options.paramName[index] || paramName,
358
387
  file,
@@ -442,7 +471,8 @@
442
471
  // The HTTP request method must be "POST" or "PUT":
443
472
  options.type = (options.type || options.form.prop('method') || '')
444
473
  .toUpperCase();
445
- if (options.type !== 'POST' && options.type !== 'PUT') {
474
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
475
+ options.type !== 'PATCH') {
446
476
  options.type = 'POST';
447
477
  }
448
478
  if (!options.formAcceptCharset) {
@@ -457,6 +487,21 @@
457
487
  return options;
458
488
  },
459
489
 
490
+ // jQuery 1.6 doesn't provide .state(),
491
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
492
+ _getDeferredState: function (deferred) {
493
+ if (deferred.state) {
494
+ return deferred.state();
495
+ }
496
+ if (deferred.isResolved()) {
497
+ return 'resolved';
498
+ }
499
+ if (deferred.isRejected()) {
500
+ return 'rejected';
501
+ }
502
+ return 'pending';
503
+ },
504
+
460
505
  // Maps jqXHR callbacks to the equivalent
461
506
  // methods of the given Promise object:
462
507
  _enhancePromise: function (promise) {
@@ -481,6 +526,33 @@
481
526
  return this._enhancePromise(promise);
482
527
  },
483
528
 
529
+ // Adds convenience methods to the callback arguments:
530
+ _addConvenienceMethods: function (e, data) {
531
+ var that = this;
532
+ data.submit = function () {
533
+ if (this.state() !== 'pending') {
534
+ data.jqXHR = this.jqXHR =
535
+ (that._trigger('submit', e, this) !== false) &&
536
+ that._onSend(e, this);
537
+ }
538
+ return this.jqXHR || that._getXHRPromise();
539
+ };
540
+ data.abort = function () {
541
+ if (this.jqXHR) {
542
+ return this.jqXHR.abort();
543
+ }
544
+ return this._getXHRPromise();
545
+ };
546
+ data.state = function () {
547
+ if (this.jqXHR) {
548
+ return that._getDeferredState(this.jqXHR);
549
+ }
550
+ };
551
+ data.progress = function () {
552
+ return this._progress;
553
+ };
554
+ },
555
+
484
556
  // Parses the Range header from the server response
485
557
  // and returns the uploaded bytes:
486
558
  _getUploadedBytes: function (jqXHR) {
@@ -523,13 +595,15 @@
523
595
  );
524
596
  }
525
597
  // The chunk upload method:
526
- upload = function (i) {
598
+ upload = function () {
527
599
  // Clone the options object for each chunk upload:
528
- var o = $.extend({}, options);
600
+ var o = $.extend({}, options),
601
+ currentLoaded = o._progress.loaded;
529
602
  o.blob = slice.call(
530
603
  file,
531
604
  ub,
532
- ub + mcs
605
+ ub + mcs,
606
+ file.type
533
607
  );
534
608
  // Store the current chunk size, as the blob itself
535
609
  // will be dereferenced after data processing:
@@ -541,13 +615,15 @@
541
615
  that._initXHRData(o);
542
616
  // Add progress listeners for this chunk upload:
543
617
  that._initProgressListener(o);
544
- jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
618
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
619
+ that._getXHRPromise(false, o.context))
545
620
  .done(function (result, textStatus, jqXHR) {
546
621
  ub = that._getUploadedBytes(jqXHR) ||
547
622
  (ub + o.chunkSize);
548
- // Create a progress event if upload is done and
549
- // no progress event has been invoked for this chunk:
550
- if (!o.loaded) {
623
+ // Create a progress event if no final progress event
624
+ // with loaded equaling total has been triggered
625
+ // for this chunk:
626
+ if (o._progress.loaded === currentLoaded) {
551
627
  that._onProgress($.Event('progress', {
552
628
  lengthComputable: true,
553
629
  loaded: ub - o.uploadedBytes,
@@ -555,6 +631,11 @@
555
631
  }), o);
556
632
  }
557
633
  options.uploadedBytes = o.uploadedBytes = ub;
634
+ o.result = result;
635
+ o.textStatus = textStatus;
636
+ o.jqXHR = jqXHR;
637
+ that._trigger('chunkdone', null, o);
638
+ that._trigger('chunkalways', null, o);
558
639
  if (ub < fs) {
559
640
  // File upload not yet complete,
560
641
  // continue with the next chunk:
@@ -567,6 +648,11 @@
567
648
  }
568
649
  })
569
650
  .fail(function (jqXHR, textStatus, errorThrown) {
651
+ o.jqXHR = jqXHR;
652
+ o.textStatus = textStatus;
653
+ o.errorThrown = errorThrown;
654
+ that._trigger('chunkfail', null, o);
655
+ that._trigger('chunkalways', null, o);
570
656
  dfd.rejectWith(
571
657
  o.context,
572
658
  [jqXHR, textStatus, errorThrown]
@@ -589,20 +675,31 @@
589
675
  this._trigger('start');
590
676
  // Set timer for global bitrate progress calculation:
591
677
  this._bitrateTimer = new this._BitrateTimer();
678
+ // Reset the global progress values:
679
+ this._progress.loaded = this._progress.total = 0;
680
+ this._progress.bitrate = 0;
592
681
  }
682
+ if (!data._progress) {
683
+ data._progress = {};
684
+ }
685
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
686
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
687
+ data._progress.bitrate = data.bitrate = 0;
593
688
  this._active += 1;
594
689
  // Initialize the global progress values:
595
- this._loaded += data.uploadedBytes || 0;
596
- this._total += this._getTotal(data.files);
690
+ this._progress.loaded += data.loaded;
691
+ this._progress.total += data.total;
597
692
  },
598
693
 
599
694
  _onDone: function (result, textStatus, jqXHR, options) {
600
- if (!this._isXHRUpload(options)) {
601
- // Create a progress event for each iframe load:
695
+ var total = options._progress.total;
696
+ if (options._progress.loaded < total) {
697
+ // Create a progress event if no final progress event
698
+ // with loaded equaling total has been triggered:
602
699
  this._onProgress($.Event('progress', {
603
700
  lengthComputable: true,
604
- loaded: 1,
605
- total: 1
701
+ loaded: total,
702
+ total: total
606
703
  }), options);
607
704
  }
608
705
  options.result = result;
@@ -619,47 +716,41 @@
619
716
  if (options.recalculateProgress) {
620
717
  // Remove the failed (error or abort) file upload from
621
718
  // the global progress calculation:
622
- this._loaded -= options.loaded || options.uploadedBytes || 0;
623
- this._total -= options.total || this._getTotal(options.files);
719
+ this._progress.loaded -= options._progress.loaded;
720
+ this._progress.total -= options._progress.total;
624
721
  }
625
722
  },
626
723
 
627
724
  _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
725
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
726
+ // options object via done and fail callbacks
628
727
  this._active -= 1;
629
- options.textStatus = textStatus;
630
- if (jqXHRorError && jqXHRorError.always) {
631
- options.jqXHR = jqXHRorError;
632
- options.result = jqXHRorResult;
633
- } else {
634
- options.jqXHR = jqXHRorResult;
635
- options.errorThrown = jqXHRorError;
636
- }
637
728
  this._trigger('always', null, options);
638
729
  if (this._active === 0) {
639
730
  // The stop callback is triggered when all uploads have
640
731
  // been completed, equivalent to the global ajaxStop event:
641
732
  this._trigger('stop');
642
- // Reset the global progress values:
643
- this._loaded = this._total = 0;
644
- this._bitrateTimer = null;
645
733
  }
646
734
  },
647
735
 
648
736
  _onSend: function (e, data) {
737
+ if (!data.submit) {
738
+ this._addConvenienceMethods(e, data);
739
+ }
649
740
  var that = this,
650
741
  jqXHR,
742
+ aborted,
651
743
  slot,
652
744
  pipe,
653
745
  options = that._getAJAXSettings(data),
654
- send = function (resolve, args) {
746
+ send = function () {
655
747
  that._sending += 1;
656
748
  // Set timer for bitrate progress calculation:
657
749
  options._bitrateTimer = new that._BitrateTimer();
658
750
  jqXHR = jqXHR || (
659
- (resolve !== false &&
660
- that._trigger('send', e, options) !== false &&
661
- (that._chunkedUpload(options) || $.ajax(options))) ||
662
- that._getXHRPromise(false, options.context, args)
751
+ ((aborted || that._trigger('send', e, options) === false) &&
752
+ that._getXHRPromise(false, options.context, aborted)) ||
753
+ that._chunkedUpload(options) || $.ajax(options)
663
754
  ).done(function (result, textStatus, jqXHR) {
664
755
  that._onDone(result, textStatus, jqXHR, options);
665
756
  }).fail(function (jqXHR, textStatus, errorThrown) {
@@ -676,15 +767,9 @@
676
767
  options.limitConcurrentUploads > that._sending) {
677
768
  // Start the next queued upload,
678
769
  // that has not been aborted:
679
- var nextSlot = that._slots.shift(),
680
- isPending;
770
+ var nextSlot = that._slots.shift();
681
771
  while (nextSlot) {
682
- // jQuery 1.6 doesn't provide .state(),
683
- // while jQuery 1.8+ removed .isRejected():
684
- isPending = nextSlot.state ?
685
- nextSlot.state() === 'pending' :
686
- !nextSlot.isRejected();
687
- if (isPending) {
772
+ if (that._getDeferredState(nextSlot) === 'pending') {
688
773
  nextSlot.resolve();
689
774
  break;
690
775
  }
@@ -709,12 +794,12 @@
709
794
  // which is delegated to the jqXHR object of the current upload,
710
795
  // and jqXHR callbacks mapped to the equivalent Promise methods:
711
796
  pipe.abort = function () {
712
- var args = [undefined, 'abort', 'abort'];
797
+ aborted = [undefined, 'abort', 'abort'];
713
798
  if (!jqXHR) {
714
799
  if (slot) {
715
- slot.rejectWith(pipe, args);
800
+ slot.rejectWith(options.context, aborted);
716
801
  }
717
- return send(false, args);
802
+ return send();
718
803
  }
719
804
  return jqXHR.abort();
720
805
  };
@@ -756,13 +841,10 @@
756
841
  var newData = $.extend({}, data);
757
842
  newData.files = fileSet ? element : [element];
758
843
  newData.paramName = paramNameSet[index];
759
- newData.submit = function () {
760
- newData.jqXHR = this.jqXHR =
761
- (that._trigger('submit', e, this) !== false) &&
762
- that._onSend(e, this);
763
- return this.jqXHR;
764
- };
765
- return (result = that._trigger('add', e, newData));
844
+ that._initProgressObject(newData);
845
+ that._addConvenienceMethods(e, newData);
846
+ result = that._trigger('add', e, newData);
847
+ return result;
766
848
  });
767
849
  return result;
768
850
  },
@@ -894,6 +976,12 @@
894
976
  // support the File API and we add a pseudo File object with
895
977
  // the input value as name with path information removed:
896
978
  files = [{name: value.replace(/^.*\\/, '')}];
979
+ } else if (files[0].name === undefined && files[0].fileName) {
980
+ // File normalization for Safari 4 and Firefox 3:
981
+ $.each(files, function (index, file) {
982
+ file.name = file.fileName;
983
+ file.size = file.fileSize;
984
+ });
897
985
  }
898
986
  return $.Deferred().resolve(files).promise();
899
987
  },
@@ -947,10 +1035,12 @@
947
1035
  },
948
1036
 
949
1037
  _onDrop: function (e) {
950
- e.preventDefault();
951
1038
  var that = this,
952
1039
  dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
953
1040
  data = {};
1041
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1042
+ e.preventDefault();
1043
+ }
954
1044
  this._getDroppedFiles(dataTransfer).always(function (files) {
955
1045
  data.files = files;
956
1046
  if (that._trigger('drop', e, data) !== false) {
@@ -964,10 +1054,10 @@
964
1054
  if (this._trigger('dragover', e) === false) {
965
1055
  return false;
966
1056
  }
967
- if (dataTransfer) {
1057
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) {
968
1058
  dataTransfer.dropEffect = 'copy';
1059
+ e.preventDefault();
969
1060
  }
970
- e.preventDefault();
971
1061
  },
972
1062
 
973
1063
  _initEventHandlers: function () {
@@ -1026,12 +1116,17 @@
1026
1116
  this._initSpecialOptions();
1027
1117
  this._slots = [];
1028
1118
  this._sequence = this._getXHRPromise(true);
1029
- this._sending = this._active = this._loaded = this._total = 0;
1119
+ this._sending = this._active = 0;
1120
+ this._initProgressObject(this);
1030
1121
  this._initEventHandlers();
1031
1122
  },
1032
1123
 
1033
- _destroy: function () {
1034
- this._destroyEventHandlers();
1124
+ // This method is exposed to the widget API and allows to query
1125
+ // the widget upload progress.
1126
+ // It returns an object with loaded, total and bitrate properties
1127
+ // for the running uploads:
1128
+ progress: function () {
1129
+ return this._progress;
1035
1130
  },
1036
1131
 
1037
1132
  // This method is exposed to the widget API and allows adding files