avatars_for_rails 0.2.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ }));