avatars_for_rails 0.2.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/README.rdoc +20 -71
  4. data/app/assets/javascripts/avatars_for_rails.js +67 -11
  5. data/app/assets/stylesheets/avatars_for_rails.css +0 -117
  6. data/app/controllers/avatars_controller.rb +23 -82
  7. data/app/views/avatars/_crop.html.erb +10 -0
  8. data/app/views/avatars/_form.html.erb +8 -80
  9. data/avatars_for_rails.gemspec +10 -12
  10. data/config/locales/en.yml +1 -0
  11. data/config/locales/es.yml +1 -0
  12. data/config/routes.rb +1 -4
  13. data/lib/avatars_for_rails/active_record.rb +13 -0
  14. data/lib/avatars_for_rails/avatarable.rb +86 -0
  15. data/lib/avatars_for_rails/engine.rb +13 -0
  16. data/lib/avatars_for_rails/version.rb +3 -0
  17. data/lib/avatars_for_rails.rb +24 -23
  18. data/vendor/assets/javascripts/jquery.Jcrop.js +1694 -0
  19. data/vendor/assets/javascripts/jquery.fileupload-ui.js +747 -469
  20. data/vendor/assets/javascripts/jquery.fileupload.js +1060 -867
  21. data/vendor/assets/stylesheets/jquery.Jcrop.css +161 -31
  22. metadata +98 -63
  23. data/app/models/avatar.rb +0 -149
  24. data/app/views/avatars/_errors.html.erb +0 -5
  25. data/app/views/avatars/_list.html.erb +0 -30
  26. data/app/views/avatars/_new.html.erb +0 -7
  27. data/app/views/avatars/_precrop.html.erb +0 -38
  28. data/app/views/avatars/destroy.js.erb +0 -2
  29. data/app/views/avatars/edit.html.erb +0 -6
  30. data/app/views/avatars/index.html.erb +0 -2
  31. data/app/views/avatars/new.html.erb +0 -1
  32. data/app/views/avatars/show.html.erb +0 -5
  33. data/app/views/avatars/update.js.erb +0 -9
  34. data/lib/avatars_for_rails/avatars_controller_config.rb +0 -4
  35. data/vendor/assets/javascripts/jquery.Jcrop.min.js +0 -163
@@ -1,956 +1,1149 @@
1
1
  /*
2
- * jQuery File Upload Plugin 4.4.2
2
+ * jQuery File Upload Plugin 5.21.3
3
3
  * https://github.com/blueimp/jQuery-File-Upload
4
4
  *
5
5
  * Copyright 2010, Sebastian Tschan
6
6
  * https://blueimp.net
7
7
  *
8
8
  * Licensed under the MIT license:
9
- * http://creativecommons.org/licenses/MIT/
9
+ * http://www.opensource.org/licenses/MIT
10
10
  */
11
11
 
12
- /*jslint browser: true */
13
- /*global XMLHttpRequestUpload, File, FileReader, FormData, ProgressEvent, unescape, jQuery, upload */
12
+ /*jslint nomen: true, unparam: true, regexp: true */
13
+ /*global define, window, document, File, Blob, FormData, location */
14
14
 
15
- (function ($) {
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.ui.widget'
22
+ ], factory);
23
+ } else {
24
+ // Browser globals:
25
+ factory(window.jQuery);
26
+ }
27
+ }(function ($) {
16
28
  'use strict';
17
29
 
18
- var defaultNamespace = 'file_upload',
19
- undef = 'undefined',
20
- func = 'function',
21
- FileUpload,
22
- methods,
23
-
24
- MultiLoader = function (callBack, numOrList) {
25
- var loaded = 0,
26
- list = [];
27
- if (numOrList) {
28
- if (numOrList.length) {
29
- list = numOrList;
30
- } else {
31
- list[numOrList - 1] = null;
32
- }
33
- }
34
- this.complete = function () {
35
- loaded += 1;
36
- if (loaded === list.length) {
37
- callBack(list);
38
- loaded = 0;
39
- list = [];
40
- }
41
- };
42
- this.push = function (item) {
43
- list.push(item);
44
- };
45
- this.getList = function () {
46
- return list;
47
- };
48
- },
49
-
50
- SequenceHandler = function () {
51
- var sequence = [];
52
- this.push = function (callBack) {
53
- sequence.push(callBack);
54
- if (sequence.length === 1) {
55
- callBack();
56
- }
57
- };
58
- this.next = function () {
59
- sequence.shift();
60
- if (sequence.length) {
61
- sequence[0]();
62
- }
63
- };
64
- };
65
-
66
- FileUpload = function (container) {
67
- var fileUpload = this,
68
- uploadForm,
69
- fileInput,
70
- settings = {
71
- namespace: defaultNamespace,
72
- uploadFormFilter: function (index) {
73
- return true;
74
- },
75
- fileInputFilter: function (index) {
76
- return true;
77
- },
78
- cssClass: defaultNamespace,
79
- dragDropSupport: true,
80
- dropZone: container,
81
- url: function (form) {
82
- return form.attr('action');
83
- },
84
- method: function (form) {
85
- return form.attr('method');
86
- },
87
- fieldName: function (input) {
88
- return input.attr('name');
89
- },
90
- formData: function (form) {
91
- return form.serializeArray();
92
- },
93
- requestHeaders: null,
94
- multipart: true,
95
- multiFileRequest: false,
96
- withCredentials: false,
97
- forceIframeUpload: false,
98
- sequentialUploads: false,
99
- maxChunkSize: null,
100
- maxFileReaderSize: 50000000
101
- },
102
- documentListeners = {},
103
- dropZoneListeners = {},
104
- protocolRegExp = /^http(s)?:\/\//,
105
- optionsReference,
106
- multiLoader = new MultiLoader(function (list) {
107
- if (typeof settings.onLoadAll === func) {
108
- settings.onLoadAll(list);
109
- }
110
- }),
111
- sequenceHandler = new SequenceHandler(),
112
-
113
- completeNext = function () {
114
- multiLoader.complete();
115
- sequenceHandler.next();
30
+ // The FileReader API is not actually used, but works as feature detection,
31
+ // as e.g. Safari supports XHR file uploads via the FormData API,
32
+ // but not non-multipart XHR file uploads:
33
+ $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
34
+ $.support.xhrFormDataFileUpload = !!window.FormData;
35
+
36
+ // The fileupload widget listens for change events on file input fields defined
37
+ // via fileInput setting and paste or drop events of the given dropZone.
38
+ // In addition to the default jQuery Widget methods, the fileupload widget
39
+ // exposes the "add" and "send" methods, to add or directly send files using
40
+ // the fileupload API.
41
+ // By default, files added via file input selection, paste, drag & drop or
42
+ // "add" method are uploaded immediately, but it is possible to override
43
+ // the "add" callback option to queue file uploads.
44
+ $.widget('blueimp.fileupload', {
45
+
46
+ options: {
47
+ // The drop target element(s), by the default the complete document.
48
+ // Set to null to disable drag & drop support:
49
+ dropZone: $(document),
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.
54
+ // If undefined, it is set to the file input fields inside
55
+ // of the widget element on plugin initialization.
56
+ // Set to null to disable the change listener.
57
+ fileInput: undefined,
58
+ // By default, the file input field is replaced with a clone after
59
+ // each input field change event. This is required for iframe transport
60
+ // queues and allows change events to be fired for the same file
61
+ // selection, but can be disabled by setting the following option to false:
62
+ replaceFileInput: true,
63
+ // The parameter name for the file form data (the request argument name).
64
+ // If undefined or empty, the name property of the file input field is
65
+ // used, or "files[]" if the file input name property is also empty,
66
+ // can be a string or an array of strings:
67
+ paramName: undefined,
68
+ // By default, each file of a selection is uploaded using an individual
69
+ // request for XHR type uploads. Set to false to upload file
70
+ // selections in one request each:
71
+ singleFileUploads: true,
72
+ // To limit the number of files uploaded with one XHR request,
73
+ // set the following option to an integer greater than 0:
74
+ limitMultiFileUploads: undefined,
75
+ // Set the following option to true to issue all file upload requests
76
+ // in a sequential order:
77
+ sequentialUploads: false,
78
+ // To limit the number of concurrent uploads,
79
+ // set the following option to an integer greater than 0:
80
+ limitConcurrentUploads: undefined,
81
+ // Set the following option to true to force iframe transport uploads:
82
+ forceIframeTransport: false,
83
+ // Set the following option to the location of a redirect url on the
84
+ // origin server, for cross-domain iframe transport uploads:
85
+ redirect: undefined,
86
+ // The parameter name for the redirect url, sent as part of the form
87
+ // data and set to 'redirect' if this option is empty:
88
+ redirectParamName: undefined,
89
+ // Set the following option to the location of a postMessage window,
90
+ // to enable postMessage transport uploads:
91
+ postMessage: undefined,
92
+ // By default, XHR file uploads are sent as multipart/form-data.
93
+ // The iframe transport is always using multipart/form-data.
94
+ // Set to false to enable non-multipart XHR uploads:
95
+ multipart: true,
96
+ // To upload large files in smaller chunks, set the following option
97
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
98
+ // or the browser does not support the required Blob API, files will
99
+ // be uploaded as a whole.
100
+ maxChunkSize: undefined,
101
+ // When a non-multipart upload or a chunked multipart upload has been
102
+ // aborted, this option can be used to resume the upload by setting
103
+ // it to the size of the already uploaded bytes. This option is most
104
+ // useful when modifying the options object inside of the "add" or
105
+ // "send" callbacks, as the options are cloned for each file upload.
106
+ uploadedBytes: undefined,
107
+ // By default, failed (abort or error) file uploads are removed from the
108
+ // global progress calculation. Set the following option to false to
109
+ // prevent recalculating the global progress data:
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
+
116
+ // Additional form data to be sent along with the file uploads can be set
117
+ // using this option, which accepts an array of objects with name and
118
+ // value properties, a function returning such an array, a FormData
119
+ // object (for XHR file uploads), or a simple object.
120
+ // The form of the first fileInput is given as parameter to the function:
121
+ formData: function (form) {
122
+ return form.serializeArray();
116
123
  },
117
124
 
118
- isXHRUploadCapable = function () {
119
- return typeof XMLHttpRequest !== undef && typeof XMLHttpRequestUpload !== undef &&
120
- typeof File !== undef && (!settings.multipart || typeof FormData !== undef ||
121
- (typeof FileReader !== undef && typeof XMLHttpRequest.prototype.sendAsBinary === func));
125
+ // The add callback is invoked as soon as files are added to the fileupload
126
+ // widget (via file input selection, drag & drop, paste or add API call).
127
+ // If the singleFileUploads option is enabled, this callback will be
128
+ // called once for each file in the selection for XHR file uplaods, else
129
+ // once for each file selection.
130
+ // The upload starts when the submit method is invoked on the data parameter.
131
+ // The data object contains a files property holding the added files
132
+ // and allows to override plugin options as well as define ajax settings.
133
+ // Listeners for this callback can also be bound the following way:
134
+ // .bind('fileuploadadd', func);
135
+ // data.submit() returns a Promise object and allows to attach additional
136
+ // handlers using jQuery's Deferred callbacks:
137
+ // data.submit().done(func).fail(func).always(func);
138
+ add: function (e, data) {
139
+ data.submit();
122
140
  },
123
141
 
124
- initEventHandlers = function () {
125
- if (settings.dragDropSupport) {
126
- if (typeof settings.onDocumentDragEnter === func) {
127
- documentListeners['dragenter.' + settings.namespace] = function (e) {
128
- settings.onDocumentDragEnter(e);
129
- };
130
- }
131
- if (typeof settings.onDocumentDragLeave === func) {
132
- documentListeners['dragleave.' + settings.namespace] = function (e) {
133
- settings.onDocumentDragLeave(e);
134
- };
135
- }
136
- documentListeners['dragover.' + settings.namespace] = fileUpload.onDocumentDragOver;
137
- documentListeners['drop.' + settings.namespace] = fileUpload.onDocumentDrop;
138
- $(document).bind(documentListeners);
139
- if (typeof settings.onDragEnter === func) {
140
- dropZoneListeners['dragenter.' + settings.namespace] = function (e) {
141
- settings.onDragEnter(e);
142
- };
143
- }
144
- if (typeof settings.onDragLeave === func) {
145
- dropZoneListeners['dragleave.' + settings.namespace] = function (e) {
146
- settings.onDragLeave(e);
147
- };
148
- }
149
- dropZoneListeners['dragover.' + settings.namespace] = fileUpload.onDragOver;
150
- dropZoneListeners['drop.' + settings.namespace] = fileUpload.onDrop;
151
- settings.dropZone.bind(dropZoneListeners);
142
+ // Other callbacks:
143
+
144
+ // Callback for the submit event of each file upload:
145
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
146
+
147
+ // Callback for the start of each file upload request:
148
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
149
+
150
+ // Callback for successful uploads:
151
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
152
+
153
+ // Callback for failed (abort or error) uploads:
154
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
155
+
156
+ // Callback for completed (success, abort or error) requests:
157
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
158
+
159
+ // Callback for upload progress events:
160
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
161
+
162
+ // Callback for global upload progress events:
163
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
164
+
165
+ // Callback for uploads start, equivalent to the global ajaxStart event:
166
+ // start: function (e) {}, // .bind('fileuploadstart', func);
167
+
168
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
169
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
170
+
171
+ // Callback for change events of the fileInput(s):
172
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
173
+
174
+ // Callback for paste events to the pasteZone(s):
175
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
176
+
177
+ // Callback for drop events of the dropZone(s):
178
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
179
+
180
+ // Callback for dragover events of the dropZone(s):
181
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
182
+
183
+ // Callback for the start of each chunk upload request:
184
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
185
+
186
+ // Callback for successful chunk uploads:
187
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
188
+
189
+ // Callback for failed (abort or error) chunk uploads:
190
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
191
+
192
+ // Callback for completed (success, abort or error) chunk upload requests:
193
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
194
+
195
+ // The plugin options are used as settings object for the ajax calls.
196
+ // The following are jQuery ajax settings required for the file uploads:
197
+ processData: false,
198
+ contentType: false,
199
+ cache: false
200
+ },
201
+
202
+ // A list of options that require a refresh after assigning a new value:
203
+ _refreshOptionsList: [
204
+ 'fileInput',
205
+ 'dropZone',
206
+ 'pasteZone',
207
+ 'multipart',
208
+ 'forceIframeTransport'
209
+ ],
210
+
211
+ _BitrateTimer: function () {
212
+ this.timestamp = +(new Date());
213
+ this.loaded = 0;
214
+ this.bitrate = 0;
215
+ this.getBitrate = function (now, loaded, interval) {
216
+ var timeDiff = now - this.timestamp;
217
+ if (!this.bitrate || !interval || timeDiff > interval) {
218
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
219
+ this.loaded = loaded;
220
+ this.timestamp = now;
152
221
  }
153
- fileInput.bind('change.' + settings.namespace, fileUpload.onChange);
154
- },
222
+ return this.bitrate;
223
+ };
224
+ },
155
225
 
156
- removeEventHandlers = function () {
157
- $.each(documentListeners, function (key, value) {
158
- $(document).unbind(key, value);
159
- });
160
- $.each(dropZoneListeners, function (key, value) {
161
- settings.dropZone.unbind(key, value);
226
+ _isXHRUpload: function (options) {
227
+ return !options.forceIframeTransport &&
228
+ ((!options.multipart && $.support.xhrFileUpload) ||
229
+ $.support.xhrFormDataFileUpload);
230
+ },
231
+
232
+ _getFormData: function (options) {
233
+ var formData;
234
+ if (typeof options.formData === 'function') {
235
+ return options.formData(options.form);
236
+ }
237
+ if ($.isArray(options.formData)) {
238
+ return options.formData;
239
+ }
240
+ if (options.formData) {
241
+ formData = [];
242
+ $.each(options.formData, function (name, value) {
243
+ formData.push({name: name, value: value});
162
244
  });
163
- fileInput.unbind('change.' + settings.namespace);
164
- },
245
+ return formData;
246
+ }
247
+ return [];
248
+ },
165
249
 
166
- isChunkedUpload = function (settings) {
167
- return typeof settings.uploadedBytes !== undef;
168
- },
250
+ _getTotal: function (files) {
251
+ var total = 0;
252
+ $.each(files, function (index, file) {
253
+ total += file.size || 1;
254
+ });
255
+ return total;
256
+ },
169
257
 
170
- createProgressEvent = function (lengthComputable, loaded, total) {
171
- var event;
172
- if (typeof document.createEvent === func && typeof ProgressEvent !== undef) {
173
- event = document.createEvent('ProgressEvent');
174
- event.initProgressEvent(
175
- 'progress',
176
- false,
177
- false,
178
- lengthComputable,
179
- loaded,
180
- total
181
- );
182
- } else {
183
- event = {
184
- lengthComputable: true,
185
- loaded: loaded,
186
- total: total
187
- };
258
+ _onProgress: function (e, data) {
259
+ if (e.lengthComputable) {
260
+ var now = +(new Date()),
261
+ total,
262
+ loaded;
263
+ if (data._time && data.progressInterval &&
264
+ (now - data._time < data.progressInterval) &&
265
+ e.loaded !== e.total) {
266
+ return;
188
267
  }
189
- return event;
190
- },
268
+ data._time = now;
269
+ total = data.total || this._getTotal(data.files);
270
+ loaded = parseInt(
271
+ e.loaded / e.total * (data.chunkSize || total),
272
+ 10
273
+ ) + (data.uploadedBytes || 0);
274
+ this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
275
+ data.lengthComputable = true;
276
+ data.loaded = loaded;
277
+ data.total = total;
278
+ data.bitrate = data._bitrateTimer.getBitrate(
279
+ now,
280
+ loaded,
281
+ data.bitrateInterval
282
+ );
283
+ // Trigger a custom progress event with a total data property set
284
+ // to the file size(s) of the current upload and a loaded data
285
+ // property calculated accordingly:
286
+ this._trigger('progress', e, data);
287
+ // Trigger a global progress event for all current file uploads,
288
+ // including ajax calls queued for sequential file uploads:
289
+ this._trigger('progressall', e, {
290
+ lengthComputable: true,
291
+ loaded: this._loaded,
292
+ total: this._total,
293
+ bitrate: this._bitrateTimer.getBitrate(
294
+ now,
295
+ this._loaded,
296
+ data.bitrateInterval
297
+ )
298
+ });
299
+ }
300
+ },
191
301
 
192
- getProgressTotal = function (files, index, settings) {
193
- var i,
194
- total;
195
- if (typeof settings.progressTotal === undef) {
196
- if (files[index]) {
197
- total = files[index].size;
198
- settings.progressTotal = total ? total : 1;
199
- } else {
200
- total = 0;
201
- for (i = 0; i < files.length; i += 1) {
202
- total += files[i].size;
203
- }
204
- settings.progressTotal = total;
205
- }
206
- }
207
- return settings.progressTotal;
208
- },
302
+ _initProgressListener: function (options) {
303
+ var that = this,
304
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
305
+ // Accesss to the native XHR object is required to add event listeners
306
+ // for the upload progress event:
307
+ if (xhr.upload) {
308
+ $(xhr.upload).bind('progress', function (e) {
309
+ var oe = e.originalEvent;
310
+ // Make sure the progress event properties get copied over:
311
+ e.lengthComputable = oe.lengthComputable;
312
+ e.loaded = oe.loaded;
313
+ e.total = oe.total;
314
+ that._onProgress(e, options);
315
+ });
316
+ options.xhr = function () {
317
+ return xhr;
318
+ };
319
+ }
320
+ },
209
321
 
210
- handleGlobalProgress = function (event, files, index, xhr, settings) {
211
- var progressEvent,
212
- loaderList,
213
- globalLoaded = 0,
214
- globalTotal = 0;
215
- if (event.lengthComputable && typeof settings.onProgressAll === func) {
216
- settings.progressLoaded = parseInt(
217
- event.loaded / event.total * getProgressTotal(files, index, settings),
218
- 10
219
- );
220
- loaderList = multiLoader.getList();
221
- $.each(loaderList, function (index, item) {
222
- // item is an array with [files, index, xhr, settings]
223
- globalLoaded += item[3].progressLoaded || 0;
224
- globalTotal += getProgressTotal(item[0], item[1], item[3]);
225
- });
226
- progressEvent = createProgressEvent(
227
- true,
228
- globalLoaded,
229
- globalTotal
230
- );
231
- settings.onProgressAll(progressEvent, loaderList);
232
- }
233
- },
234
-
235
- handleLoadEvent = function (event, files, index, xhr, settings) {
236
- var progressEvent;
237
- if (isChunkedUpload(settings)) {
238
- settings.uploadedBytes += settings.chunkSize;
239
- progressEvent = createProgressEvent(
240
- true,
241
- settings.uploadedBytes,
242
- files[index].size
243
- );
244
- if (typeof settings.onProgress === func) {
245
- settings.onProgress(progressEvent, files, index, xhr, settings);
246
- }
247
- handleGlobalProgress(progressEvent, files, index, xhr, settings);
248
- if (settings.uploadedBytes < files[index].size) {
249
- if (typeof settings.resumeUpload === func) {
250
- settings.resumeUpload(
251
- event,
252
- files,
253
- index,
254
- xhr,
255
- settings,
256
- function () {
257
- upload(event, files, index, xhr, settings, true);
258
- }
259
- );
260
- } else {
261
- upload(event, files, index, xhr, settings, true);
262
- }
263
- return;
322
+ _initXHRData: function (options) {
323
+ var formData,
324
+ file = options.files[0],
325
+ // Ignore non-multipart setting if not supported:
326
+ multipart = options.multipart || !$.support.xhrFileUpload,
327
+ paramName = options.paramName[0];
328
+ options.headers = options.headers || {};
329
+ if (options.contentRange) {
330
+ options.headers['Content-Range'] = options.contentRange;
331
+ }
332
+ if (!multipart) {
333
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
334
+ encodeURI(file.name) + '"';
335
+ options.contentType = file.type;
336
+ options.data = options.blob || file;
337
+ } else if ($.support.xhrFormDataFileUpload) {
338
+ if (options.postMessage) {
339
+ // window.postMessage does not allow sending FormData
340
+ // objects, so we just add the File/Blob objects to
341
+ // the formData array and let the postMessage window
342
+ // create the FormData object out of this array:
343
+ formData = this._getFormData(options);
344
+ if (options.blob) {
345
+ formData.push({
346
+ name: paramName,
347
+ value: options.blob
348
+ });
349
+ } else {
350
+ $.each(options.files, function (index, file) {
351
+ formData.push({
352
+ name: options.paramName[index] || paramName,
353
+ value: file
354
+ });
355
+ });
264
356
  }
265
- }
266
- settings.progressLoaded = getProgressTotal(files, index, settings);
267
- if (typeof settings.onLoad === func) {
268
- settings.onLoad(event, files, index, xhr, settings);
269
- }
270
- completeNext();
271
- },
272
-
273
- handleProgressEvent = function (event, files, index, xhr, settings) {
274
- var progressEvent = event;
275
- if (isChunkedUpload(settings) && event.lengthComputable) {
276
- progressEvent = createProgressEvent(
277
- true,
278
- settings.uploadedBytes + parseInt(event.loaded / event.total * settings.chunkSize, 10),
279
- files[index].size
280
- );
281
- }
282
- if (typeof settings.onProgress === func) {
283
- settings.onProgress(progressEvent, files, index, xhr, settings);
284
- }
285
- handleGlobalProgress(progressEvent, files, index, xhr, settings);
286
- },
287
-
288
- initUploadEventHandlers = function (files, index, xhr, settings) {
289
- if (xhr.upload) {
290
- xhr.upload.onprogress = function (e) {
291
- handleProgressEvent(e, files, index, xhr, settings);
292
- };
293
- }
294
- xhr.onload = function (e) {
295
- handleLoadEvent(e, files, index, xhr, settings);
296
- };
297
- xhr.onabort = function (e) {
298
- settings.progressTotal = settings.progressLoaded;
299
- if (typeof settings.onAbort === func) {
300
- settings.onAbort(e, files, index, xhr, settings);
357
+ } else {
358
+ if (options.formData instanceof FormData) {
359
+ formData = options.formData;
360
+ } else {
361
+ formData = new FormData();
362
+ $.each(this._getFormData(options), function (index, field) {
363
+ formData.append(field.name, field.value);
364
+ });
301
365
  }
302
- completeNext();
303
- };
304
- xhr.onerror = function (e) {
305
- settings.progressTotal = settings.progressLoaded;
306
- if (typeof settings.onError === func) {
307
- settings.onError(e, files, index, xhr, settings);
366
+ if (options.blob) {
367
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
368
+ encodeURI(file.name) + '"';
369
+ formData.append(paramName, options.blob, file.name);
370
+ } else {
371
+ $.each(options.files, function (index, file) {
372
+ // Files are also Blob instances, but some browsers
373
+ // (Firefox 3.6) support the File API but not Blobs.
374
+ // This check allows the tests to run with
375
+ // dummy objects:
376
+ if ((window.Blob && file instanceof Blob) ||
377
+ (window.File && file instanceof File)) {
378
+ formData.append(
379
+ options.paramName[index] || paramName,
380
+ file,
381
+ file.name
382
+ );
383
+ }
384
+ });
308
385
  }
309
- completeNext();
310
- };
311
- },
312
-
313
- getUrl = function (settings) {
314
- if (typeof settings.url === func) {
315
- return settings.url(settings.uploadForm || uploadForm);
316
- }
317
- return settings.url;
318
- },
319
-
320
- getMethod = function (settings) {
321
- if (typeof settings.method === func) {
322
- return settings.method(settings.uploadForm || uploadForm);
323
- }
324
- return settings.method;
325
- },
326
-
327
- getFieldName = function (settings) {
328
- if (typeof settings.fieldName === func) {
329
- return settings.fieldName(settings.fileInput || fileInput);
330
386
  }
331
- return settings.fieldName;
332
- },
387
+ options.data = formData;
388
+ }
389
+ // Blob reference is not needed anymore, free memory:
390
+ options.blob = null;
391
+ },
333
392
 
334
- getFormData = function (settings) {
335
- var formData;
336
- if (typeof settings.formData === func) {
337
- return settings.formData(settings.uploadForm || uploadForm);
338
- } else if ($.isArray(settings.formData)) {
339
- return settings.formData;
340
- } else if (settings.formData) {
341
- formData = [];
342
- $.each(settings.formData, function (name, value) {
343
- formData.push({name: name, value: value});
344
- });
345
- return formData;
346
- }
347
- return [];
348
- },
393
+ _initIframeSettings: function (options) {
394
+ // Setting the dataType to iframe enables the iframe transport:
395
+ options.dataType = 'iframe ' + (options.dataType || '');
396
+ // The iframe transport accepts a serialized array as form data:
397
+ options.formData = this._getFormData(options);
398
+ // Add redirect url to form data on cross-domain uploads:
399
+ if (options.redirect && $('<a></a>').prop('href', options.url)
400
+ .prop('host') !== location.host) {
401
+ options.formData.push({
402
+ name: options.redirectParamName || 'redirect',
403
+ value: options.redirect
404
+ });
405
+ }
406
+ },
349
407
 
350
- isSameDomain = function (url) {
351
- if (protocolRegExp.test(url)) {
352
- var host = location.host,
353
- indexStart = location.protocol.length + 2,
354
- index = url.indexOf(host, indexStart),
355
- pathIndex = index + host.length;
356
- if ((index === indexStart || index === url.indexOf('@', indexStart) + 1) &&
357
- (url.length === pathIndex || $.inArray(url.charAt(pathIndex), ['/', '?', '#']) !== -1)) {
358
- return true;
408
+ _initDataSettings: function (options) {
409
+ if (this._isXHRUpload(options)) {
410
+ if (!this._chunkedUpload(options, true)) {
411
+ if (!options.data) {
412
+ this._initXHRData(options);
359
413
  }
360
- return false;
414
+ this._initProgressListener(options);
361
415
  }
362
- return true;
363
- },
416
+ if (options.postMessage) {
417
+ // Setting the dataType to postmessage enables the
418
+ // postMessage transport:
419
+ options.dataType = 'postmessage ' + (options.dataType || '');
420
+ }
421
+ } else {
422
+ this._initIframeSettings(options, 'iframe');
423
+ }
424
+ },
364
425
 
365
- initUploadRequest = function (files, index, xhr, settings) {
366
- var file = files[index],
367
- url = getUrl(settings),
368
- sameDomain = isSameDomain(url);
369
- xhr.open(getMethod(settings), url, true);
370
- if (sameDomain) {
371
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
372
- if (!settings.multipart || isChunkedUpload(settings)) {
373
- xhr.setRequestHeader('X-File-Name', file.name);
374
- xhr.setRequestHeader('X-File-Type', file.type);
375
- xhr.setRequestHeader('X-File-Size', file.size);
376
- if (!isChunkedUpload(settings)) {
377
- xhr.setRequestHeader('Content-Type', file.type);
378
- } else if (!settings.multipart) {
379
- xhr.setRequestHeader('Content-Type', 'application/octet-stream');
380
- }
426
+ _getParamName: function (options) {
427
+ var fileInput = $(options.fileInput),
428
+ paramName = options.paramName;
429
+ if (!paramName) {
430
+ paramName = [];
431
+ fileInput.each(function () {
432
+ var input = $(this),
433
+ name = input.prop('name') || 'files[]',
434
+ i = (input.prop('files') || [1]).length;
435
+ while (i) {
436
+ paramName.push(name);
437
+ i -= 1;
381
438
  }
382
- } else if (settings.withCredentials) {
383
- xhr.withCredentials = true;
384
- }
385
- if ($.isArray(settings.requestHeaders)) {
386
- $.each(settings.requestHeaders, function (index, header) {
387
- xhr.setRequestHeader(header.name, header.value);
388
- });
389
- } else if (settings.requestHeaders) {
390
- $.each(settings.requestHeaders, function (name, value) {
391
- xhr.setRequestHeader(name, value);
392
- });
439
+ });
440
+ if (!paramName.length) {
441
+ paramName = [fileInput.prop('name') || 'files[]'];
393
442
  }
394
- },
443
+ } else if (!$.isArray(paramName)) {
444
+ paramName = [paramName];
445
+ }
446
+ return paramName;
447
+ },
395
448
 
396
- formDataUpload = function (files, xhr, settings) {
397
- var formData = new FormData(),
398
- i;
399
- $.each(getFormData(settings), function (index, field) {
400
- formData.append(field.name, field.value);
401
- });
402
- for (i = 0; i < files.length; i += 1) {
403
- formData.append(getFieldName(settings), files[i]);
449
+ _initFormSettings: function (options) {
450
+ // Retrieve missing options from the input field and the
451
+ // associated form, if available:
452
+ if (!options.form || !options.form.length) {
453
+ options.form = $(options.fileInput.prop('form'));
454
+ // If the given file input doesn't have an associated form,
455
+ // use the default widget file input's form:
456
+ if (!options.form.length) {
457
+ options.form = $(this.options.fileInput.prop('form'));
404
458
  }
405
- xhr.send(formData);
406
- },
459
+ }
460
+ options.paramName = this._getParamName(options);
461
+ if (!options.url) {
462
+ options.url = options.form.prop('action') || location.href;
463
+ }
464
+ // The HTTP request method must be "POST" or "PUT":
465
+ options.type = (options.type || options.form.prop('method') || '')
466
+ .toUpperCase();
467
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
468
+ options.type !== 'PATCH') {
469
+ options.type = 'POST';
470
+ }
471
+ if (!options.formAcceptCharset) {
472
+ options.formAcceptCharset = options.form.attr('accept-charset');
473
+ }
474
+ },
407
475
 
408
- loadFileContent = function (file, callBack) {
409
- file.reader = new FileReader();
410
- file.reader.onload = callBack;
411
- file.reader.readAsBinaryString(file);
412
- },
476
+ _getAJAXSettings: function (data) {
477
+ var options = $.extend({}, this.options, data);
478
+ this._initFormSettings(options);
479
+ this._initDataSettings(options);
480
+ return options;
481
+ },
413
482
 
414
- utf8encode = function (str) {
415
- return unescape(encodeURIComponent(str));
416
- },
483
+ // Maps jqXHR callbacks to the equivalent
484
+ // methods of the given Promise object:
485
+ _enhancePromise: function (promise) {
486
+ promise.success = promise.done;
487
+ promise.error = promise.fail;
488
+ promise.complete = promise.always;
489
+ return promise;
490
+ },
417
491
 
418
- buildMultiPartFormData = function (boundary, files, filesFieldName, fields) {
419
- var doubleDash = '--',
420
- crlf = '\r\n',
421
- formData = '',
422
- buffer = [];
423
- $.each(fields, function (index, field) {
424
- formData += doubleDash + boundary + crlf +
425
- 'Content-Disposition: form-data; name="' +
426
- utf8encode(field.name) +
427
- '"' + crlf + crlf +
428
- utf8encode(field.value) + crlf;
429
- });
430
- $.each(files, function (index, file) {
431
- formData += doubleDash + boundary + crlf +
432
- 'Content-Disposition: form-data; name="' +
433
- utf8encode(filesFieldName) +
434
- '"; filename="' + utf8encode(file.name) + '"' + crlf +
435
- 'Content-Type: ' + utf8encode(file.type) + crlf + crlf;
436
- buffer.push(formData);
437
- buffer.push(file.reader.result);
438
- delete file.reader;
439
- formData = crlf;
440
- });
441
- formData += doubleDash + boundary + doubleDash + crlf;
442
- buffer.push(formData);
443
- return buffer.join('');
444
- },
445
-
446
- fileReaderUpload = function (files, xhr, settings) {
447
- var boundary = '----MultiPartFormBoundary' + (new Date()).getTime(),
448
- loader,
449
- i;
450
- xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
451
- loader = new MultiLoader(function () {
452
- xhr.sendAsBinary(buildMultiPartFormData(
453
- boundary,
454
- files,
455
- getFieldName(settings),
456
- getFormData(settings)
457
- ));
458
- }, files.length);
459
- for (i = 0; i < files.length; i += 1) {
460
- loadFileContent(files[i], loader.complete);
461
- }
462
- },
492
+ // Creates and returns a Promise object enhanced with
493
+ // the jqXHR methods abort, success, error and complete:
494
+ _getXHRPromise: function (resolveOrReject, context, args) {
495
+ var dfd = $.Deferred(),
496
+ promise = dfd.promise();
497
+ context = context || this.options.context || promise;
498
+ if (resolveOrReject === true) {
499
+ dfd.resolveWith(context, args);
500
+ } else if (resolveOrReject === false) {
501
+ dfd.rejectWith(context, args);
502
+ }
503
+ promise.abort = dfd.promise;
504
+ return this._enhancePromise(promise);
505
+ },
463
506
 
464
- getBlob = function (file, settings) {
465
- var blob,
466
- ub = settings.uploadedBytes,
467
- mcs = settings.maxChunkSize;
468
- if (file && typeof file.slice === func && (ub || (mcs && mcs < file.size))) {
469
- settings.uploadedBytes = ub = ub || 0;
470
- blob = file.slice(ub, mcs || file.size - ub);
471
- settings.chunkSize = blob.size;
472
- return blob;
473
- }
474
- return file;
475
- },
507
+ // Parses the Range header from the server response
508
+ // and returns the uploaded bytes:
509
+ _getUploadedBytes: function (jqXHR) {
510
+ var range = jqXHR.getResponseHeader('Range'),
511
+ parts = range && range.split('-'),
512
+ upperBytesPos = parts && parts.length > 1 &&
513
+ parseInt(parts[1], 10);
514
+ return upperBytesPos && upperBytesPos + 1;
515
+ },
476
516
 
477
- upload = function (event, files, index, xhr, settings, nextChunk) {
478
- var send;
479
- send = function () {
480
- if (!nextChunk) {
481
- if (typeof settings.onSend === func &&
482
- settings.onSend(event, files, index, xhr, settings) === false) {
483
- completeNext();
484
- return;
485
- }
486
- }
487
- var blob = getBlob(files[index], settings),
488
- filesToUpload;
489
- initUploadEventHandlers(files, index, xhr, settings);
490
- initUploadRequest(files, index, xhr, settings);
491
- if (!settings.multipart) {
492
- if (xhr.upload) {
493
- xhr.send(blob);
494
- } else {
495
- $.error('Browser does not support XHR file uploads');
517
+ // Uploads a file in multiple, sequential requests
518
+ // by splitting the file up in multiple blob chunks.
519
+ // If the second parameter is true, only tests if the file
520
+ // should be uploaded in chunks, but does not invoke any
521
+ // upload requests:
522
+ _chunkedUpload: function (options, testOnly) {
523
+ var that = this,
524
+ file = options.files[0],
525
+ fs = file.size,
526
+ ub = options.uploadedBytes = options.uploadedBytes || 0,
527
+ mcs = options.maxChunkSize || fs,
528
+ slice = file.slice || file.webkitSlice || file.mozSlice,
529
+ dfd = $.Deferred(),
530
+ promise = dfd.promise(),
531
+ jqXHR,
532
+ upload;
533
+ if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
534
+ options.data) {
535
+ return false;
536
+ }
537
+ if (testOnly) {
538
+ return true;
539
+ }
540
+ if (ub >= fs) {
541
+ file.error = 'Uploaded bytes exceed file size';
542
+ return this._getXHRPromise(
543
+ false,
544
+ options.context,
545
+ [null, 'error', file.error]
546
+ );
547
+ }
548
+ // The chunk upload method:
549
+ upload = function () {
550
+ // Clone the options object for each chunk upload:
551
+ var o = $.extend({}, options);
552
+ o.blob = slice.call(
553
+ file,
554
+ ub,
555
+ ub + mcs,
556
+ file.type
557
+ );
558
+ // Store the current chunk size, as the blob itself
559
+ // will be dereferenced after data processing:
560
+ o.chunkSize = o.blob.size;
561
+ // Expose the chunk bytes position range:
562
+ o.contentRange = 'bytes ' + ub + '-' +
563
+ (ub + o.chunkSize - 1) + '/' + fs;
564
+ // Process the upload data (the blob and potential form data):
565
+ that._initXHRData(o);
566
+ // Add progress listeners for this chunk upload:
567
+ that._initProgressListener(o);
568
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
569
+ that._getXHRPromise(false, o.context))
570
+ .done(function (result, textStatus, jqXHR) {
571
+ ub = that._getUploadedBytes(jqXHR) ||
572
+ (ub + o.chunkSize);
573
+ // Create a progress event if upload is done and no progress
574
+ // event has been invoked for this chunk, or there has been
575
+ // no progress event with loaded equaling total:
576
+ if (!o.loaded || o.loaded < o.total) {
577
+ that._onProgress($.Event('progress', {
578
+ lengthComputable: true,
579
+ loaded: ub - o.uploadedBytes,
580
+ total: ub - o.uploadedBytes
581
+ }), o);
496
582
  }
497
- } else {
498
- filesToUpload = (typeof index === 'number') ? [blob] : files;
499
- if (typeof FormData !== undef) {
500
- formDataUpload(filesToUpload, xhr, settings);
501
- } else if (typeof FileReader !== undef && typeof xhr.sendAsBinary === func) {
502
- fileReaderUpload(filesToUpload, xhr, settings);
583
+ options.uploadedBytes = o.uploadedBytes = ub;
584
+ o.result = result;
585
+ o.textStatus = textStatus;
586
+ o.jqXHR = jqXHR;
587
+ that._trigger('chunkdone', null, o);
588
+ that._trigger('chunkalways', null, o);
589
+ if (ub < fs) {
590
+ // File upload not yet complete,
591
+ // continue with the next chunk:
592
+ upload();
503
593
  } else {
504
- $.error('Browser does not support multipart/form-data XHR file uploads');
594
+ dfd.resolveWith(
595
+ o.context,
596
+ [result, textStatus, jqXHR]
597
+ );
505
598
  }
506
- }
507
- };
508
- if (!nextChunk) {
509
- multiLoader.push(Array.prototype.slice.call(arguments, 1));
510
- if (settings.sequentialUploads) {
511
- sequenceHandler.push(send);
512
- return;
513
- }
514
- }
515
- send();
516
- },
599
+ })
600
+ .fail(function (jqXHR, textStatus, errorThrown) {
601
+ o.jqXHR = jqXHR;
602
+ o.textStatus = textStatus;
603
+ o.errorThrown = errorThrown;
604
+ that._trigger('chunkfail', null, o);
605
+ that._trigger('chunkalways', null, o);
606
+ dfd.rejectWith(
607
+ o.context,
608
+ [jqXHR, textStatus, errorThrown]
609
+ );
610
+ });
611
+ };
612
+ this._enhancePromise(promise);
613
+ promise.abort = function () {
614
+ return jqXHR.abort();
615
+ };
616
+ upload();
617
+ return promise;
618
+ },
517
619
 
518
- handleUpload = function (event, files, input, form, index) {
519
- var xhr = new XMLHttpRequest(),
520
- uploadSettings = $.extend({}, settings);
521
- uploadSettings.fileInput = input;
522
- uploadSettings.uploadForm = form;
523
- if (typeof uploadSettings.initUpload === func) {
524
- uploadSettings.initUpload(
525
- event,
526
- files,
527
- index,
528
- xhr,
529
- uploadSettings,
530
- function () {
531
- upload(event, files, index, xhr, uploadSettings);
532
- }
533
- );
534
- } else {
535
- upload(event, files, index, xhr, uploadSettings);
536
- }
537
- },
620
+ _beforeSend: function (e, data) {
621
+ if (this._active === 0) {
622
+ // the start callback is triggered when an upload starts
623
+ // and no other uploads are currently running,
624
+ // equivalent to the global ajaxStart event:
625
+ this._trigger('start');
626
+ // Set timer for global bitrate progress calculation:
627
+ this._bitrateTimer = new this._BitrateTimer();
628
+ }
629
+ this._active += 1;
630
+ // Initialize the global progress values:
631
+ this._loaded += data.uploadedBytes || 0;
632
+ this._total += this._getTotal(data.files);
633
+ },
538
634
 
539
- handleLegacyGlobalProgress = function (event, files, index, iframe, settings) {
540
- var total = 0,
541
- progressEvent;
542
- if (typeof index === undef) {
543
- $.each(files, function (index, file) {
544
- total += file.size ? file.size : 1;
545
- });
546
- } else {
547
- total = files[index].size ? files[index].size : 1;
548
- }
549
- progressEvent = createProgressEvent(true, total, total);
550
- settings.progressLoaded = total;
551
- handleGlobalProgress(progressEvent, files, index, iframe, settings);
552
- },
635
+ _onDone: function (result, textStatus, jqXHR, options) {
636
+ if (!options.uploadedBytes && (!this._isXHRUpload(options) ||
637
+ !options.loaded || options.loaded < options.total)) {
638
+ var total = this._getTotal(options.files) || 1;
639
+ // Create a progress event for each iframe load,
640
+ // or if there has been no progress event with
641
+ // loaded equaling total for XHR uploads:
642
+ this._onProgress($.Event('progress', {
643
+ lengthComputable: true,
644
+ loaded: total,
645
+ total: total
646
+ }), options);
647
+ }
648
+ options.result = result;
649
+ options.textStatus = textStatus;
650
+ options.jqXHR = jqXHR;
651
+ this._trigger('done', null, options);
652
+ },
553
653
 
554
- legacyUploadFormDataInit = function (input, form, settings) {
555
- var formData = getFormData(settings);
556
- form.find(':input').not(':disabled')
557
- .attr('disabled', true)
558
- .addClass(settings.namespace + '_disabled');
559
- $.each(formData, function (index, field) {
560
- $('<input type="hidden"/>')
561
- .attr('name', field.name)
562
- .val(field.value)
563
- .addClass(settings.namespace + '_form_data')
564
- .appendTo(form);
565
- });
566
- input
567
- .attr('name', getFieldName(settings))
568
- .appendTo(form);
569
- },
654
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
655
+ options.jqXHR = jqXHR;
656
+ options.textStatus = textStatus;
657
+ options.errorThrown = errorThrown;
658
+ this._trigger('fail', null, options);
659
+ if (options.recalculateProgress) {
660
+ // Remove the failed (error or abort) file upload from
661
+ // the global progress calculation:
662
+ this._loaded -= options.loaded || options.uploadedBytes || 0;
663
+ this._total -= options.total || this._getTotal(options.files);
664
+ }
665
+ },
570
666
 
571
- legacyUploadFormDataReset = function (input, form, settings) {
572
- input.detach();
573
- form.find('.' + settings.namespace + '_disabled')
574
- .removeAttr('disabled')
575
- .removeClass(settings.namespace + '_disabled');
576
- form.find('.' + settings.namespace + '_form_data').remove();
577
- },
667
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
668
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
669
+ // options object via done and fail callbacks
670
+ this._active -= 1;
671
+ this._trigger('always', null, options);
672
+ if (this._active === 0) {
673
+ // The stop callback is triggered when all uploads have
674
+ // been completed, equivalent to the global ajaxStop event:
675
+ this._trigger('stop');
676
+ // Reset the global progress values:
677
+ this._loaded = this._total = 0;
678
+ this._bitrateTimer = null;
679
+ }
680
+ },
578
681
 
579
- legacyUpload = function (event, files, input, form, iframe, settings, index) {
580
- var send;
682
+ _onSend: function (e, data) {
683
+ var that = this,
684
+ jqXHR,
685
+ aborted,
686
+ slot,
687
+ pipe,
688
+ options = that._getAJAXSettings(data),
581
689
  send = function () {
582
- if (typeof settings.onSend === func && settings.onSend(event, files, index, iframe, settings) === false) {
583
- completeNext();
584
- return;
585
- }
586
- var originalAction = form.attr('action'),
587
- originalMethod = form.attr('method'),
588
- originalTarget = form.attr('target');
589
- iframe
590
- .unbind('abort')
591
- .bind('abort', function (e) {
592
- iframe.readyState = 0;
593
- // javascript:false as iframe src prevents warning popups on HTTPS in IE6
594
- // concat is used here to prevent the "Script URL" JSLint error:
595
- iframe.unbind('load').attr('src', 'javascript'.concat(':false;'));
596
- handleLegacyGlobalProgress(e, files, index, iframe, settings);
597
- if (typeof settings.onAbort === func) {
598
- settings.onAbort(e, files, index, iframe, settings);
599
- }
600
- completeNext();
601
- })
602
- .unbind('load')
603
- .bind('load', function (e) {
604
- iframe.readyState = 4;
605
- handleLegacyGlobalProgress(e, files, index, iframe, settings);
606
- if (typeof settings.onLoad === func) {
607
- settings.onLoad(e, files, index, iframe, settings);
690
+ that._sending += 1;
691
+ // Set timer for bitrate progress calculation:
692
+ options._bitrateTimer = new that._BitrateTimer();
693
+ jqXHR = jqXHR || (
694
+ ((aborted || that._trigger('send', e, options) === false) &&
695
+ that._getXHRPromise(false, options.context, aborted)) ||
696
+ that._chunkedUpload(options) || $.ajax(options)
697
+ ).done(function (result, textStatus, jqXHR) {
698
+ that._onDone(result, textStatus, jqXHR, options);
699
+ }).fail(function (jqXHR, textStatus, errorThrown) {
700
+ that._onFail(jqXHR, textStatus, errorThrown, options);
701
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
702
+ that._sending -= 1;
703
+ that._onAlways(
704
+ jqXHRorResult,
705
+ textStatus,
706
+ jqXHRorError,
707
+ options
708
+ );
709
+ if (options.limitConcurrentUploads &&
710
+ options.limitConcurrentUploads > that._sending) {
711
+ // Start the next queued upload,
712
+ // that has not been aborted:
713
+ var nextSlot = that._slots.shift(),
714
+ isPending;
715
+ while (nextSlot) {
716
+ // jQuery 1.6 doesn't provide .state(),
717
+ // while jQuery 1.8+ removed .isRejected():
718
+ isPending = nextSlot.state ?
719
+ nextSlot.state() === 'pending' :
720
+ !nextSlot.isRejected();
721
+ if (isPending) {
722
+ nextSlot.resolve();
723
+ break;
724
+ }
725
+ nextSlot = that._slots.shift();
608
726
  }
609
- // Fix for IE endless progress bar activity bug
610
- // (happens on form submits to iframe targets):
611
- $('<iframe src="javascript:false;" style="display:none;"></iframe>')
612
- .appendTo(form).remove();
613
- completeNext();
614
- });
615
- form
616
- .attr('action', getUrl(settings))
617
- .attr('method', getMethod(settings))
618
- .attr('target', iframe.attr('name'));
619
- legacyUploadFormDataInit(input, form, settings);
620
- iframe.readyState = 2;
621
- form.get(0).submit();
622
- legacyUploadFormDataReset(input, form, settings);
623
- form
624
- .attr('action', originalAction)
625
- .attr('method', originalMethod)
626
- .attr('target', originalTarget);
727
+ }
728
+ });
729
+ return jqXHR;
627
730
  };
628
- multiLoader.push([files, index, iframe, settings]);
629
- if (settings.sequentialUploads) {
630
- sequenceHandler.push(send);
731
+ this._beforeSend(e, options);
732
+ if (this.options.sequentialUploads ||
733
+ (this.options.limitConcurrentUploads &&
734
+ this.options.limitConcurrentUploads <= this._sending)) {
735
+ if (this.options.limitConcurrentUploads > 1) {
736
+ slot = $.Deferred();
737
+ this._slots.push(slot);
738
+ pipe = slot.pipe(send);
631
739
  } else {
632
- send();
740
+ pipe = (this._sequence = this._sequence.pipe(send, send));
633
741
  }
634
- },
742
+ // Return the piped Promise object, enhanced with an abort method,
743
+ // which is delegated to the jqXHR object of the current upload,
744
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
745
+ pipe.abort = function () {
746
+ aborted = [undefined, 'abort', 'abort'];
747
+ if (!jqXHR) {
748
+ if (slot) {
749
+ slot.rejectWith(options.context, aborted);
750
+ }
751
+ return send();
752
+ }
753
+ return jqXHR.abort();
754
+ };
755
+ return this._enhancePromise(pipe);
756
+ }
757
+ return send();
758
+ },
635
759
 
636
- handleLegacyUpload = function (event, input, form, index) {
637
- if (!(event && input && form)) {
638
- $.error('Iframe based File Upload requires a file input change event');
639
- return;
760
+ _onAdd: function (e, data) {
761
+ var that = this,
762
+ result = true,
763
+ options = $.extend({}, this.options, data),
764
+ limit = options.limitMultiFileUploads,
765
+ paramName = this._getParamName(options),
766
+ paramNameSet,
767
+ paramNameSlice,
768
+ fileSet,
769
+ i;
770
+ if (!(options.singleFileUploads || limit) ||
771
+ !this._isXHRUpload(options)) {
772
+ fileSet = [data.files];
773
+ paramNameSet = [paramName];
774
+ } else if (!options.singleFileUploads && limit) {
775
+ fileSet = [];
776
+ paramNameSet = [];
777
+ for (i = 0; i < data.files.length; i += limit) {
778
+ fileSet.push(data.files.slice(i, i + limit));
779
+ paramNameSlice = paramName.slice(i, i + limit);
780
+ if (!paramNameSlice.length) {
781
+ paramNameSlice = paramName;
782
+ }
783
+ paramNameSet.push(paramNameSlice);
640
784
  }
641
- // javascript:false as iframe src prevents warning popups on HTTPS in IE6:
642
- var iframe = $('<iframe src="javascript:false;" style="display:none;" name="iframe_' +
643
- settings.namespace + '_' + (new Date()).getTime() + '"></iframe>'),
644
- uploadSettings = $.extend({}, settings),
645
- files = event.target && event.target.files;
646
- files = files ? Array.prototype.slice.call(files, 0) : [{name: input.val(), type: null, size: null}];
647
- index = files.length === 1 ? 0 : index;
648
- uploadSettings.fileInput = input;
649
- uploadSettings.uploadForm = form;
650
- iframe.readyState = 0;
651
- iframe.abort = function () {
652
- iframe.trigger('abort');
785
+ } else {
786
+ paramNameSet = paramName;
787
+ }
788
+ data.originalFiles = data.files;
789
+ $.each(fileSet || data.files, function (index, element) {
790
+ var newData = $.extend({}, data);
791
+ newData.files = fileSet ? element : [element];
792
+ newData.paramName = paramNameSet[index];
793
+ newData.submit = function () {
794
+ newData.jqXHR = this.jqXHR =
795
+ (that._trigger('submit', e, this) !== false) &&
796
+ that._onSend(e, this);
797
+ return this.jqXHR;
653
798
  };
654
- iframe.bind('load', function () {
655
- iframe.unbind('load');
656
- if (typeof uploadSettings.initUpload === func) {
657
- uploadSettings.initUpload(
658
- event,
659
- files,
660
- index,
661
- iframe,
662
- uploadSettings,
663
- function () {
664
- legacyUpload(event, files, input, form, iframe, uploadSettings, index);
665
- }
666
- );
667
- } else {
668
- legacyUpload(event, files, input, form, iframe, uploadSettings, index);
799
+ result = that._trigger('add', e, newData);
800
+ return result;
801
+ });
802
+ return result;
803
+ },
804
+
805
+ _replaceFileInput: function (input) {
806
+ var inputClone = input.clone(true);
807
+ $('<form></form>').append(inputClone)[0].reset();
808
+ // Detaching allows to insert the fileInput on another form
809
+ // without loosing the file input value:
810
+ input.after(inputClone).detach();
811
+ // Avoid memory leaks with the detached file input:
812
+ $.cleanData(input.unbind('remove'));
813
+ // Replace the original file input element in the fileInput
814
+ // elements set with the clone, which has been copied including
815
+ // event handlers:
816
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
817
+ if (el === input[0]) {
818
+ return inputClone[0];
819
+ }
820
+ return el;
821
+ });
822
+ // If the widget has been initialized on the file input itself,
823
+ // override this.element with the file input clone:
824
+ if (input[0] === this.element[0]) {
825
+ this.element = inputClone;
826
+ }
827
+ },
828
+
829
+ _handleFileTreeEntry: function (entry, path) {
830
+ var that = this,
831
+ dfd = $.Deferred(),
832
+ errorHandler = function (e) {
833
+ if (e && !e.entry) {
834
+ e.entry = entry;
669
835
  }
670
- }).appendTo(form);
671
- },
836
+ // Since $.when returns immediately if one
837
+ // Deferred is rejected, we use resolve instead.
838
+ // This allows valid files and invalid items
839
+ // to be returned together in one set:
840
+ dfd.resolve([e]);
841
+ },
842
+ dirReader;
843
+ path = path || '';
844
+ if (entry.isFile) {
845
+ if (entry._file) {
846
+ // Workaround for Chrome bug #149735
847
+ entry._file.relativePath = path;
848
+ dfd.resolve(entry._file);
849
+ } else {
850
+ entry.file(function (file) {
851
+ file.relativePath = path;
852
+ dfd.resolve(file);
853
+ }, errorHandler);
854
+ }
855
+ } else if (entry.isDirectory) {
856
+ dirReader = entry.createReader();
857
+ dirReader.readEntries(function (entries) {
858
+ that._handleFileTreeEntries(
859
+ entries,
860
+ path + entry.name + '/'
861
+ ).done(function (files) {
862
+ dfd.resolve(files);
863
+ }).fail(errorHandler);
864
+ }, errorHandler);
865
+ } else {
866
+ // Return an empy list for file system items
867
+ // other than files or directories:
868
+ dfd.resolve([]);
869
+ }
870
+ return dfd.promise();
871
+ },
872
+
873
+ _handleFileTreeEntries: function (entries, path) {
874
+ var that = this;
875
+ return $.when.apply(
876
+ $,
877
+ $.map(entries, function (entry) {
878
+ return that._handleFileTreeEntry(entry, path);
879
+ })
880
+ ).pipe(function () {
881
+ return Array.prototype.concat.apply(
882
+ [],
883
+ arguments
884
+ );
885
+ });
886
+ },
672
887
 
673
- canHandleXHRUploadSize = function (files) {
674
- var bytes = 0,
675
- totalBytes = 0,
676
- i;
677
- if (settings.multipart && typeof FormData === undef) {
678
- for (i = 0; i < files.length; i += 1) {
679
- bytes = files[i].size;
680
- if (bytes > settings.maxFileReaderSize) {
681
- return false;
888
+ _getDroppedFiles: function (dataTransfer) {
889
+ dataTransfer = dataTransfer || {};
890
+ var items = dataTransfer.items;
891
+ if (items && items.length && (items[0].webkitGetAsEntry ||
892
+ items[0].getAsEntry)) {
893
+ return this._handleFileTreeEntries(
894
+ $.map(items, function (item) {
895
+ var entry;
896
+ if (item.webkitGetAsEntry) {
897
+ entry = item.webkitGetAsEntry();
898
+ if (entry) {
899
+ // Workaround for Chrome bug #149735:
900
+ entry._file = item.getAsFile();
901
+ }
902
+ return entry;
682
903
  }
683
- totalBytes += bytes;
684
- }
685
- if (settings.multiFileRequest && totalBytes > settings.maxFileReaderSize) {
686
- return false;
687
- }
904
+ return item.getAsEntry();
905
+ })
906
+ );
907
+ }
908
+ return $.Deferred().resolve(
909
+ $.makeArray(dataTransfer.files)
910
+ ).promise();
911
+ },
912
+
913
+ _getSingleFileInputFiles: function (fileInput) {
914
+ fileInput = $(fileInput);
915
+ var entries = fileInput.prop('webkitEntries') ||
916
+ fileInput.prop('entries'),
917
+ files,
918
+ value;
919
+ if (entries && entries.length) {
920
+ return this._handleFileTreeEntries(entries);
921
+ }
922
+ files = $.makeArray(fileInput.prop('files'));
923
+ if (!files.length) {
924
+ value = fileInput.prop('value');
925
+ if (!value) {
926
+ return $.Deferred().resolve([]).promise();
688
927
  }
689
- return true;
690
- },
928
+ // If the files property is not available, the browser does not
929
+ // support the File API and we add a pseudo File object with
930
+ // the input value as name with path information removed:
931
+ files = [{name: value.replace(/^.*\\/, '')}];
932
+ } else if (files[0].name === undefined && files[0].fileName) {
933
+ // File normalization for Safari 4 and Firefox 3:
934
+ $.each(files, function (index, file) {
935
+ file.name = file.fileName;
936
+ file.size = file.fileSize;
937
+ });
938
+ }
939
+ return $.Deferred().resolve(files).promise();
940
+ },
691
941
 
692
- handleFiles = function (event, files, input, form) {
693
- if (!canHandleXHRUploadSize(files)) {
694
- handleLegacyUpload(event, input, form);
695
- return;
942
+ _getFileInputFiles: function (fileInput) {
943
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
944
+ return this._getSingleFileInputFiles(fileInput);
945
+ }
946
+ return $.when.apply(
947
+ $,
948
+ $.map(fileInput, this._getSingleFileInputFiles)
949
+ ).pipe(function () {
950
+ return Array.prototype.concat.apply(
951
+ [],
952
+ arguments
953
+ );
954
+ });
955
+ },
956
+
957
+ _onChange: function (e) {
958
+ var that = this,
959
+ data = {
960
+ fileInput: $(e.target),
961
+ form: $(e.target.form)
962
+ };
963
+ this._getFileInputFiles(data.fileInput).always(function (files) {
964
+ data.files = files;
965
+ if (that.options.replaceFileInput) {
966
+ that._replaceFileInput(data.fileInput);
696
967
  }
697
- var i;
698
- files = Array.prototype.slice.call(files, 0);
699
- if (settings.multiFileRequest && settings.multipart && files.length) {
700
- handleUpload(event, files, input, form);
701
- } else {
702
- for (i = 0; i < files.length; i += 1) {
703
- handleUpload(event, files, input, form, i);
704
- }
968
+ if (that._trigger('change', e, data) !== false) {
969
+ that._onAdd(e, data);
705
970
  }
706
- },
707
-
708
- initUploadForm = function () {
709
- uploadForm = (container.is('form') ? container : container.find('form'))
710
- .filter(settings.uploadFormFilter);
711
- },
712
-
713
- initFileInput = function () {
714
- fileInput = (uploadForm.length ? uploadForm : container).find('input:file')
715
- .filter(settings.fileInputFilter);
716
- },
717
-
718
- replaceFileInput = function (input) {
719
- var inputClone = input.clone(true);
720
- $('<form/>').append(inputClone).get(0).reset();
721
- input.after(inputClone).detach();
722
- initFileInput();
723
- };
971
+ });
972
+ },
724
973
 
725
- this.onDocumentDragOver = function (e) {
726
- if (typeof settings.onDocumentDragOver === func &&
727
- settings.onDocumentDragOver(e) === false) {
974
+ _onPaste: function (e) {
975
+ var cbd = e.originalEvent.clipboardData,
976
+ items = (cbd && cbd.items) || [],
977
+ data = {files: []};
978
+ $.each(items, function (index, item) {
979
+ var file = item.getAsFile && item.getAsFile();
980
+ if (file) {
981
+ data.files.push(file);
982
+ }
983
+ });
984
+ if (this._trigger('paste', e, data) === false ||
985
+ this._onAdd(e, data) === false) {
728
986
  return false;
729
987
  }
730
- e.preventDefault();
731
- };
732
-
733
- this.onDocumentDrop = function (e) {
734
- if (typeof settings.onDocumentDrop === func &&
735
- settings.onDocumentDrop(e) === false) {
736
- return false;
988
+ },
989
+
990
+ _onDrop: function (e) {
991
+ var that = this,
992
+ dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
993
+ data = {};
994
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
995
+ e.preventDefault();
737
996
  }
738
- e.preventDefault();
739
- };
997
+ this._getDroppedFiles(dataTransfer).always(function (files) {
998
+ data.files = files;
999
+ if (that._trigger('drop', e, data) !== false) {
1000
+ that._onAdd(e, data);
1001
+ }
1002
+ });
1003
+ },
740
1004
 
741
- this.onDragOver = function (e) {
742
- if (typeof settings.onDragOver === func &&
743
- settings.onDragOver(e) === false) {
1005
+ _onDragOver: function (e) {
1006
+ var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
1007
+ if (this._trigger('dragover', e) === false) {
744
1008
  return false;
745
1009
  }
746
- var dataTransfer = e.originalEvent.dataTransfer;
747
- if (dataTransfer && dataTransfer.files) {
748
- dataTransfer.dropEffect = dataTransfer.effectAllowed = 'copy';
1010
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) {
1011
+ dataTransfer.dropEffect = 'copy';
749
1012
  e.preventDefault();
750
1013
  }
751
- };
1014
+ },
752
1015
 
753
- this.onDrop = function (e) {
754
- if (typeof settings.onDrop === func &&
755
- settings.onDrop(e) === false) {
756
- return false;
1016
+ _initEventHandlers: function () {
1017
+ if (this._isXHRUpload(this.options)) {
1018
+ this._on(this.options.dropZone, {
1019
+ dragover: this._onDragOver,
1020
+ drop: this._onDrop
1021
+ });
1022
+ this._on(this.options.pasteZone, {
1023
+ paste: this._onPaste
1024
+ });
1025
+ }
1026
+ this._on(this.options.fileInput, {
1027
+ change: this._onChange
1028
+ });
1029
+ },
1030
+
1031
+ _destroyEventHandlers: function () {
1032
+ this._off(this.options.dropZone, 'dragover drop');
1033
+ this._off(this.options.pasteZone, 'paste');
1034
+ this._off(this.options.fileInput, 'change');
1035
+ },
1036
+
1037
+ _setOption: function (key, value) {
1038
+ var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
1039
+ if (refresh) {
1040
+ this._destroyEventHandlers();
757
1041
  }
758
- var dataTransfer = e.originalEvent.dataTransfer;
759
- if (dataTransfer && dataTransfer.files && isXHRUploadCapable()) {
760
- handleFiles(e, dataTransfer.files);
1042
+ this._super(key, value);
1043
+ if (refresh) {
1044
+ this._initSpecialOptions();
1045
+ this._initEventHandlers();
761
1046
  }
762
- e.preventDefault();
763
- };
764
-
765
- this.onChange = function (e) {
766
- if (typeof settings.onChange === func &&
767
- settings.onChange(e) === false) {
768
- return false;
1047
+ },
1048
+
1049
+ _initSpecialOptions: function () {
1050
+ var options = this.options;
1051
+ if (options.fileInput === undefined) {
1052
+ options.fileInput = this.element.is('input[type="file"]') ?
1053
+ this.element : this.element.find('input[type="file"]');
1054
+ } else if (!(options.fileInput instanceof $)) {
1055
+ options.fileInput = $(options.fileInput);
769
1056
  }
770
- var input = $(e.target),
771
- form = $(e.target.form);
772
- if (form.length === 1) {
773
- input.data(defaultNamespace + '_form', form);
774
- replaceFileInput(input);
775
- } else {
776
- form = input.data(defaultNamespace + '_form');
1057
+ if (!(options.dropZone instanceof $)) {
1058
+ options.dropZone = $(options.dropZone);
777
1059
  }
778
- if (!settings.forceIframeUpload && e.target.files && isXHRUploadCapable()) {
779
- handleFiles(e, e.target.files, input, form);
780
- } else {
781
- handleLegacyUpload(e, input, form);
1060
+ if (!(options.pasteZone instanceof $)) {
1061
+ options.pasteZone = $(options.pasteZone);
782
1062
  }
783
- };
1063
+ },
784
1064
 
785
- this.init = function (options) {
786
- if (options) {
787
- $.extend(settings, options);
788
- optionsReference = options;
789
- }
790
- initUploadForm();
791
- initFileInput();
792
- if (container.data(settings.namespace)) {
793
- $.error('FileUpload with namespace "' + settings.namespace + '" already assigned to this element');
1065
+ _create: function () {
1066
+ var options = this.options;
1067
+ // Initialize options set via HTML5 data-attributes:
1068
+ $.extend(options, $(this.element[0].cloneNode(false)).data());
1069
+ this._initSpecialOptions();
1070
+ this._slots = [];
1071
+ this._sequence = this._getXHRPromise(true);
1072
+ this._sending = this._active = this._loaded = this._total = 0;
1073
+ this._initEventHandlers();
1074
+ },
1075
+
1076
+ _destroy: function () {
1077
+ this._destroyEventHandlers();
1078
+ },
1079
+
1080
+ // This method is exposed to the widget API and allows adding files
1081
+ // using the fileupload API. The data parameter accepts an object which
1082
+ // must have a files property and can contain additional options:
1083
+ // .fileupload('add', {files: filesList});
1084
+ add: function (data) {
1085
+ var that = this;
1086
+ if (!data || this.options.disabled) {
794
1087
  return;
795
1088
  }
796
- container
797
- .data(settings.namespace, fileUpload)
798
- .addClass(settings.cssClass);
799
- settings.dropZone.not(container).addClass(settings.cssClass);
800
- initEventHandlers();
801
- if (typeof settings.init === func) {
802
- settings.init();
803
- }
804
- };
805
-
806
- this.options = function (options) {
807
- var oldCssClass,
808
- oldDropZone,
809
- uploadFormFilterUpdate,
810
- fileInputFilterUpdate;
811
- if (typeof options === undef) {
812
- return $.extend({}, settings);
813
- }
814
- if (optionsReference) {
815
- $.extend(optionsReference, options);
816
- }
817
- removeEventHandlers();
818
- $.each(options, function (name, value) {
819
- switch (name) {
820
- case 'namespace':
821
- $.error('The FileUpload namespace cannot be updated.');
822
- return;
823
- case 'uploadFormFilter':
824
- uploadFormFilterUpdate = true;
825
- fileInputFilterUpdate = true;
826
- break;
827
- case 'fileInputFilter':
828
- fileInputFilterUpdate = true;
829
- break;
830
- case 'cssClass':
831
- oldCssClass = settings.cssClass;
832
- break;
833
- case 'dropZone':
834
- oldDropZone = settings.dropZone;
835
- break;
836
- }
837
- settings[name] = value;
838
- });
839
- if (uploadFormFilterUpdate) {
840
- initUploadForm();
841
- }
842
- if (fileInputFilterUpdate) {
843
- initFileInput();
844
- }
845
- if (typeof oldCssClass !== undef) {
846
- container
847
- .removeClass(oldCssClass)
848
- .addClass(settings.cssClass);
849
- (oldDropZone ? oldDropZone : settings.dropZone).not(container)
850
- .removeClass(oldCssClass);
851
- settings.dropZone.not(container).addClass(settings.cssClass);
852
- } else if (oldDropZone) {
853
- oldDropZone.not(container).removeClass(settings.cssClass);
854
- settings.dropZone.not(container).addClass(settings.cssClass);
855
- }
856
- initEventHandlers();
857
- };
858
-
859
- this.option = function (name, value) {
860
- var options;
861
- if (typeof value === undef) {
862
- return settings[name];
863
- }
864
- options = {};
865
- options[name] = value;
866
- fileUpload.options(options);
867
- };
868
-
869
- this.destroy = function () {
870
- if (typeof settings.destroy === func) {
871
- settings.destroy();
872
- }
873
- removeEventHandlers();
874
- container
875
- .removeData(settings.namespace)
876
- .removeClass(settings.cssClass);
877
- settings.dropZone.not(container).removeClass(settings.cssClass);
878
- };
879
-
880
- this.upload = function (files) {
881
- if (typeof files.length === undef) {
882
- files = [files];
883
- }
884
- handleFiles(null, files);
885
- };
886
- };
887
-
888
- methods = {
889
- init : function (options) {
890
- return this.each(function () {
891
- (new FileUpload($(this))).init(options);
892
- });
893
- },
894
-
895
- option: function (option, value, namespace) {
896
- namespace = namespace ? namespace : defaultNamespace;
897
- var fileUpload = $(this).data(namespace);
898
- if (fileUpload) {
899
- if (!option) {
900
- return fileUpload.options();
901
- } else if (typeof option === 'string' && typeof value === undef) {
902
- return fileUpload.option(option);
903
- }
1089
+ if (data.fileInput && !data.files) {
1090
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1091
+ data.files = files;
1092
+ that._onAdd(null, data);
1093
+ });
904
1094
  } else {
905
- $.error('No FileUpload with namespace "' + namespace + '" assigned to this element');
1095
+ data.files = $.makeArray(data.files);
1096
+ this._onAdd(null, data);
906
1097
  }
907
- return this.each(function () {
908
- var fu = $(this).data(namespace);
909
- if (fu) {
910
- if (typeof option === 'string') {
911
- fu.option(option, value);
912
- } else {
913
- fu.options(option);
914
- }
915
- } else {
916
- $.error('No FileUpload with namespace "' + namespace + '" assigned to this element');
917
- }
918
- });
919
1098
  },
920
-
921
- destroy: function (namespace) {
922
- namespace = namespace ? namespace : defaultNamespace;
923
- return this.each(function () {
924
- var fileUpload = $(this).data(namespace);
925
- if (fileUpload) {
926
- fileUpload.destroy();
927
- } else {
928
- $.error('No FileUpload with namespace "' + namespace + '" assigned to this element');
1099
+
1100
+ // This method is exposed to the widget API and allows sending files
1101
+ // using the fileupload API. The data parameter accepts an object which
1102
+ // must have a files or fileInput property and can contain additional options:
1103
+ // .fileupload('send', {files: filesList});
1104
+ // The method returns a Promise object for the file upload call.
1105
+ send: function (data) {
1106
+ if (data && !this.options.disabled) {
1107
+ if (data.fileInput && !data.files) {
1108
+ var that = this,
1109
+ dfd = $.Deferred(),
1110
+ promise = dfd.promise(),
1111
+ jqXHR,
1112
+ aborted;
1113
+ promise.abort = function () {
1114
+ aborted = true;
1115
+ if (jqXHR) {
1116
+ return jqXHR.abort();
1117
+ }
1118
+ dfd.reject(null, 'abort', 'abort');
1119
+ return promise;
1120
+ };
1121
+ this._getFileInputFiles(data.fileInput).always(
1122
+ function (files) {
1123
+ if (aborted) {
1124
+ return;
1125
+ }
1126
+ data.files = files;
1127
+ jqXHR = that._onSend(null, data).then(
1128
+ function (result, textStatus, jqXHR) {
1129
+ dfd.resolve(result, textStatus, jqXHR);
1130
+ },
1131
+ function (jqXHR, textStatus, errorThrown) {
1132
+ dfd.reject(jqXHR, textStatus, errorThrown);
1133
+ }
1134
+ );
1135
+ }
1136
+ );
1137
+ return this._enhancePromise(promise);
929
1138
  }
930
- });
931
- },
932
-
933
- upload: function (files, namespace) {
934
- namespace = namespace ? namespace : defaultNamespace;
935
- return this.each(function () {
936
- var fileUpload = $(this).data(namespace);
937
- if (fileUpload) {
938
- fileUpload.upload(files);
939
- } else {
940
- $.error('No FileUpload with namespace "' + namespace + '" assigned to this element');
1139
+ data.files = $.makeArray(data.files);
1140
+ if (data.files.length) {
1141
+ return this._onSend(null, data);
941
1142
  }
942
- });
943
- }
944
- };
945
-
946
- $.fn.fileUpload = function (method) {
947
- if (methods[method]) {
948
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
949
- } else if (typeof method === 'object' || !method) {
950
- return methods.init.apply(this, arguments);
951
- } else {
952
- $.error('Method "' + method + '" does not exist on jQuery.fileUpload');
1143
+ }
1144
+ return this._getXHRPromise(false, data && data.context);
953
1145
  }
954
- };
955
-
956
- }(jQuery));
1146
+
1147
+ });
1148
+
1149
+ }));