jquery-fileupload-rails 0.3.4 → 0.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,4 +1,4 @@
1
- # jQuery File Upload for Rails 3.1 Asset Pipeline
1
+ # jQuery File Upload for Rails
2
2
 
3
3
  [jQuery-File-Plugin](https://github.com/blueimp/jQuery-File-Upload) is a file upload plugin written by [Sebastian Tschan](https://github.com/blueimp). jQuery File Upload features multiple file selection, drag&drop support, progress bars and preview images for jQuery. Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
4
4
 
@@ -6,9 +6,9 @@ jquery-fileupload-rails is a library that integrates jQuery File Upload for Rail
6
6
 
7
7
  ## Plugin versions
8
8
 
9
- * jQuery File Upload User Interface Plugin 6.9.4
10
- * jQuery File Upload Plugin 5.16.3
11
- * jQuery UI Widget 1.8.23+amd
9
+ * jQuery File Upload User Interface Plugin 6.11
10
+ * jQuery File Upload Plugin 5.19.2
11
+ * jQuery UI Widget 1.9.1+amd
12
12
 
13
13
  ## Installing Gem
14
14
 
@@ -51,6 +51,9 @@ Require the stylesheet file to app/assets/stylesheets/application.css
51
51
  ## [Example app](https://github.com/tors/jquery-fileupload-rails-paperclip-example)
52
52
  This app uses paperclip and twitter-bootstrap-rails
53
53
 
54
+ You can also check out Ryan Bate's RailsCast [jQuery File Upload episode](http://railscasts.com/episodes/381-jquery-file-upload). You will
55
+ need a pro account to watch it though.
56
+
54
57
 
55
58
  ## Thanks
56
59
  Thanks to [Sebastian Tschan](https://github.com/blueimp) for writing an awesome file upload plugin.
@@ -1,7 +1,7 @@
1
1
  module JQuery
2
2
  module FileUpload
3
3
  module Rails
4
- VERSION = "0.3.4"
4
+ VERSION = "0.3.5"
5
5
  end
6
6
  end
7
7
  end
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload File Processing Plugin 1.0
2
+ * jQuery File Upload File Processing Plugin 1.2
3
3
  * https://github.com/blueimp/jQuery-File-Upload
4
4
  *
5
5
  * Copyright 2012, Sebastian Tschan
@@ -32,9 +32,9 @@
32
32
  }(function ($, loadImage) {
33
33
  'use strict';
34
34
 
35
- // The File Upload IP version extends the basic fileupload widget
35
+ // The File Upload FP version extends the fileupload widget
36
36
  // with file processing functionality:
37
- $.widget('blueimpFP.fileupload', $.blueimp.fileupload, {
37
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
38
38
 
39
39
  options: {
40
40
  // The list of file processing actions:
@@ -70,7 +70,7 @@
70
70
 
71
71
  processActions: {
72
72
  // Loads the image given via data.files and data.index
73
- // as canvas element.
73
+ // as img element if the browser supports canvas.
74
74
  // Accepts the options fileTypes (regular expression)
75
75
  // and maxFileSize (integer) to limit the files to load:
76
76
  load: function (data, options) {
@@ -85,28 +85,29 @@
85
85
  options.fileTypes.test(file.type))) {
86
86
  loadImage(
87
87
  file,
88
- function (canvas) {
89
- data.canvas = canvas;
88
+ function (img) {
89
+ data.img = img;
90
90
  dfd.resolveWith(that, [data]);
91
- },
92
- {canvas: true}
91
+ }
93
92
  );
94
93
  } else {
95
94
  dfd.rejectWith(that, [data]);
96
95
  }
97
96
  return dfd.promise();
98
97
  },
99
- // Resizes the image given as data.canvas and updates
100
- // data.canvas with the resized image.
98
+ // Resizes the image given as data.img and updates
99
+ // data.canvas with the resized image as canvas element.
101
100
  // Accepts the options maxWidth, maxHeight, minWidth and
102
101
  // minHeight to scale the given image:
103
102
  resize: function (data, options) {
104
- if (data.canvas) {
105
- var canvas = loadImage.scale(data.canvas, options);
106
- if (canvas.width !== data.canvas.width ||
107
- canvas.height !== data.canvas.height) {
103
+ var img = data.img,
104
+ canvas;
105
+ options = $.extend({canvas: true}, options);
106
+ if (img) {
107
+ canvas = loadImage.scale(img, options);
108
+ if (canvas.width !== img.width ||
109
+ canvas.height !== img.height) {
108
110
  data.canvas = canvas;
109
- data.processed = true;
110
111
  }
111
112
  }
112
113
  return data;
@@ -115,7 +116,7 @@
115
116
  // inplace at data.index of data.files:
116
117
  save: function (data, options) {
117
118
  // Do nothing if no processing has happened:
118
- if (!data.canvas || !data.processed) {
119
+ if (!data.canvas) {
119
120
  return data;
120
121
  }
121
122
  var that = this,
@@ -208,7 +209,7 @@
208
209
  },
209
210
 
210
211
  _create: function () {
211
- $.blueimp.fileupload.prototype._create.call(this);
212
+ this._super();
212
213
  this._processing = 0;
213
214
  this._processingQueue = $.Deferred().resolveWith(this)
214
215
  .promise();
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload User Interface Plugin 6.9.4
2
+ * jQuery File Upload User Interface Plugin 6.11
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, URL, webkitURL, FileReader */
13
+ /*global define, window, URL, webkitURL, FileReader */
14
14
 
15
15
  (function (factory) {
16
16
  'use strict';
@@ -33,10 +33,9 @@
33
33
  }(function ($, tmpl, loadImage) {
34
34
  'use strict';
35
35
 
36
- // The UI version extends the FP (file processing) version or the basic
37
- // file upload widget and adds complete user interface interaction:
38
- var parentWidget = ($.blueimpFP || $.blueimp).fileupload;
39
- $.widget('blueimpUI.fileupload', parentWidget, {
36
+ // The UI version extends the file upload widget
37
+ // and adds complete user interface interaction:
38
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
40
39
 
41
40
  options: {
42
41
  // By default, files added to the widget are uploaded as soon
@@ -144,7 +143,8 @@
144
143
  if (data.context) {
145
144
  data.context.each(function (index) {
146
145
  var file = ($.isArray(data.result) &&
147
- data.result[index]) || {error: 'emptyResult'};
146
+ data.result[index]) ||
147
+ {error: 'Empty file upload result'};
148
148
  if (file.error) {
149
149
  that._adjustMaxNumberOfFiles(1);
150
150
  }
@@ -398,22 +398,22 @@
398
398
  // maxNumberOfFiles before validation, so we check if
399
399
  // maxNumberOfFiles is below 0 (instead of below 1):
400
400
  if (this.options.maxNumberOfFiles < 0) {
401
- return 'maxNumberOfFiles';
401
+ return 'Maximum number of files exceeded';
402
402
  }
403
403
  // Files are accepted if either the file type or the file name
404
404
  // matches against the acceptFileTypes regular expression, as
405
405
  // only browsers with support for the File API report the type:
406
406
  if (!(this.options.acceptFileTypes.test(file.type) ||
407
407
  this.options.acceptFileTypes.test(file.name))) {
408
- return 'acceptFileTypes';
408
+ return 'Filetype not allowed';
409
409
  }
410
410
  if (this.options.maxFileSize &&
411
411
  file.size > this.options.maxFileSize) {
412
- return 'maxFileSize';
412
+ return 'File is too big';
413
413
  }
414
414
  if (typeof file.size === 'number' &&
415
415
  file.size < this.options.minFileSize) {
416
- return 'minFileSize';
416
+ return 'File is too small';
417
417
  }
418
418
  return null;
419
419
  },
@@ -457,7 +457,7 @@
457
457
  that._transition(node).done(function () {
458
458
  dfd.resolveWith(node);
459
459
  });
460
- if (!$.contains(document.body, node[0])) {
460
+ if (!$.contains(that.document[0].body, node[0])) {
461
461
  // If the element is not part of the DOM,
462
462
  // transition events are not triggered,
463
463
  // so we have to resolve manually:
@@ -510,7 +510,7 @@
510
510
 
511
511
  _startHandler: function (e) {
512
512
  e.preventDefault();
513
- var button = $(this),
513
+ var button = $(e.currentTarget),
514
514
  template = button.closest('.template-upload'),
515
515
  data = template.data('data');
516
516
  if (data && data.submit && !data.jqXHR && data.submit()) {
@@ -520,11 +520,11 @@
520
520
 
521
521
  _cancelHandler: function (e) {
522
522
  e.preventDefault();
523
- var template = $(this).closest('.template-upload'),
523
+ var template = $(e.currentTarget).closest('.template-upload'),
524
524
  data = template.data('data') || {};
525
525
  if (!data.jqXHR) {
526
526
  data.errorThrown = 'abort';
527
- e.data.fileupload._trigger('fail', e, data);
527
+ this._trigger('fail', e, data);
528
528
  } else {
529
529
  data.jqXHR.abort();
530
530
  }
@@ -532,13 +532,12 @@
532
532
 
533
533
  _deleteHandler: function (e) {
534
534
  e.preventDefault();
535
- var button = $(this);
536
- e.data.fileupload._trigger('destroy', e, {
535
+ var button = $(e.currentTarget);
536
+ this._trigger('destroy', e, $.extend({
537
537
  context: button.closest('.template-download'),
538
- url: button.attr('data-url'),
539
- type: button.attr('data-type') || 'DELETE',
540
- dataType: e.data.fileupload.options.dataType
541
- });
538
+ type: 'DELETE',
539
+ dataType: this.options.dataType
540
+ }, button.data()));
542
541
  },
543
542
 
544
543
  _forceReflow: function (node) {
@@ -569,75 +568,63 @@
569
568
 
570
569
  _initButtonBarEventHandlers: function () {
571
570
  var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
572
- filesList = this.options.filesContainer,
573
- ns = this.options.namespace;
574
- fileUploadButtonBar.find('.start')
575
- .bind('click.' + ns, function (e) {
571
+ filesList = this.options.filesContainer;
572
+ this._on(fileUploadButtonBar.find('.start'), {
573
+ click: function (e) {
576
574
  e.preventDefault();
577
575
  filesList.find('.start button').click();
578
- });
579
- fileUploadButtonBar.find('.cancel')
580
- .bind('click.' + ns, function (e) {
576
+ }
577
+ });
578
+ this._on(fileUploadButtonBar.find('.cancel'), {
579
+ click: function (e) {
581
580
  e.preventDefault();
582
581
  filesList.find('.cancel button').click();
583
- });
584
- fileUploadButtonBar.find('.delete')
585
- .bind('click.' + ns, function (e) {
582
+ }
583
+ });
584
+ this._on(fileUploadButtonBar.find('.delete'), {
585
+ click: function (e) {
586
586
  e.preventDefault();
587
587
  filesList.find('.delete input:checked')
588
588
  .siblings('button').click();
589
589
  fileUploadButtonBar.find('.toggle')
590
590
  .prop('checked', false);
591
- });
592
- fileUploadButtonBar.find('.toggle')
593
- .bind('change.' + ns, function (e) {
591
+ }
592
+ });
593
+ this._on(fileUploadButtonBar.find('.toggle'), {
594
+ change: function (e) {
594
595
  filesList.find('.delete input').prop(
595
596
  'checked',
596
- $(this).is(':checked')
597
+ $(e.currentTarget).is(':checked')
597
598
  );
598
- });
599
+ }
600
+ });
599
601
  },
600
602
 
601
603
  _destroyButtonBarEventHandlers: function () {
602
- this.element.find('.fileupload-buttonbar button')
603
- .unbind('click.' + this.options.namespace);
604
- this.element.find('.fileupload-buttonbar .toggle')
605
- .unbind('change.' + this.options.namespace);
604
+ this._off(
605
+ this.element.find('.fileupload-buttonbar button'),
606
+ 'click'
607
+ );
608
+ this._off(
609
+ this.element.find('.fileupload-buttonbar .toggle'),
610
+ 'change.'
611
+ );
606
612
  },
607
613
 
608
614
  _initEventHandlers: function () {
609
- parentWidget.prototype._initEventHandlers.call(this);
610
- var eventData = {fileupload: this};
611
- this.options.filesContainer
612
- .delegate(
613
- '.start button',
614
- 'click.' + this.options.namespace,
615
- eventData,
616
- this._startHandler
617
- )
618
- .delegate(
619
- '.cancel button',
620
- 'click.' + this.options.namespace,
621
- eventData,
622
- this._cancelHandler
623
- )
624
- .delegate(
625
- '.delete button',
626
- 'click.' + this.options.namespace,
627
- eventData,
628
- this._deleteHandler
629
- );
615
+ this._super();
616
+ this._on(this.options.filesContainer, {
617
+ 'click .start button': this._startHandler,
618
+ 'click .cancel button': this._cancelHandler,
619
+ 'click .delete button': this._deleteHandler
620
+ });
630
621
  this._initButtonBarEventHandlers();
631
622
  },
632
623
 
633
624
  _destroyEventHandlers: function () {
634
- var options = this.options;
635
625
  this._destroyButtonBarEventHandlers();
636
- options.filesContainer
637
- .undelegate('.start button', 'click.' + options.namespace)
638
- .undelegate('.cancel button', 'click.' + options.namespace)
639
- .undelegate('.delete button', 'click.' + options.namespace);
640
- parentWidget.prototype._destroyEventHandlers.call(this);
626
+ this._off(this.options.filesContainer, 'click');
627
+ this._super();
641
628
  },
642
629
 
643
630
  _enableFileInputButton: function () {
@@ -654,7 +641,7 @@
654
641
 
655
642
  _initTemplates: function () {
656
643
  var options = this.options;
657
- options.templatesContainer = document.createElement(
644
+ options.templatesContainer = this.document[0].createElement(
658
645
  options.filesContainer.prop('nodeName')
659
646
  );
660
647
  if (tmpl) {
@@ -698,20 +685,20 @@
698
685
  },
699
686
 
700
687
  _initSpecialOptions: function () {
701
- parentWidget.prototype._initSpecialOptions.call(this);
688
+ this._super();
702
689
  this._initFilesContainer();
703
690
  this._initTemplates();
704
691
  this._initRegExpOptions();
705
692
  },
706
693
 
707
694
  _create: function () {
708
- parentWidget.prototype._create.call(this);
695
+ this._super();
709
696
  this._refreshOptionsList.push(
710
697
  'filesContainer',
711
698
  'uploadTemplateId',
712
699
  'downloadTemplateId'
713
700
  );
714
- if (!$.blueimpFP) {
701
+ if (!this._processingQueue) {
715
702
  this._processingQueue = $.Deferred().resolveWith(this).promise();
716
703
  this.process = function () {
717
704
  return this._processingQueue;
@@ -720,15 +707,23 @@
720
707
  },
721
708
 
722
709
  enable: function () {
723
- parentWidget.prototype.enable.call(this);
724
- this.element.find('input, button').prop('disabled', false);
725
- this._enableFileInputButton();
710
+ var wasDisabled = false;
711
+ if (this.options.disabled) {
712
+ wasDisabled = true;
713
+ }
714
+ this._super();
715
+ if (wasDisabled) {
716
+ this.element.find('input, button').prop('disabled', false);
717
+ this._enableFileInputButton();
718
+ }
726
719
  },
727
720
 
728
721
  disable: function () {
729
- this.element.find('input, button').prop('disabled', true);
730
- this._disableFileInputButton();
731
- parentWidget.prototype.disable.call(this);
722
+ if (!this.options.disabled) {
723
+ this.element.find('input, button').prop('disabled', true);
724
+ this._disableFileInputButton();
725
+ }
726
+ this._super();
732
727
  }
733
728
 
734
729
  });
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload Plugin 5.16.3
2
+ * jQuery File Upload Plugin 5.19.2
3
3
  * https://github.com/blueimp/jQuery-File-Upload
4
4
  *
5
5
  * Copyright 2010, Sebastian Tschan
@@ -44,17 +44,16 @@
44
44
  $.widget('blueimp.fileupload', {
45
45
 
46
46
  options: {
47
- // The namespace used for event handler binding on the dropZone and
48
- // fileInput collections.
49
- // If not set, the name of the widget ("fileupload") is used.
50
- namespace: undefined,
51
- // The drop target collection, by the default the complete document.
52
- // Set to null or an empty collection to disable drag & drop support:
47
+ // The drop target element(s), by the default the complete document.
48
+ // Set to null to disable drag & drop support:
53
49
  dropZone: $(document),
54
- // The file input field collection, that is listened for change events.
50
+ // The paste target element(s), by the default the complete document.
51
+ // Set to null to disable paste support:
52
+ pasteZone: $(document),
53
+ // The file input field(s), that are listened to for change events.
55
54
  // If undefined, it is set to the file input fields inside
56
55
  // of the widget element on plugin initialization.
57
- // Set to null or an empty collection to disable the change listener.
56
+ // Set to null to disable the change listener.
58
57
  fileInput: undefined,
59
58
  // By default, the file input field is replaced with a clone after
60
59
  // each input field change event. This is required for iframe transport
@@ -159,13 +158,13 @@
159
158
  // start: function (e) {}, // .bind('fileuploadstart', func);
160
159
  // Callback for uploads stop, equivalent to the global ajaxStop event:
161
160
  // stop: function (e) {}, // .bind('fileuploadstop', func);
162
- // Callback for change events of the fileInput collection:
161
+ // Callback for change events of the fileInput(s):
163
162
  // change: function (e, data) {}, // .bind('fileuploadchange', func);
164
- // Callback for paste events to the dropZone collection:
163
+ // Callback for paste events to the pasteZone(s):
165
164
  // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
166
- // Callback for drop events of the dropZone collection:
165
+ // Callback for drop events of the dropZone(s):
167
166
  // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
168
- // Callback for dragover events of the dropZone collection:
167
+ // Callback for dragover events of the dropZone(s):
169
168
  // dragover: function (e) {}, // .bind('fileuploaddragover', func);
170
169
 
171
170
  // The plugin options are used as settings object for the ajax calls.
@@ -177,9 +176,9 @@
177
176
 
178
177
  // A list of options that require a refresh after assigning a new value:
179
178
  _refreshOptionsList: [
180
- 'namespace',
181
- 'dropZone',
182
179
  'fileInput',
180
+ 'dropZone',
181
+ 'pasteZone',
183
182
  'multipart',
184
183
  'forceIframeTransport'
185
184
  ],
@@ -301,29 +300,16 @@
301
300
  // Ignore non-multipart setting if not supported:
302
301
  multipart = options.multipart || !$.support.xhrFileUpload,
303
302
  paramName = options.paramName[0];
304
- if (!multipart || options.blob) {
305
- // For non-multipart uploads and chunked uploads,
306
- // file meta data is not part of the request body,
307
- // so we transmit this data as part of the HTTP headers.
308
- // For cross domain requests, these headers must be allowed
309
- // via Access-Control-Allow-Headers or removed using
310
- // the beforeSend callback:
311
- options.headers = $.extend(options.headers, {
312
- 'X-File-Name': file.name,
313
- 'X-File-Type': file.type,
314
- 'X-File-Size': file.size
315
- });
316
- if (!options.blob) {
317
- // Non-chunked non-multipart upload:
318
- options.contentType = file.type;
319
- options.data = file;
320
- } else if (!multipart) {
321
- // Chunked non-multipart upload:
322
- options.contentType = 'application/octet-stream';
323
- options.data = options.blob;
324
- }
303
+ options.headers = options.headers || {};
304
+ if (options.contentRange) {
305
+ options.headers['Content-Range'] = options.contentRange;
325
306
  }
326
- if (multipart && $.support.xhrFormDataFileUpload) {
307
+ if (!multipart) {
308
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
309
+ encodeURI(file.name) + '"';
310
+ options.contentType = file.type;
311
+ options.data = options.blob || file;
312
+ } else if ($.support.xhrFormDataFileUpload) {
327
313
  if (options.postMessage) {
328
314
  // window.postMessage does not allow sending FormData
329
315
  // objects, so we just add the File/Blob objects to
@@ -353,6 +339,9 @@
353
339
  });
354
340
  }
355
341
  if (options.blob) {
342
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
343
+ encodeURI(file.name) + '"';
344
+ options.headers['Content-Description'] = encodeURI(file.type);
356
345
  formData.append(paramName, options.blob, file.name);
357
346
  } else {
358
347
  $.each(options.files, function (index, file) {
@@ -436,6 +425,11 @@
436
425
  // associated form, if available:
437
426
  if (!options.form || !options.form.length) {
438
427
  options.form = $(options.fileInput.prop('form'));
428
+ // If the given file input doesn't have an associated form,
429
+ // use the default widget file input's form:
430
+ if (!options.form.length) {
431
+ options.form = $(this.options.fileInput.prop('form'));
432
+ }
439
433
  }
440
434
  options.paramName = this._getParamName(options);
441
435
  if (!options.url) {
@@ -483,6 +477,16 @@
483
477
  return this._enhancePromise(promise);
484
478
  },
485
479
 
480
+ // Parses the Range header from the server response
481
+ // and returns the uploaded bytes:
482
+ _getUploadedBytes: function (jqXHR) {
483
+ var range = jqXHR.getResponseHeader('Range'),
484
+ parts = range && range.split('-'),
485
+ upperBytesPos = parts && parts.length > 1 &&
486
+ parseInt(parts[1], 10);
487
+ return upperBytesPos && upperBytesPos + 1;
488
+ },
489
+
486
490
  // Uploads a file in multiple, sequential requests
487
491
  // by splitting the file up in multiple blob chunks.
488
492
  // If the second parameter is true, only tests if the file
@@ -494,13 +498,11 @@
494
498
  fs = file.size,
495
499
  ub = options.uploadedBytes = options.uploadedBytes || 0,
496
500
  mcs = options.maxChunkSize || fs,
497
- // Use the Blob methods with the slice implementation
498
- // according to the W3C Blob API specification:
499
- slice = file.webkitSlice || file.mozSlice || file.slice,
500
- upload,
501
- n,
501
+ slice = file.slice || file.webkitSlice || file.mozSlice,
502
+ dfd = $.Deferred(),
503
+ promise = dfd.promise(),
502
504
  jqXHR,
503
- pipe;
505
+ upload;
504
506
  if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
505
507
  options.data) {
506
508
  return false;
@@ -509,66 +511,70 @@
509
511
  return true;
510
512
  }
511
513
  if (ub >= fs) {
512
- file.error = 'uploadedBytes';
514
+ file.error = 'Uploaded bytes exceed file size';
513
515
  return this._getXHRPromise(
514
516
  false,
515
517
  options.context,
516
518
  [null, 'error', file.error]
517
519
  );
518
520
  }
519
- // n is the number of blobs to upload,
520
- // calculated via filesize, uploaded bytes and max chunk size:
521
- n = Math.ceil((fs - ub) / mcs);
522
- // The chunk upload method accepting the chunk number as parameter:
521
+ // The chunk upload method:
523
522
  upload = function (i) {
524
- if (!i) {
525
- return that._getXHRPromise(true, options.context);
526
- }
527
- // Upload the blobs in sequential order:
528
- return upload(i -= 1).pipe(function () {
529
- // Clone the options object for each chunk upload:
530
- var o = $.extend({}, options);
531
- o.blob = slice.call(
532
- file,
533
- ub + i * mcs,
534
- ub + (i + 1) * mcs
535
- );
536
- // Expose the chunk index:
537
- o.chunkIndex = i;
538
- // Expose the number of chunks:
539
- o.chunksNumber = n;
540
- // Store the current chunk size, as the blob itself
541
- // will be dereferenced after data processing:
542
- o.chunkSize = o.blob.size;
543
- // Process the upload data (the blob and potential form data):
544
- that._initXHRData(o);
545
- // Add progress listeners for this chunk upload:
546
- that._initProgressListener(o);
547
- jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
548
- .done(function () {
549
- // Create a progress event if upload is done and
550
- // no progress event has been invoked for this chunk:
551
- if (!o.loaded) {
552
- that._onProgress($.Event('progress', {
553
- lengthComputable: true,
554
- loaded: o.chunkSize,
555
- total: o.chunkSize
556
- }), o);
557
- }
558
- options.uploadedBytes = o.uploadedBytes +=
559
- o.chunkSize;
560
- });
561
- return jqXHR;
562
- });
523
+ // Clone the options object for each chunk upload:
524
+ var o = $.extend({}, options);
525
+ o.blob = slice.call(
526
+ file,
527
+ ub,
528
+ ub + mcs
529
+ );
530
+ // Store the current chunk size, as the blob itself
531
+ // will be dereferenced after data processing:
532
+ o.chunkSize = o.blob.size;
533
+ // Expose the chunk bytes position range:
534
+ o.contentRange = 'bytes ' + ub + '-' +
535
+ (ub + o.chunkSize - 1) + '/' + fs;
536
+ // Process the upload data (the blob and potential form data):
537
+ that._initXHRData(o);
538
+ // Add progress listeners for this chunk upload:
539
+ that._initProgressListener(o);
540
+ jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
541
+ .done(function (result, textStatus, jqXHR) {
542
+ ub = that._getUploadedBytes(jqXHR) ||
543
+ (ub + o.chunkSize);
544
+ // Create a progress event if upload is done and
545
+ // no progress event has been invoked for this chunk:
546
+ if (!o.loaded) {
547
+ that._onProgress($.Event('progress', {
548
+ lengthComputable: true,
549
+ loaded: ub - o.uploadedBytes,
550
+ total: ub - o.uploadedBytes
551
+ }), o);
552
+ }
553
+ options.uploadedBytes = o.uploadedBytes = ub;
554
+ if (ub < fs) {
555
+ // File upload not yet complete,
556
+ // continue with the next chunk:
557
+ upload();
558
+ } else {
559
+ dfd.resolveWith(
560
+ o.context,
561
+ [result, textStatus, jqXHR]
562
+ );
563
+ }
564
+ })
565
+ .fail(function (jqXHR, textStatus, errorThrown) {
566
+ dfd.rejectWith(
567
+ o.context,
568
+ [jqXHR, textStatus, errorThrown]
569
+ );
570
+ });
563
571
  };
564
- // Return the piped Promise object, enhanced with an abort method,
565
- // which is delegated to the jqXHR object of the current upload,
566
- // and jqXHR callbacks mapped to the equivalent Promise methods:
567
- pipe = upload(n);
568
- pipe.abort = function () {
572
+ this._enhancePromise(promise);
573
+ promise.abort = function () {
569
574
  return jqXHR.abort();
570
575
  };
571
- return this._enhancePromise(pipe);
576
+ upload();
577
+ return promise;
572
578
  },
573
579
 
574
580
  _beforeSend: function (e, data) {
@@ -766,7 +772,7 @@
766
772
  // Avoid memory leaks with the detached file input:
767
773
  $.cleanData(input.unbind('remove'));
768
774
  // Replace the original file input element in the fileInput
769
- // collection with the clone, which has been copied including
775
+ // elements set with the clone, which has been copied including
770
776
  // event handlers:
771
777
  this.options.fileInput = this.options.fileInput.map(function (i, el) {
772
778
  if (el === input[0]) {
@@ -784,16 +790,29 @@
784
790
  _handleFileTreeEntry: function (entry, path) {
785
791
  var that = this,
786
792
  dfd = $.Deferred(),
787
- errorHandler = function () {
788
- dfd.reject();
793
+ errorHandler = function (e) {
794
+ if (e && !e.entry) {
795
+ e.entry = entry;
796
+ }
797
+ // Since $.when returns immediately if one
798
+ // Deferred is rejected, we use resolve instead.
799
+ // This allows valid files and invalid items
800
+ // to be returned together in one set:
801
+ dfd.resolve([e]);
789
802
  },
790
803
  dirReader;
791
804
  path = path || '';
792
805
  if (entry.isFile) {
793
- entry.file(function (file) {
794
- file.relativePath = path;
795
- dfd.resolve(file);
796
- }, errorHandler);
806
+ if (entry._file) {
807
+ // Workaround for Chrome bug #149735
808
+ entry._file.relativePath = path;
809
+ dfd.resolve(entry._file);
810
+ } else {
811
+ entry.file(function (file) {
812
+ file.relativePath = path;
813
+ dfd.resolve(file);
814
+ }, errorHandler);
815
+ }
797
816
  } else if (entry.isDirectory) {
798
817
  dirReader = entry.createReader();
799
818
  dirReader.readEntries(function (entries) {
@@ -805,7 +824,9 @@
805
824
  }).fail(errorHandler);
806
825
  }, errorHandler);
807
826
  } else {
808
- errorHandler();
827
+ // Return an empy list for file system items
828
+ // other than files or directories:
829
+ dfd.resolve([]);
809
830
  }
810
831
  return dfd.promise();
811
832
  },
@@ -832,8 +853,14 @@
832
853
  items[0].getAsEntry)) {
833
854
  return this._handleFileTreeEntries(
834
855
  $.map(items, function (item) {
856
+ var entry;
835
857
  if (item.webkitGetAsEntry) {
836
- return item.webkitGetAsEntry();
858
+ entry = item.webkitGetAsEntry();
859
+ if (entry) {
860
+ // Workaround for Chrome bug #149735:
861
+ entry._file = item.getAsFile();
862
+ }
863
+ return entry;
837
864
  }
838
865
  return item.getAsEntry();
839
866
  })
@@ -844,7 +871,7 @@
844
871
  ).promise();
845
872
  },
846
873
 
847
- _getFileInputFiles: function (fileInput) {
874
+ _getSingleFileInputFiles: function (fileInput) {
848
875
  fileInput = $(fileInput);
849
876
  var entries = fileInput.prop('webkitEntries') ||
850
877
  fileInput.prop('entries'),
@@ -857,23 +884,44 @@
857
884
  if (!files.length) {
858
885
  value = fileInput.prop('value');
859
886
  if (!value) {
860
- return $.Deferred().reject([]).promise();
887
+ return $.Deferred().resolve([]).promise();
861
888
  }
862
889
  // If the files property is not available, the browser does not
863
890
  // support the File API and we add a pseudo File object with
864
891
  // the input value as name with path information removed:
865
892
  files = [{name: value.replace(/^.*\\/, '')}];
893
+ } else if (files[0].name === undefined && files[0].fileName) {
894
+ // File normalization for Safari 4 and Firefox 3:
895
+ $.each(files, function (index, file) {
896
+ file.name = file.fileName;
897
+ file.size = file.fileSize;
898
+ });
866
899
  }
867
900
  return $.Deferred().resolve(files).promise();
868
901
  },
869
902
 
903
+ _getFileInputFiles: function (fileInput) {
904
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
905
+ return this._getSingleFileInputFiles(fileInput);
906
+ }
907
+ return $.when.apply(
908
+ $,
909
+ $.map(fileInput, this._getSingleFileInputFiles)
910
+ ).pipe(function () {
911
+ return Array.prototype.concat.apply(
912
+ [],
913
+ arguments
914
+ );
915
+ });
916
+ },
917
+
870
918
  _onChange: function (e) {
871
- var that = e.data.fileupload,
919
+ var that = this,
872
920
  data = {
873
921
  fileInput: $(e.target),
874
922
  form: $(e.target.form)
875
923
  };
876
- that._getFileInputFiles(data.fileInput).always(function (files) {
924
+ this._getFileInputFiles(data.fileInput).always(function (files) {
877
925
  data.files = files;
878
926
  if (that.options.replaceFileInput) {
879
927
  that._replaceFileInput(data.fileInput);
@@ -885,8 +933,7 @@
885
933
  },
886
934
 
887
935
  _onPaste: function (e) {
888
- var that = e.data.fileupload,
889
- cbd = e.originalEvent.clipboardData,
936
+ var cbd = e.originalEvent.clipboardData,
890
937
  items = (cbd && cbd.items) || [],
891
938
  data = {files: []};
892
939
  $.each(items, function (index, item) {
@@ -895,18 +942,18 @@
895
942
  data.files.push(file);
896
943
  }
897
944
  });
898
- if (that._trigger('paste', e, data) === false ||
899
- that._onAdd(e, data) === false) {
945
+ if (this._trigger('paste', e, data) === false ||
946
+ this._onAdd(e, data) === false) {
900
947
  return false;
901
948
  }
902
949
  },
903
950
 
904
951
  _onDrop: function (e) {
905
952
  e.preventDefault();
906
- var that = e.data.fileupload,
953
+ var that = this,
907
954
  dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
908
955
  data = {};
909
- that._getDroppedFiles(dataTransfer).always(function (files) {
956
+ this._getDroppedFiles(dataTransfer).always(function (files) {
910
957
  data.files = files;
911
958
  if (that._trigger('drop', e, data) !== false) {
912
959
  that._onAdd(e, data);
@@ -915,9 +962,8 @@
915
962
  },
916
963
 
917
964
  _onDragOver: function (e) {
918
- var that = e.data.fileupload,
919
- dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
920
- if (that._trigger('dragover', e) === false) {
965
+ var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
966
+ if (this._trigger('dragover', e) === false) {
921
967
  return false;
922
968
  }
923
969
  if (dataTransfer) {
@@ -927,25 +973,24 @@
927
973
  },
928
974
 
929
975
  _initEventHandlers: function () {
930
- var ns = this.options.namespace;
931
976
  if (this._isXHRUpload(this.options)) {
932
- this.options.dropZone
933
- .bind('dragover.' + ns, {fileupload: this}, this._onDragOver)
934
- .bind('drop.' + ns, {fileupload: this}, this._onDrop)
935
- .bind('paste.' + ns, {fileupload: this}, this._onPaste);
977
+ this._on(this.options.dropZone, {
978
+ dragover: this._onDragOver,
979
+ drop: this._onDrop
980
+ });
981
+ this._on(this.options.pasteZone, {
982
+ paste: this._onPaste
983
+ });
936
984
  }
937
- this.options.fileInput
938
- .bind('change.' + ns, {fileupload: this}, this._onChange);
985
+ this._on(this.options.fileInput, {
986
+ change: this._onChange
987
+ });
939
988
  },
940
989
 
941
990
  _destroyEventHandlers: function () {
942
- var ns = this.options.namespace;
943
- this.options.dropZone
944
- .unbind('dragover.' + ns, this._onDragOver)
945
- .unbind('drop.' + ns, this._onDrop)
946
- .unbind('paste.' + ns, this._onPaste);
947
- this.options.fileInput
948
- .unbind('change.' + ns, this._onChange);
991
+ this._off(this.options.dropZone, 'dragover drop');
992
+ this._off(this.options.pasteZone, 'paste');
993
+ this._off(this.options.fileInput, 'change');
949
994
  },
950
995
 
951
996
  _setOption: function (key, value) {
@@ -953,7 +998,7 @@
953
998
  if (refresh) {
954
999
  this._destroyEventHandlers();
955
1000
  }
956
- $.Widget.prototype._setOption.call(this, key, value);
1001
+ this._super(key, value);
957
1002
  if (refresh) {
958
1003
  this._initSpecialOptions();
959
1004
  this._initEventHandlers();
@@ -971,13 +1016,15 @@
971
1016
  if (!(options.dropZone instanceof $)) {
972
1017
  options.dropZone = $(options.dropZone);
973
1018
  }
1019
+ if (!(options.pasteZone instanceof $)) {
1020
+ options.pasteZone = $(options.pasteZone);
1021
+ }
974
1022
  },
975
1023
 
976
1024
  _create: function () {
977
1025
  var options = this.options;
978
1026
  // Initialize options set via HTML5 data-attributes:
979
1027
  $.extend(options, $(this.element[0].cloneNode(false)).data());
980
- options.namespace = options.namespace || this.widgetName;
981
1028
  this._initSpecialOptions();
982
1029
  this._slots = [];
983
1030
  this._sequence = this._getXHRPromise(true);
@@ -985,19 +1032,8 @@
985
1032
  this._initEventHandlers();
986
1033
  },
987
1034
 
988
- destroy: function () {
989
- this._destroyEventHandlers();
990
- $.Widget.prototype.destroy.call(this);
991
- },
992
-
993
- enable: function () {
994
- $.Widget.prototype.enable.call(this);
995
- this._initEventHandlers();
996
- },
997
-
998
- disable: function () {
1035
+ _destroy: function () {
999
1036
  this._destroyEventHandlers();
1000
- $.Widget.prototype.disable.call(this);
1001
1037
  },
1002
1038
 
1003
1039
  // This method is exposed to the widget API and allows adding files