glebtv-rails-uploader 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +17 -10
  3. data/{vendor → app}/assets/images/uploader/but_del_tag2.png +0 -0
  4. data/{vendor → app}/assets/images/uploader/ico_attach.png +0 -0
  5. data/{vendor → app}/assets/images/uploader/preloader.gif +0 -0
  6. data/{vendor → app}/assets/images/uploader/progressBarFillBg.png +0 -0
  7. data/app/assets/javascripts/uploader/application.js +2 -2
  8. data/app/assets/javascripts/uploader/canvas-to-blob.js +95 -0
  9. data/app/assets/javascripts/uploader/jquery.fileupload-angular.js +348 -0
  10. data/app/assets/javascripts/uploader/jquery.fileupload-process.js +158 -0
  11. data/app/assets/javascripts/uploader/jquery.fileupload-resize.js +212 -0
  12. data/{vendor → app}/assets/javascripts/uploader/jquery.fileupload-ui.js +265 -269
  13. data/app/assets/javascripts/uploader/jquery.fileupload-validate.js +116 -0
  14. data/{vendor → app}/assets/javascripts/uploader/jquery.fileupload.js +655 -258
  15. data/{vendor → app}/assets/javascripts/uploader/jquery.iframe-transport.js +29 -9
  16. data/app/assets/javascripts/uploader/jquery.ui.widget.js +530 -0
  17. data/app/assets/javascripts/uploader/load-image.js +381 -0
  18. data/{vendor → app}/assets/javascripts/uploader/locales/en.js +0 -0
  19. data/{vendor → app}/assets/javascripts/uploader/locales/ru.js +0 -0
  20. data/{vendor → app}/assets/javascripts/uploader/locales/uk.js +0 -0
  21. data/app/assets/javascripts/uploader/rails_admin.js +26 -24
  22. data/app/assets/javascripts/uploader/tmpl.js +86 -0
  23. data/{vendor → app}/assets/stylesheets/uploader/default.css +0 -0
  24. data/{vendor → app}/assets/stylesheets/uploader/jquery.fileupload-ui.css +0 -0
  25. data/app/controllers/uploader/attachments_controller.rb +13 -10
  26. data/app/views/rails_admin/main/_form_rails_uploader.haml +9 -9
  27. data/app/views/uploader/default/_container.html.erb +1 -2
  28. data/app/views/uploader/default/_download.html.erb +1 -1
  29. data/lib/uploader/asset.rb +2 -2
  30. data/lib/uploader/engine.rb +4 -4
  31. data/lib/uploader/fileuploads.rb +18 -18
  32. data/lib/uploader/helpers/field_tag.rb +16 -17
  33. data/lib/uploader/helpers/form_builder.rb +1 -1
  34. data/lib/uploader/helpers/form_tag_helper.rb +1 -1
  35. data/lib/uploader/hooks/formtastic.rb +1 -2
  36. data/lib/uploader/rails_admin/field.rb +27 -27
  37. data/lib/uploader/version.rb +1 -1
  38. metadata +115 -131
  39. data/vendor/assets/javascripts/uploader/jquery.fileupload-ip.js +0 -160
  40. data/vendor/assets/javascripts/uploader/jquery.ui.widget.js +0 -282
  41. data/vendor/assets/javascripts/uploader/load-image.min.js +0 -1
  42. data/vendor/assets/javascripts/uploader/tmpl.min.js +0 -1
@@ -0,0 +1,116 @@
1
+ /*
2
+ * jQuery File Upload Validation Plugin 1.0.2
3
+ * https://github.com/blueimp/jQuery-File-Upload
4
+ *
5
+ * Copyright 2013, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /*jslint nomen: true, unparam: true, regexp: true */
13
+ /*global define, window */
14
+
15
+ (function (factory) {
16
+ 'use strict';
17
+ if (typeof define === 'function' && define.amd) {
18
+ // Register as an anonymous AMD module:
19
+ define([
20
+ 'jquery',
21
+ './jquery.fileupload-process'
22
+ ], factory);
23
+ } else {
24
+ // Browser globals:
25
+ factory(
26
+ window.jQuery
27
+ );
28
+ }
29
+ }(function ($) {
30
+ 'use strict';
31
+
32
+ // Append to the default processQueue:
33
+ $.blueimp.fileupload.prototype.options.processQueue.push(
34
+ {
35
+ action: 'validate',
36
+ // Always trigger this action,
37
+ // even if the previous action was rejected:
38
+ always: true,
39
+ // Options taken from the global options map:
40
+ acceptFileTypes: '@acceptFileTypes',
41
+ maxFileSize: '@maxFileSize',
42
+ minFileSize: '@minFileSize',
43
+ maxNumberOfFiles: '@maxNumberOfFiles',
44
+ disabled: '@disableValidation'
45
+ }
46
+ );
47
+
48
+ // The File Upload Validation plugin extends the fileupload widget
49
+ // with file validation functionality:
50
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
51
+
52
+ options: {
53
+ /*
54
+ // The regular expression for allowed file types, matches
55
+ // against either file type or file name:
56
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
57
+ // The maximum allowed file size in bytes:
58
+ maxFileSize: 10000000, // 10 MB
59
+ // The minimum allowed file size in bytes:
60
+ minFileSize: undefined, // No minimal file size
61
+ // The limit of files to be uploaded:
62
+ maxNumberOfFiles: 10,
63
+ */
64
+
65
+ // Function returning the current number of files,
66
+ // has to be overriden for maxNumberOfFiles validation:
67
+ getNumberOfFiles: $.noop,
68
+
69
+ // Error and info messages:
70
+ messages: {
71
+ maxNumberOfFiles: 'Maximum number of files exceeded',
72
+ acceptFileTypes: 'File type not allowed',
73
+ maxFileSize: 'File is too large',
74
+ minFileSize: 'File is too small'
75
+ }
76
+ },
77
+
78
+ processActions: {
79
+
80
+ validate: function (data, options) {
81
+ if (options.disabled) {
82
+ return data;
83
+ }
84
+ var dfd = $.Deferred(),
85
+ settings = this.options,
86
+ file = data.files[data.index],
87
+ numberOfFiles = settings.getNumberOfFiles();
88
+ if (numberOfFiles && $.type(options.maxNumberOfFiles) === 'number' &&
89
+ numberOfFiles + data.files.length > options.maxNumberOfFiles) {
90
+ file.error = settings.i18n('maxNumberOfFiles');
91
+ } else if (options.acceptFileTypes &&
92
+ !(options.acceptFileTypes.test(file.type) ||
93
+ options.acceptFileTypes.test(file.name))) {
94
+ file.error = settings.i18n('acceptFileTypes');
95
+ } else if (options.maxFileSize && file.size > options.maxFileSize) {
96
+ file.error = settings.i18n('maxFileSize');
97
+ } else if ($.type(file.size) === 'number' &&
98
+ file.size < options.minFileSize) {
99
+ file.error = settings.i18n('minFileSize');
100
+ } else {
101
+ delete file.error;
102
+ }
103
+ if (file.error || data.files.error) {
104
+ data.files.error = true;
105
+ dfd.rejectWith(this, [data]);
106
+ } else {
107
+ dfd.resolveWith(this, [data]);
108
+ }
109
+ return dfd.promise();
110
+ }
111
+
112
+ }
113
+
114
+ });
115
+
116
+ }));
@@ -1,5 +1,5 @@
1
1
  /*
2
- * jQuery File Upload Plugin 5.10.1
2
+ * jQuery File Upload Plugin 5.31.1
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';
@@ -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
@@ -109,6 +108,29 @@
109
108
  // global progress calculation. Set the following option to false to
110
109
  // prevent recalculating the global progress data:
111
110
  recalculateProgress: true,
111
+ // Interval in milliseconds to calculate and trigger progress events:
112
+ progressInterval: 100,
113
+ // Interval in milliseconds to calculate progress bitrate:
114
+ bitrateInterval: 500,
115
+ // By default, uploads are started automatically when adding files:
116
+ autoUpload: true,
117
+
118
+ // Error and info messages:
119
+ messages: {
120
+ uploadedBytes: 'Uploaded bytes exceed file size'
121
+ },
122
+
123
+ // Translation function, gets the message key to be translated
124
+ // and an object with context specific data as arguments:
125
+ i18n: function (message, context) {
126
+ message = this.messages[message] || message.toString();
127
+ if (context) {
128
+ $.each(context, function (key, value) {
129
+ message = message.replace('{' + key + '}', value);
130
+ });
131
+ }
132
+ return message;
133
+ },
112
134
 
113
135
  // Additional form data to be sent along with the file uploads can be set
114
136
  // using this option, which accepts an array of objects with name and
@@ -133,37 +155,67 @@
133
155
  // handlers using jQuery's Deferred callbacks:
134
156
  // data.submit().done(func).fail(func).always(func);
135
157
  add: function (e, data) {
136
- data.submit();
158
+ if (data.autoUpload || (data.autoUpload !== false &&
159
+ $(this).fileupload('option', 'autoUpload'))) {
160
+ data.process().done(function () {
161
+ data.submit();
162
+ });
163
+ }
137
164
  },
138
165
 
139
166
  // Other callbacks:
167
+
140
168
  // Callback for the submit event of each file upload:
141
169
  // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
170
+
142
171
  // Callback for the start of each file upload request:
143
172
  // send: function (e, data) {}, // .bind('fileuploadsend', func);
173
+
144
174
  // Callback for successful uploads:
145
175
  // done: function (e, data) {}, // .bind('fileuploaddone', func);
176
+
146
177
  // Callback for failed (abort or error) uploads:
147
178
  // fail: function (e, data) {}, // .bind('fileuploadfail', func);
179
+
148
180
  // Callback for completed (success, abort or error) requests:
149
181
  // always: function (e, data) {}, // .bind('fileuploadalways', func);
182
+
150
183
  // Callback for upload progress events:
151
184
  // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
185
+
152
186
  // Callback for global upload progress events:
153
187
  // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
188
+
154
189
  // Callback for uploads start, equivalent to the global ajaxStart event:
155
190
  // start: function (e) {}, // .bind('fileuploadstart', func);
191
+
156
192
  // Callback for uploads stop, equivalent to the global ajaxStop event:
157
193
  // stop: function (e) {}, // .bind('fileuploadstop', func);
158
- // Callback for change events of the fileInput collection:
194
+
195
+ // Callback for change events of the fileInput(s):
159
196
  // change: function (e, data) {}, // .bind('fileuploadchange', func);
160
- // Callback for paste events to the dropZone collection:
197
+
198
+ // Callback for paste events to the pasteZone(s):
161
199
  // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
162
- // Callback for drop events of the dropZone collection:
200
+
201
+ // Callback for drop events of the dropZone(s):
163
202
  // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
164
- // Callback for dragover events of the dropZone collection:
203
+
204
+ // Callback for dragover events of the dropZone(s):
165
205
  // dragover: function (e) {}, // .bind('fileuploaddragover', func);
166
206
 
207
+ // Callback for the start of each chunk upload request:
208
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
209
+
210
+ // Callback for successful chunk uploads:
211
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
212
+
213
+ // Callback for failed (abort or error) chunk uploads:
214
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
215
+
216
+ // Callback for completed (success, abort or error) chunk upload requests:
217
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
218
+
167
219
  // The plugin options are used as settings object for the ajax calls.
168
220
  // The following are jQuery ajax settings required for the file uploads:
169
221
  processData: false,
@@ -171,15 +223,31 @@
171
223
  cache: false
172
224
  },
173
225
 
174
- // A list of options that require a refresh after assigning a new value:
175
- _refreshOptionsList: [
176
- 'namespace',
177
- 'dropZone',
226
+ // A list of options that require reinitializing event listeners and/or
227
+ // special initialization code:
228
+ _specialOptions: [
178
229
  'fileInput',
230
+ 'dropZone',
231
+ 'pasteZone',
179
232
  'multipart',
180
233
  'forceIframeTransport'
181
234
  ],
182
235
 
236
+ _BitrateTimer: function () {
237
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
238
+ this.loaded = 0;
239
+ this.bitrate = 0;
240
+ this.getBitrate = function (now, loaded, interval) {
241
+ var timeDiff = now - this.timestamp;
242
+ if (!this.bitrate || !interval || timeDiff > interval) {
243
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
244
+ this.loaded = loaded;
245
+ this.timestamp = now;
246
+ }
247
+ return this.bitrate;
248
+ };
249
+ },
250
+
183
251
  _isXHRUpload: function (options) {
184
252
  return !options.forceIframeTransport &&
185
253
  ((!options.multipart && $.support.xhrFileUpload) ||
@@ -190,9 +258,11 @@
190
258
  var formData;
191
259
  if (typeof options.formData === 'function') {
192
260
  return options.formData(options.form);
193
- } else if ($.isArray(options.formData)) {
261
+ }
262
+ if ($.isArray(options.formData)) {
194
263
  return options.formData;
195
- } else if (options.formData) {
264
+ }
265
+ if ($.type(options.formData) === 'object') {
196
266
  formData = [];
197
267
  $.each(options.formData, function (name, value) {
198
268
  formData.push({name: name, value: value});
@@ -210,28 +280,66 @@
210
280
  return total;
211
281
  },
212
282
 
283
+ _initProgressObject: function (obj) {
284
+ var progress = {
285
+ loaded: 0,
286
+ total: 0,
287
+ bitrate: 0
288
+ };
289
+ if (obj._progress) {
290
+ $.extend(obj._progress, progress);
291
+ } else {
292
+ obj._progress = progress;
293
+ }
294
+ },
295
+
296
+ _initResponseObject: function (obj) {
297
+ var prop;
298
+ if (obj._response) {
299
+ for (prop in obj._response) {
300
+ if (obj._response.hasOwnProperty(prop)) {
301
+ delete obj._response[prop];
302
+ }
303
+ }
304
+ } else {
305
+ obj._response = {};
306
+ }
307
+ },
308
+
213
309
  _onProgress: function (e, data) {
214
310
  if (e.lengthComputable) {
215
- var total = data.total || this._getTotal(data.files),
216
- loaded = parseInt(
217
- e.loaded / e.total * (data.chunkSize || total),
218
- 10
219
- ) + (data.uploadedBytes || 0);
220
- this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
221
- data.lengthComputable = true;
222
- data.loaded = loaded;
223
- data.total = total;
311
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
312
+ loaded;
313
+ if (data._time && data.progressInterval &&
314
+ (now - data._time < data.progressInterval) &&
315
+ e.loaded !== e.total) {
316
+ return;
317
+ }
318
+ data._time = now;
319
+ loaded = Math.floor(
320
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
321
+ ) + (data.uploadedBytes || 0);
322
+ // Add the difference from the previously loaded state
323
+ // to the global loaded counter:
324
+ this._progress.loaded += (loaded - data._progress.loaded);
325
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
326
+ now,
327
+ this._progress.loaded,
328
+ data.bitrateInterval
329
+ );
330
+ data._progress.loaded = data.loaded = loaded;
331
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
332
+ now,
333
+ loaded,
334
+ data.bitrateInterval
335
+ );
224
336
  // Trigger a custom progress event with a total data property set
225
337
  // to the file size(s) of the current upload and a loaded data
226
338
  // property calculated accordingly:
227
339
  this._trigger('progress', e, data);
228
340
  // Trigger a global progress event for all current file uploads,
229
341
  // including ajax calls queued for sequential file uploads:
230
- this._trigger('progressall', e, {
231
- lengthComputable: true,
232
- loaded: this._loaded,
233
- total: this._total
234
- });
342
+ this._trigger('progressall', e, this._progress);
235
343
  }
236
344
  },
237
345
 
@@ -255,35 +363,28 @@
255
363
  }
256
364
  },
257
365
 
366
+ _isInstanceOf: function (type, obj) {
367
+ // Cross-frame instanceof check
368
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
369
+ },
370
+
258
371
  _initXHRData: function (options) {
259
- var formData,
372
+ var that = this,
373
+ formData,
260
374
  file = options.files[0],
261
375
  // Ignore non-multipart setting if not supported:
262
376
  multipart = options.multipart || !$.support.xhrFileUpload,
263
377
  paramName = options.paramName[0];
264
- if (!multipart || options.blob) {
265
- // For non-multipart uploads and chunked uploads,
266
- // file meta data is not part of the request body,
267
- // so we transmit this data as part of the HTTP headers.
268
- // For cross domain requests, these headers must be allowed
269
- // via Access-Control-Allow-Headers or removed using
270
- // the beforeSend callback:
271
- options.headers = $.extend(options.headers, {
272
- 'X-File-Name': file.name,
273
- 'X-File-Type': file.type,
274
- 'X-File-Size': file.size
275
- });
276
- if (!options.blob) {
277
- // Non-chunked non-multipart upload:
278
- options.contentType = file.type;
279
- options.data = file;
280
- } else if (!multipart) {
281
- // Chunked non-multipart upload:
282
- options.contentType = 'application/octet-stream';
283
- options.data = options.blob;
284
- }
378
+ options.headers = options.headers || {};
379
+ if (options.contentRange) {
380
+ options.headers['Content-Range'] = options.contentRange;
285
381
  }
286
- if (multipart && $.support.xhrFormDataFileUpload) {
382
+ if (!multipart) {
383
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
384
+ encodeURI(file.name) + '"';
385
+ options.contentType = file.type;
386
+ options.data = options.blob || file;
387
+ } else if ($.support.xhrFormDataFileUpload) {
287
388
  if (options.postMessage) {
288
389
  // window.postMessage does not allow sending FormData
289
390
  // objects, so we just add the File/Blob objects to
@@ -304,7 +405,7 @@
304
405
  });
305
406
  }
306
407
  } else {
307
- if (options.formData instanceof FormData) {
408
+ if (that._isInstanceOf('FormData', options.formData)) {
308
409
  formData = options.formData;
309
410
  } else {
310
411
  formData = new FormData();
@@ -313,13 +414,15 @@
313
414
  });
314
415
  }
315
416
  if (options.blob) {
417
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
418
+ encodeURI(file.name) + '"';
316
419
  formData.append(paramName, options.blob, file.name);
317
420
  } else {
318
421
  $.each(options.files, function (index, file) {
319
- // File objects are also Blob instances.
320
422
  // This check allows the tests to run with
321
423
  // dummy objects:
322
- if (file instanceof Blob) {
424
+ if (that._isInstanceOf('File', file) ||
425
+ that._isInstanceOf('Blob', file)) {
323
426
  formData.append(
324
427
  options.paramName[index] || paramName,
325
428
  file,
@@ -364,7 +467,7 @@
364
467
  options.dataType = 'postmessage ' + (options.dataType || '');
365
468
  }
366
469
  } else {
367
- this._initIframeSettings(options, 'iframe');
470
+ this._initIframeSettings(options);
368
471
  }
369
472
  },
370
473
 
@@ -396,6 +499,11 @@
396
499
  // associated form, if available:
397
500
  if (!options.form || !options.form.length) {
398
501
  options.form = $(options.fileInput.prop('form'));
502
+ // If the given file input doesn't have an associated form,
503
+ // use the default widget file input's form:
504
+ if (!options.form.length) {
505
+ options.form = $(this.options.fileInput.prop('form'));
506
+ }
399
507
  }
400
508
  options.paramName = this._getParamName(options);
401
509
  if (!options.url) {
@@ -404,9 +512,13 @@
404
512
  // The HTTP request method must be "POST" or "PUT":
405
513
  options.type = (options.type || options.form.prop('method') || '')
406
514
  .toUpperCase();
407
- if (options.type !== 'POST' && options.type !== 'PUT') {
515
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
516
+ options.type !== 'PATCH') {
408
517
  options.type = 'POST';
409
518
  }
519
+ if (!options.formAcceptCharset) {
520
+ options.formAcceptCharset = options.form.attr('accept-charset');
521
+ }
410
522
  },
411
523
 
412
524
  _getAJAXSettings: function (data) {
@@ -416,6 +528,21 @@
416
528
  return options;
417
529
  },
418
530
 
531
+ // jQuery 1.6 doesn't provide .state(),
532
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
533
+ _getDeferredState: function (deferred) {
534
+ if (deferred.state) {
535
+ return deferred.state();
536
+ }
537
+ if (deferred.isResolved()) {
538
+ return 'resolved';
539
+ }
540
+ if (deferred.isRejected()) {
541
+ return 'rejected';
542
+ }
543
+ return 'pending';
544
+ },
545
+
419
546
  // Maps jqXHR callbacks to the equivalent
420
547
  // methods of the given Promise object:
421
548
  _enhancePromise: function (promise) {
@@ -440,6 +567,60 @@
440
567
  return this._enhancePromise(promise);
441
568
  },
442
569
 
570
+ // Adds convenience methods to the data callback argument:
571
+ _addConvenienceMethods: function (e, data) {
572
+ var that = this,
573
+ getPromise = function (data) {
574
+ return $.Deferred().resolveWith(that, [data]).promise();
575
+ };
576
+ data.process = function (resolveFunc, rejectFunc) {
577
+ if (resolveFunc || rejectFunc) {
578
+ data._processQueue = this._processQueue =
579
+ (this._processQueue || getPromise(this))
580
+ .pipe(resolveFunc, rejectFunc);
581
+ }
582
+ return this._processQueue || getPromise(this);
583
+ };
584
+ data.submit = function () {
585
+ if (this.state() !== 'pending') {
586
+ data.jqXHR = this.jqXHR =
587
+ (that._trigger('submit', e, this) !== false) &&
588
+ that._onSend(e, this);
589
+ }
590
+ return this.jqXHR || that._getXHRPromise();
591
+ };
592
+ data.abort = function () {
593
+ if (this.jqXHR) {
594
+ return this.jqXHR.abort();
595
+ }
596
+ return that._getXHRPromise();
597
+ };
598
+ data.state = function () {
599
+ if (this.jqXHR) {
600
+ return that._getDeferredState(this.jqXHR);
601
+ }
602
+ if (this._processQueue) {
603
+ return that._getDeferredState(this._processQueue);
604
+ }
605
+ };
606
+ data.progress = function () {
607
+ return this._progress;
608
+ };
609
+ data.response = function () {
610
+ return this._response;
611
+ };
612
+ },
613
+
614
+ // Parses the Range header from the server response
615
+ // and returns the uploaded bytes:
616
+ _getUploadedBytes: function (jqXHR) {
617
+ var range = jqXHR.getResponseHeader('Range'),
618
+ parts = range && range.split('-'),
619
+ upperBytesPos = parts && parts.length > 1 &&
620
+ parseInt(parts[1], 10);
621
+ return upperBytesPos && upperBytesPos + 1;
622
+ },
623
+
443
624
  // Uploads a file in multiple, sequential requests
444
625
  // by splitting the file up in multiple blob chunks.
445
626
  // If the second parameter is true, only tests if the file
@@ -451,13 +632,11 @@
451
632
  fs = file.size,
452
633
  ub = options.uploadedBytes = options.uploadedBytes || 0,
453
634
  mcs = options.maxChunkSize || fs,
454
- // Use the Blob methods with the slice implementation
455
- // according to the W3C Blob API specification:
456
- slice = file.webkitSlice || file.mozSlice || file.slice,
457
- upload,
458
- n,
635
+ slice = file.slice || file.webkitSlice || file.mozSlice,
636
+ dfd = $.Deferred(),
637
+ promise = dfd.promise(),
459
638
  jqXHR,
460
- pipe;
639
+ upload;
461
640
  if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
462
641
  options.data) {
463
642
  return false;
@@ -466,62 +645,84 @@
466
645
  return true;
467
646
  }
468
647
  if (ub >= fs) {
469
- file.error = 'uploadedBytes';
648
+ file.error = options.i18n('uploadedBytes');
470
649
  return this._getXHRPromise(
471
650
  false,
472
651
  options.context,
473
652
  [null, 'error', file.error]
474
653
  );
475
654
  }
476
- // n is the number of blobs to upload,
477
- // calculated via filesize, uploaded bytes and max chunk size:
478
- n = Math.ceil((fs - ub) / mcs);
479
- // The chunk upload method accepting the chunk number as parameter:
480
- upload = function (i) {
481
- if (!i) {
482
- return that._getXHRPromise(true, options.context);
483
- }
484
- // Upload the blobs in sequential order:
485
- return upload(i -= 1).pipe(function () {
486
- // Clone the options object for each chunk upload:
487
- var o = $.extend({}, options);
488
- o.blob = slice.call(
489
- file,
490
- ub + i * mcs,
491
- ub + (i + 1) * mcs
492
- );
493
- // Store the current chunk size, as the blob itself
494
- // will be dereferenced after data processing:
495
- o.chunkSize = o.blob.size;
496
- // Process the upload data (the blob and potential form data):
497
- that._initXHRData(o);
498
- // Add progress listeners for this chunk upload:
499
- that._initProgressListener(o);
500
- jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
501
- .done(function () {
502
- // Create a progress event if upload is done and
503
- // no progress event has been invoked for this chunk:
504
- if (!o.loaded) {
505
- that._onProgress($.Event('progress', {
506
- lengthComputable: true,
507
- loaded: o.chunkSize,
508
- total: o.chunkSize
509
- }), o);
510
- }
511
- options.uploadedBytes = o.uploadedBytes +=
512
- o.chunkSize;
513
- });
514
- return jqXHR;
515
- });
655
+ // The chunk upload method:
656
+ upload = function () {
657
+ // Clone the options object for each chunk upload:
658
+ var o = $.extend({}, options),
659
+ currentLoaded = o._progress.loaded;
660
+ o.blob = slice.call(
661
+ file,
662
+ ub,
663
+ ub + mcs,
664
+ file.type
665
+ );
666
+ // Store the current chunk size, as the blob itself
667
+ // will be dereferenced after data processing:
668
+ o.chunkSize = o.blob.size;
669
+ // Expose the chunk bytes position range:
670
+ o.contentRange = 'bytes ' + ub + '-' +
671
+ (ub + o.chunkSize - 1) + '/' + fs;
672
+ // Process the upload data (the blob and potential form data):
673
+ that._initXHRData(o);
674
+ // Add progress listeners for this chunk upload:
675
+ that._initProgressListener(o);
676
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
677
+ that._getXHRPromise(false, o.context))
678
+ .done(function (result, textStatus, jqXHR) {
679
+ ub = that._getUploadedBytes(jqXHR) ||
680
+ (ub + o.chunkSize);
681
+ // Create a progress event if no final progress event
682
+ // with loaded equaling total has been triggered
683
+ // for this chunk:
684
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
685
+ that._onProgress($.Event('progress', {
686
+ lengthComputable: true,
687
+ loaded: ub - o.uploadedBytes,
688
+ total: ub - o.uploadedBytes
689
+ }), o);
690
+ }
691
+ options.uploadedBytes = o.uploadedBytes = ub;
692
+ o.result = result;
693
+ o.textStatus = textStatus;
694
+ o.jqXHR = jqXHR;
695
+ that._trigger('chunkdone', null, o);
696
+ that._trigger('chunkalways', null, o);
697
+ if (ub < fs) {
698
+ // File upload not yet complete,
699
+ // continue with the next chunk:
700
+ upload();
701
+ } else {
702
+ dfd.resolveWith(
703
+ o.context,
704
+ [result, textStatus, jqXHR]
705
+ );
706
+ }
707
+ })
708
+ .fail(function (jqXHR, textStatus, errorThrown) {
709
+ o.jqXHR = jqXHR;
710
+ o.textStatus = textStatus;
711
+ o.errorThrown = errorThrown;
712
+ that._trigger('chunkfail', null, o);
713
+ that._trigger('chunkalways', null, o);
714
+ dfd.rejectWith(
715
+ o.context,
716
+ [jqXHR, textStatus, errorThrown]
717
+ );
718
+ });
516
719
  };
517
- // Return the piped Promise object, enhanced with an abort method,
518
- // which is delegated to the jqXHR object of the current upload,
519
- // and jqXHR callbacks mapped to the equivalent Promise methods:
520
- pipe = upload(n);
521
- pipe.abort = function () {
720
+ this._enhancePromise(promise);
721
+ promise.abort = function () {
522
722
  return jqXHR.abort();
523
723
  };
524
- return this._enhancePromise(pipe);
724
+ upload();
725
+ return promise;
525
726
  },
526
727
 
527
728
  _beforeSend: function (e, data) {
@@ -530,99 +731,113 @@
530
731
  // and no other uploads are currently running,
531
732
  // equivalent to the global ajaxStart event:
532
733
  this._trigger('start');
734
+ // Set timer for global bitrate progress calculation:
735
+ this._bitrateTimer = new this._BitrateTimer();
736
+ // Reset the global progress values:
737
+ this._progress.loaded = this._progress.total = 0;
738
+ this._progress.bitrate = 0;
533
739
  }
740
+ // Make sure the container objects for the .response() and
741
+ // .progress() methods on the data object are available
742
+ // and reset to their initial state:
743
+ this._initResponseObject(data);
744
+ this._initProgressObject(data);
745
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
746
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
747
+ data._progress.bitrate = data.bitrate = 0;
534
748
  this._active += 1;
535
749
  // Initialize the global progress values:
536
- this._loaded += data.uploadedBytes || 0;
537
- this._total += this._getTotal(data.files);
750
+ this._progress.loaded += data.loaded;
751
+ this._progress.total += data.total;
538
752
  },
539
753
 
540
754
  _onDone: function (result, textStatus, jqXHR, options) {
541
- if (!this._isXHRUpload(options)) {
542
- // Create a progress event for each iframe load:
755
+ var total = options._progress.total,
756
+ response = options._response;
757
+ if (options._progress.loaded < total) {
758
+ // Create a progress event if no final progress event
759
+ // with loaded equaling total has been triggered:
543
760
  this._onProgress($.Event('progress', {
544
761
  lengthComputable: true,
545
- loaded: 1,
546
- total: 1
762
+ loaded: total,
763
+ total: total
547
764
  }), options);
548
765
  }
549
- options.result = result;
550
- options.textStatus = textStatus;
551
- options.jqXHR = jqXHR;
766
+ response.result = options.result = result;
767
+ response.textStatus = options.textStatus = textStatus;
768
+ response.jqXHR = options.jqXHR = jqXHR;
552
769
  this._trigger('done', null, options);
553
770
  },
554
771
 
555
772
  _onFail: function (jqXHR, textStatus, errorThrown, options) {
556
- options.jqXHR = jqXHR;
557
- options.textStatus = textStatus;
558
- options.errorThrown = errorThrown;
559
- this._trigger('fail', null, options);
773
+ var response = options._response;
560
774
  if (options.recalculateProgress) {
561
775
  // Remove the failed (error or abort) file upload from
562
776
  // the global progress calculation:
563
- this._loaded -= options.loaded || options.uploadedBytes || 0;
564
- this._total -= options.total || this._getTotal(options.files);
777
+ this._progress.loaded -= options._progress.loaded;
778
+ this._progress.total -= options._progress.total;
565
779
  }
780
+ response.jqXHR = options.jqXHR = jqXHR;
781
+ response.textStatus = options.textStatus = textStatus;
782
+ response.errorThrown = options.errorThrown = errorThrown;
783
+ this._trigger('fail', null, options);
566
784
  },
567
785
 
568
786
  _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
569
- this._active -= 1;
570
- options.textStatus = textStatus;
571
- if (jqXHRorError && jqXHRorError.always) {
572
- options.jqXHR = jqXHRorError;
573
- options.result = jqXHRorResult;
574
- } else {
575
- options.jqXHR = jqXHRorResult;
576
- options.errorThrown = jqXHRorError;
577
- }
787
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
788
+ // options object via done and fail callbacks
578
789
  this._trigger('always', null, options);
579
- if (this._active === 0) {
580
- // The stop callback is triggered when all uploads have
581
- // been completed, equivalent to the global ajaxStop event:
582
- this._trigger('stop');
583
- // Reset the global progress values:
584
- this._loaded = this._total = 0;
585
- }
586
790
  },
587
791
 
588
792
  _onSend: function (e, data) {
793
+ if (!data.submit) {
794
+ this._addConvenienceMethods(e, data);
795
+ }
589
796
  var that = this,
590
797
  jqXHR,
798
+ aborted,
591
799
  slot,
592
800
  pipe,
593
801
  options = that._getAJAXSettings(data),
594
- send = function (resolve, args) {
802
+ send = function () {
595
803
  that._sending += 1;
804
+ // Set timer for bitrate progress calculation:
805
+ options._bitrateTimer = new that._BitrateTimer();
596
806
  jqXHR = jqXHR || (
597
- (resolve !== false &&
598
- that._trigger('send', e, options) !== false &&
599
- (that._chunkedUpload(options) || $.ajax(options))) ||
600
- that._getXHRPromise(false, options.context, args)
807
+ ((aborted || that._trigger('send', e, options) === false) &&
808
+ that._getXHRPromise(false, options.context, aborted)) ||
809
+ that._chunkedUpload(options) || $.ajax(options)
601
810
  ).done(function (result, textStatus, jqXHR) {
602
811
  that._onDone(result, textStatus, jqXHR, options);
603
812
  }).fail(function (jqXHR, textStatus, errorThrown) {
604
813
  that._onFail(jqXHR, textStatus, errorThrown, options);
605
814
  }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
606
- that._sending -= 1;
607
815
  that._onAlways(
608
816
  jqXHRorResult,
609
817
  textStatus,
610
818
  jqXHRorError,
611
819
  options
612
820
  );
821
+ that._sending -= 1;
822
+ that._active -= 1;
613
823
  if (options.limitConcurrentUploads &&
614
824
  options.limitConcurrentUploads > that._sending) {
615
825
  // Start the next queued upload,
616
826
  // that has not been aborted:
617
827
  var nextSlot = that._slots.shift();
618
828
  while (nextSlot) {
619
- if (!nextSlot.isRejected()) {
829
+ if (that._getDeferredState(nextSlot) === 'pending') {
620
830
  nextSlot.resolve();
621
831
  break;
622
832
  }
623
833
  nextSlot = that._slots.shift();
624
834
  }
625
835
  }
836
+ if (that._active === 0) {
837
+ // The stop callback is triggered when all uploads have
838
+ // been completed, equivalent to the global ajaxStop event:
839
+ that._trigger('stop');
840
+ }
626
841
  });
627
842
  return jqXHR;
628
843
  };
@@ -641,12 +856,12 @@
641
856
  // which is delegated to the jqXHR object of the current upload,
642
857
  // and jqXHR callbacks mapped to the equivalent Promise methods:
643
858
  pipe.abort = function () {
644
- var args = [undefined, 'abort', 'abort'];
859
+ aborted = [undefined, 'abort', 'abort'];
645
860
  if (!jqXHR) {
646
861
  if (slot) {
647
- slot.rejectWith(args);
862
+ slot.rejectWith(options.context, aborted);
648
863
  }
649
- return send(false, args);
864
+ return send();
650
865
  }
651
866
  return jqXHR.abort();
652
867
  };
@@ -688,25 +903,15 @@
688
903
  var newData = $.extend({}, data);
689
904
  newData.files = fileSet ? element : [element];
690
905
  newData.paramName = paramNameSet[index];
691
- newData.submit = function () {
692
- newData.jqXHR = this.jqXHR =
693
- (that._trigger('submit', e, this) !== false) &&
694
- that._onSend(e, this);
695
- return this.jqXHR;
696
- };
697
- return (result = that._trigger('add', e, newData));
906
+ that._initResponseObject(newData);
907
+ that._initProgressObject(newData);
908
+ that._addConvenienceMethods(e, newData);
909
+ result = that._trigger('add', e, newData);
910
+ return result;
698
911
  });
699
912
  return result;
700
913
  },
701
914
 
702
- // File Normalization for Gecko 1.9.1 (Firefox 3.5) support:
703
- _normalizeFile: function (index, file) {
704
- if (file.name === undefined && file.size === undefined) {
705
- file.name = file.fileName;
706
- file.size = file.fileSize;
707
- }
708
- },
709
-
710
915
  _replaceFileInput: function (input) {
711
916
  var inputClone = input.clone(true);
712
917
  $('<form></form>').append(inputClone)[0].reset();
@@ -716,7 +921,7 @@
716
921
  // Avoid memory leaks with the detached file input:
717
922
  $.cleanData(input.unbind('remove'));
718
923
  // Replace the original file input element in the fileInput
719
- // collection with the clone, which has been copied including
924
+ // elements set with the clone, which has been copied including
720
925
  // event handlers:
721
926
  this.options.fileInput = this.options.fileInput.map(function (i, el) {
722
927
  if (el === input[0]) {
@@ -731,102 +936,227 @@
731
936
  }
732
937
  },
733
938
 
734
- _onChange: function (e) {
735
- var that = e.data.fileupload,
736
- data = {
737
- files: $.each($.makeArray(e.target.files), that._normalizeFile),
738
- fileInput: $(e.target),
739
- form: $(e.target.form)
740
- };
741
- if (!data.files.length) {
939
+ _handleFileTreeEntry: function (entry, path) {
940
+ var that = this,
941
+ dfd = $.Deferred(),
942
+ errorHandler = function (e) {
943
+ if (e && !e.entry) {
944
+ e.entry = entry;
945
+ }
946
+ // Since $.when returns immediately if one
947
+ // Deferred is rejected, we use resolve instead.
948
+ // This allows valid files and invalid items
949
+ // to be returned together in one set:
950
+ dfd.resolve([e]);
951
+ },
952
+ dirReader;
953
+ path = path || '';
954
+ if (entry.isFile) {
955
+ if (entry._file) {
956
+ // Workaround for Chrome bug #149735
957
+ entry._file.relativePath = path;
958
+ dfd.resolve(entry._file);
959
+ } else {
960
+ entry.file(function (file) {
961
+ file.relativePath = path;
962
+ dfd.resolve(file);
963
+ }, errorHandler);
964
+ }
965
+ } else if (entry.isDirectory) {
966
+ dirReader = entry.createReader();
967
+ dirReader.readEntries(function (entries) {
968
+ that._handleFileTreeEntries(
969
+ entries,
970
+ path + entry.name + '/'
971
+ ).done(function (files) {
972
+ dfd.resolve(files);
973
+ }).fail(errorHandler);
974
+ }, errorHandler);
975
+ } else {
976
+ // Return an empy list for file system items
977
+ // other than files or directories:
978
+ dfd.resolve([]);
979
+ }
980
+ return dfd.promise();
981
+ },
982
+
983
+ _handleFileTreeEntries: function (entries, path) {
984
+ var that = this;
985
+ return $.when.apply(
986
+ $,
987
+ $.map(entries, function (entry) {
988
+ return that._handleFileTreeEntry(entry, path);
989
+ })
990
+ ).pipe(function () {
991
+ return Array.prototype.concat.apply(
992
+ [],
993
+ arguments
994
+ );
995
+ });
996
+ },
997
+
998
+ _getDroppedFiles: function (dataTransfer) {
999
+ dataTransfer = dataTransfer || {};
1000
+ var items = dataTransfer.items;
1001
+ if (items && items.length && (items[0].webkitGetAsEntry ||
1002
+ items[0].getAsEntry)) {
1003
+ return this._handleFileTreeEntries(
1004
+ $.map(items, function (item) {
1005
+ var entry;
1006
+ if (item.webkitGetAsEntry) {
1007
+ entry = item.webkitGetAsEntry();
1008
+ if (entry) {
1009
+ // Workaround for Chrome bug #149735:
1010
+ entry._file = item.getAsFile();
1011
+ }
1012
+ return entry;
1013
+ }
1014
+ return item.getAsEntry();
1015
+ })
1016
+ );
1017
+ }
1018
+ return $.Deferred().resolve(
1019
+ $.makeArray(dataTransfer.files)
1020
+ ).promise();
1021
+ },
1022
+
1023
+ _getSingleFileInputFiles: function (fileInput) {
1024
+ fileInput = $(fileInput);
1025
+ var entries = fileInput.prop('webkitEntries') ||
1026
+ fileInput.prop('entries'),
1027
+ files,
1028
+ value;
1029
+ if (entries && entries.length) {
1030
+ return this._handleFileTreeEntries(entries);
1031
+ }
1032
+ files = $.makeArray(fileInput.prop('files'));
1033
+ if (!files.length) {
1034
+ value = fileInput.prop('value');
1035
+ if (!value) {
1036
+ return $.Deferred().resolve([]).promise();
1037
+ }
742
1038
  // If the files property is not available, the browser does not
743
1039
  // support the File API and we add a pseudo File object with
744
1040
  // the input value as name with path information removed:
745
- data.files = [{name: e.target.value.replace(/^.*\\/, '')}];
746
- }
747
- if (that.options.replaceFileInput) {
748
- that._replaceFileInput(data.fileInput);
1041
+ files = [{name: value.replace(/^.*\\/, '')}];
1042
+ } else if (files[0].name === undefined && files[0].fileName) {
1043
+ // File normalization for Safari 4 and Firefox 3:
1044
+ $.each(files, function (index, file) {
1045
+ file.name = file.fileName;
1046
+ file.size = file.fileSize;
1047
+ });
749
1048
  }
750
- if (that._trigger('change', e, data) === false ||
751
- that._onAdd(e, data) === false) {
752
- return false;
1049
+ return $.Deferred().resolve(files).promise();
1050
+ },
1051
+
1052
+ _getFileInputFiles: function (fileInput) {
1053
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
1054
+ return this._getSingleFileInputFiles(fileInput);
753
1055
  }
1056
+ return $.when.apply(
1057
+ $,
1058
+ $.map(fileInput, this._getSingleFileInputFiles)
1059
+ ).pipe(function () {
1060
+ return Array.prototype.concat.apply(
1061
+ [],
1062
+ arguments
1063
+ );
1064
+ });
1065
+ },
1066
+
1067
+ _onChange: function (e) {
1068
+ var that = this,
1069
+ data = {
1070
+ fileInput: $(e.target),
1071
+ form: $(e.target.form)
1072
+ };
1073
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1074
+ data.files = files;
1075
+ if (that.options.replaceFileInput) {
1076
+ that._replaceFileInput(data.fileInput);
1077
+ }
1078
+ if (that._trigger('change', e, data) !== false) {
1079
+ that._onAdd(e, data);
1080
+ }
1081
+ });
754
1082
  },
755
1083
 
756
1084
  _onPaste: function (e) {
757
- var that = e.data.fileupload,
758
- cbd = e.originalEvent.clipboardData,
759
- items = (cbd && cbd.items) || [],
1085
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
1086
+ e.originalEvent.clipboardData.items,
760
1087
  data = {files: []};
761
- $.each(items, function (index, item) {
762
- var file = item.getAsFile && item.getAsFile();
763
- if (file) {
764
- data.files.push(file);
1088
+ if (items && items.length) {
1089
+ $.each(items, function (index, item) {
1090
+ var file = item.getAsFile && item.getAsFile();
1091
+ if (file) {
1092
+ data.files.push(file);
1093
+ }
1094
+ });
1095
+ if (this._trigger('paste', e, data) === false ||
1096
+ this._onAdd(e, data) === false) {
1097
+ return false;
765
1098
  }
766
- });
767
- if (that._trigger('paste', e, data) === false ||
768
- that._onAdd(e, data) === false) {
769
- return false;
770
1099
  }
771
1100
  },
772
1101
 
773
1102
  _onDrop: function (e) {
774
- var that = e.data.fileupload,
775
- dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
776
- data = {
777
- files: $.each(
778
- $.makeArray(dataTransfer && dataTransfer.files),
779
- that._normalizeFile
780
- )
781
- };
782
- if (that._trigger('drop', e, data) === false ||
783
- that._onAdd(e, data) === false) {
784
- return false;
1103
+ var that = this,
1104
+ dataTransfer = e.dataTransfer = e.originalEvent &&
1105
+ e.originalEvent.dataTransfer,
1106
+ data = {};
1107
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1108
+ e.preventDefault();
1109
+ this._getDroppedFiles(dataTransfer).always(function (files) {
1110
+ data.files = files;
1111
+ if (that._trigger('drop', e, data) !== false) {
1112
+ that._onAdd(e, data);
1113
+ }
1114
+ });
785
1115
  }
786
- e.preventDefault();
787
1116
  },
788
1117
 
789
1118
  _onDragOver: function (e) {
790
- var that = e.data.fileupload,
791
- dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
792
- if (that._trigger('dragover', e) === false) {
793
- return false;
794
- }
1119
+ var dataTransfer = e.dataTransfer = e.originalEvent &&
1120
+ e.originalEvent.dataTransfer;
795
1121
  if (dataTransfer) {
796
- dataTransfer.dropEffect = dataTransfer.effectAllowed = 'copy';
1122
+ if (this._trigger('dragover', e) === false) {
1123
+ return false;
1124
+ }
1125
+ if ($.inArray('Files', dataTransfer.types) !== -1) {
1126
+ dataTransfer.dropEffect = 'copy';
1127
+ e.preventDefault();
1128
+ }
797
1129
  }
798
- e.preventDefault();
799
1130
  },
800
1131
 
801
1132
  _initEventHandlers: function () {
802
- var ns = this.options.namespace;
803
1133
  if (this._isXHRUpload(this.options)) {
804
- this.options.dropZone
805
- .bind('dragover.' + ns, {fileupload: this}, this._onDragOver)
806
- .bind('drop.' + ns, {fileupload: this}, this._onDrop)
807
- .bind('paste.' + ns, {fileupload: this}, this._onPaste);
1134
+ this._on(this.options.dropZone, {
1135
+ dragover: this._onDragOver,
1136
+ drop: this._onDrop
1137
+ });
1138
+ this._on(this.options.pasteZone, {
1139
+ paste: this._onPaste
1140
+ });
808
1141
  }
809
- this.options.fileInput
810
- .bind('change.' + ns, {fileupload: this}, this._onChange);
1142
+ this._on(this.options.fileInput, {
1143
+ change: this._onChange
1144
+ });
811
1145
  },
812
1146
 
813
1147
  _destroyEventHandlers: function () {
814
- var ns = this.options.namespace;
815
- this.options.dropZone
816
- .unbind('dragover.' + ns, this._onDragOver)
817
- .unbind('drop.' + ns, this._onDrop)
818
- .unbind('paste.' + ns, this._onPaste);
819
- this.options.fileInput
820
- .unbind('change.' + ns, this._onChange);
1148
+ this._off(this.options.dropZone, 'dragover drop');
1149
+ this._off(this.options.pasteZone, 'paste');
1150
+ this._off(this.options.fileInput, 'change');
821
1151
  },
822
1152
 
823
1153
  _setOption: function (key, value) {
824
- var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
825
- if (refresh) {
1154
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
1155
+ if (reinit) {
826
1156
  this._destroyEventHandlers();
827
1157
  }
828
- $.Widget.prototype._setOption.call(this, key, value);
829
- if (refresh) {
1158
+ this._super(key, value);
1159
+ if (reinit) {
830
1160
  this._initSpecialOptions();
831
1161
  this._initEventHandlers();
832
1162
  }
@@ -835,41 +1165,68 @@
835
1165
  _initSpecialOptions: function () {
836
1166
  var options = this.options;
837
1167
  if (options.fileInput === undefined) {
838
- options.fileInput = this.element.is('input:file') ?
839
- this.element : this.element.find('input:file');
1168
+ options.fileInput = this.element.is('input[type="file"]') ?
1169
+ this.element : this.element.find('input[type="file"]');
840
1170
  } else if (!(options.fileInput instanceof $)) {
841
1171
  options.fileInput = $(options.fileInput);
842
1172
  }
843
1173
  if (!(options.dropZone instanceof $)) {
844
1174
  options.dropZone = $(options.dropZone);
845
1175
  }
1176
+ if (!(options.pasteZone instanceof $)) {
1177
+ options.pasteZone = $(options.pasteZone);
1178
+ }
846
1179
  },
847
1180
 
848
- _create: function () {
849
- var options = this.options;
1181
+ _getRegExp: function (str) {
1182
+ var parts = str.split('/'),
1183
+ modifiers = parts.pop();
1184
+ parts.shift();
1185
+ return new RegExp(parts.join('/'), modifiers);
1186
+ },
1187
+
1188
+ _isRegExpOption: function (key, value) {
1189
+ return key !== 'url' && $.type(value) === 'string' &&
1190
+ /^\/.*\/[igm]{0,3}$/.test(value);
1191
+ },
1192
+
1193
+ _initDataAttributes: function () {
1194
+ var that = this,
1195
+ options = this.options;
850
1196
  // Initialize options set via HTML5 data-attributes:
851
- $.extend(options, $(this.element[0].cloneNode(false)).data());
852
- options.namespace = options.namespace || this.widgetName;
1197
+ $.each(
1198
+ $(this.element[0].cloneNode(false)).data(),
1199
+ function (key, value) {
1200
+ if (that._isRegExpOption(key, value)) {
1201
+ value = that._getRegExp(value);
1202
+ }
1203
+ options[key] = value;
1204
+ }
1205
+ );
1206
+ },
1207
+
1208
+ _create: function () {
1209
+ this._initDataAttributes();
853
1210
  this._initSpecialOptions();
854
1211
  this._slots = [];
855
1212
  this._sequence = this._getXHRPromise(true);
856
- this._sending = this._active = this._loaded = this._total = 0;
1213
+ this._sending = this._active = 0;
1214
+ this._initProgressObject(this);
857
1215
  this._initEventHandlers();
858
1216
  },
859
1217
 
860
- destroy: function () {
861
- this._destroyEventHandlers();
862
- $.Widget.prototype.destroy.call(this);
863
- },
864
-
865
- enable: function () {
866
- $.Widget.prototype.enable.call(this);
867
- this._initEventHandlers();
1218
+ // This method is exposed to the widget API and allows to query
1219
+ // the number of active uploads:
1220
+ active: function () {
1221
+ return this._active;
868
1222
  },
869
1223
 
870
- disable: function () {
871
- this._destroyEventHandlers();
872
- $.Widget.prototype.disable.call(this);
1224
+ // This method is exposed to the widget API and allows to query
1225
+ // the widget upload progress.
1226
+ // It returns an object with loaded, total and bitrate properties
1227
+ // for the running uploads:
1228
+ progress: function () {
1229
+ return this._progress;
873
1230
  },
874
1231
 
875
1232
  // This method is exposed to the widget API and allows adding files
@@ -877,21 +1234,61 @@
877
1234
  // must have a files property and can contain additional options:
878
1235
  // .fileupload('add', {files: filesList});
879
1236
  add: function (data) {
1237
+ var that = this;
880
1238
  if (!data || this.options.disabled) {
881
1239
  return;
882
1240
  }
883
- data.files = $.each($.makeArray(data.files), this._normalizeFile);
884
- this._onAdd(null, data);
1241
+ if (data.fileInput && !data.files) {
1242
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1243
+ data.files = files;
1244
+ that._onAdd(null, data);
1245
+ });
1246
+ } else {
1247
+ data.files = $.makeArray(data.files);
1248
+ this._onAdd(null, data);
1249
+ }
885
1250
  },
886
1251
 
887
1252
  // This method is exposed to the widget API and allows sending files
888
1253
  // using the fileupload API. The data parameter accepts an object which
889
- // must have a files property and can contain additional options:
1254
+ // must have a files or fileInput property and can contain additional options:
890
1255
  // .fileupload('send', {files: filesList});
891
1256
  // The method returns a Promise object for the file upload call.
892
1257
  send: function (data) {
893
1258
  if (data && !this.options.disabled) {
894
- data.files = $.each($.makeArray(data.files), this._normalizeFile);
1259
+ if (data.fileInput && !data.files) {
1260
+ var that = this,
1261
+ dfd = $.Deferred(),
1262
+ promise = dfd.promise(),
1263
+ jqXHR,
1264
+ aborted;
1265
+ promise.abort = function () {
1266
+ aborted = true;
1267
+ if (jqXHR) {
1268
+ return jqXHR.abort();
1269
+ }
1270
+ dfd.reject(null, 'abort', 'abort');
1271
+ return promise;
1272
+ };
1273
+ this._getFileInputFiles(data.fileInput).always(
1274
+ function (files) {
1275
+ if (aborted) {
1276
+ return;
1277
+ }
1278
+ data.files = files;
1279
+ jqXHR = that._onSend(null, data).then(
1280
+ function (result, textStatus, jqXHR) {
1281
+ dfd.resolve(result, textStatus, jqXHR);
1282
+ },
1283
+ function (jqXHR, textStatus, errorThrown) {
1284
+ dfd.reject(jqXHR, textStatus, errorThrown);
1285
+ }
1286
+ );
1287
+ }
1288
+ );
1289
+ return this._enhancePromise(promise);
1290
+ }
1291
+ data.files = $.makeArray(data.files);
895
1292
  if (data.files.length) {
896
1293
  return this._onSend(null, data);
897
1294
  }