jquery-fileupload-rails 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
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