kf-jquery-fileupload-rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +77 -0
  3. data/Rakefile +15 -0
  4. data/lib/jquery-fileupload-rails.rb +8 -0
  5. data/lib/jquery/fileupload/rails/engine.rb +8 -0
  6. data/lib/jquery/fileupload/rails/middleware.rb +59 -0
  7. data/lib/jquery/fileupload/rails/upload.rb +3 -0
  8. data/lib/jquery/fileupload/rails/version.rb +7 -0
  9. data/vendor/assets/images/loading.gif +0 -0
  10. data/vendor/assets/images/progressbar.gif +0 -0
  11. data/vendor/assets/javascripts/jquery-fileupload/basic.js +4 -0
  12. data/vendor/assets/javascripts/jquery-fileupload/cors/jquery.postmessage-transport.js +117 -0
  13. data/vendor/assets/javascripts/jquery-fileupload/cors/jquery.xdr-transport.js +86 -0
  14. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload-angular.js +429 -0
  15. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload-audio.js +106 -0
  16. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload-image.js +315 -0
  17. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload-jquery-ui.js +152 -0
  18. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload-process.js +172 -0
  19. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload-ui.js +699 -0
  20. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload-validate.js +119 -0
  21. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload-video.js +106 -0
  22. data/vendor/assets/javascripts/jquery-fileupload/jquery.fileupload.js +1426 -0
  23. data/vendor/assets/javascripts/jquery-fileupload/jquery.iframe-transport.js +214 -0
  24. data/vendor/assets/javascripts/jquery-fileupload/vendor/jquery.ui.widget.js +530 -0
  25. data/vendor/assets/stylesheets/jquery.fileupload-ui.scss +72 -0
  26. metadata +112 -0
@@ -0,0 +1,119 @@
1
+ /*
2
+ * jQuery File Upload Validation Plugin 1.1.2
3
+ * https://github.com/blueimp/jQuery-File-Upload
4
+ *
5
+ * Copyright 2013, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /* global define, window */
13
+
14
+ (function (factory) {
15
+ 'use strict';
16
+ if (typeof define === 'function' && define.amd) {
17
+ // Register as an anonymous AMD module:
18
+ define([
19
+ 'jquery',
20
+ './jquery.fileupload-process'
21
+ ], factory);
22
+ } else {
23
+ // Browser globals:
24
+ factory(
25
+ window.jQuery
26
+ );
27
+ }
28
+ }(function ($) {
29
+ 'use strict';
30
+
31
+ // Append to the default processQueue:
32
+ $.blueimp.fileupload.prototype.options.processQueue.push(
33
+ {
34
+ action: 'validate',
35
+ // Always trigger this action,
36
+ // even if the previous action was rejected:
37
+ always: true,
38
+ // Options taken from the global options map:
39
+ acceptFileTypes: '@',
40
+ maxFileSize: '@',
41
+ minFileSize: '@',
42
+ maxNumberOfFiles: '@',
43
+ disabled: '@disableValidation'
44
+ }
45
+ );
46
+
47
+ // The File Upload Validation plugin extends the fileupload widget
48
+ // with file validation functionality:
49
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
50
+
51
+ options: {
52
+ /*
53
+ // The regular expression for allowed file types, matches
54
+ // against either file type or file name:
55
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
56
+ // The maximum allowed file size in bytes:
57
+ maxFileSize: 10000000, // 10 MB
58
+ // The minimum allowed file size in bytes:
59
+ minFileSize: undefined, // No minimal file size
60
+ // The limit of files to be uploaded:
61
+ maxNumberOfFiles: 10,
62
+ */
63
+
64
+ // Function returning the current number of files,
65
+ // has to be overriden for maxNumberOfFiles validation:
66
+ getNumberOfFiles: $.noop,
67
+
68
+ // Error and info messages:
69
+ messages: {
70
+ maxNumberOfFiles: 'Maximum number of files exceeded',
71
+ acceptFileTypes: 'File type not allowed',
72
+ maxFileSize: 'File is too large',
73
+ minFileSize: 'File is too small'
74
+ }
75
+ },
76
+
77
+ processActions: {
78
+
79
+ validate: function (data, options) {
80
+ if (options.disabled) {
81
+ return data;
82
+ }
83
+ var dfd = $.Deferred(),
84
+ settings = this.options,
85
+ file = data.files[data.index],
86
+ fileSize;
87
+ if (options.minFileSize || options.maxFileSize) {
88
+ fileSize = file.size;
89
+ }
90
+ if ($.type(options.maxNumberOfFiles) === 'number' &&
91
+ (settings.getNumberOfFiles() || 0) + data.files.length >
92
+ options.maxNumberOfFiles) {
93
+ file.error = settings.i18n('maxNumberOfFiles');
94
+ } else if (options.acceptFileTypes &&
95
+ !(options.acceptFileTypes.test(file.type) ||
96
+ options.acceptFileTypes.test(file.name))) {
97
+ file.error = settings.i18n('acceptFileTypes');
98
+ } else if (fileSize > options.maxFileSize) {
99
+ file.error = settings.i18n('maxFileSize');
100
+ } else if ($.type(fileSize) === 'number' &&
101
+ fileSize < options.minFileSize) {
102
+ file.error = settings.i18n('minFileSize');
103
+ } else {
104
+ delete file.error;
105
+ }
106
+ if (file.error || data.files.error) {
107
+ data.files.error = true;
108
+ dfd.rejectWith(this, [data]);
109
+ } else {
110
+ dfd.resolveWith(this, [data]);
111
+ }
112
+ return dfd.promise();
113
+ }
114
+
115
+ }
116
+
117
+ });
118
+
119
+ }));
@@ -0,0 +1,106 @@
1
+ /*
2
+ * jQuery File Upload Video Preview Plugin 1.0.3
3
+ * https://github.com/blueimp/jQuery-File-Upload
4
+ *
5
+ * Copyright 2013, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /* jshint nomen:false */
13
+ /* global define, window, document */
14
+
15
+ (function (factory) {
16
+ 'use strict';
17
+ if (typeof define === 'function' && define.amd) {
18
+ // Register as an anonymous AMD module:
19
+ define([
20
+ 'jquery',
21
+ 'load-image',
22
+ './jquery.fileupload-process'
23
+ ], factory);
24
+ } else {
25
+ // Browser globals:
26
+ factory(
27
+ window.jQuery,
28
+ window.loadImage
29
+ );
30
+ }
31
+ }(function ($, loadImage) {
32
+ 'use strict';
33
+
34
+ // Prepend to the default processQueue:
35
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
36
+ {
37
+ action: 'loadVideo',
38
+ // Use the action as prefix for the "@" options:
39
+ prefix: true,
40
+ fileTypes: '@',
41
+ maxFileSize: '@',
42
+ disabled: '@disableVideoPreview'
43
+ },
44
+ {
45
+ action: 'setVideo',
46
+ name: '@videoPreviewName',
47
+ disabled: '@disableVideoPreview'
48
+ }
49
+ );
50
+
51
+ // The File Upload Video Preview plugin extends the fileupload widget
52
+ // with video preview functionality:
53
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
54
+
55
+ options: {
56
+ // The regular expression for the types of video files to load,
57
+ // matched against the file type:
58
+ loadVideoFileTypes: /^video\/.*$/
59
+ },
60
+
61
+ _videoElement: document.createElement('video'),
62
+
63
+ processActions: {
64
+
65
+ // Loads the video file given via data.files and data.index
66
+ // as video element if the browser supports playing it.
67
+ // Accepts the options fileTypes (regular expression)
68
+ // and maxFileSize (integer) to limit the files to load:
69
+ loadVideo: function (data, options) {
70
+ if (options.disabled) {
71
+ return data;
72
+ }
73
+ var file = data.files[data.index],
74
+ url,
75
+ video;
76
+ if (this._videoElement.canPlayType &&
77
+ this._videoElement.canPlayType(file.type) &&
78
+ ($.type(options.maxFileSize) !== 'number' ||
79
+ file.size <= options.maxFileSize) &&
80
+ (!options.fileTypes ||
81
+ options.fileTypes.test(file.type))) {
82
+ url = loadImage.createObjectURL(file);
83
+ if (url) {
84
+ video = this._videoElement.cloneNode(false);
85
+ video.src = url;
86
+ video.controls = true;
87
+ data.video = video;
88
+ return data;
89
+ }
90
+ }
91
+ return data;
92
+ },
93
+
94
+ // Sets the video element as a property of the file object:
95
+ setVideo: function (data, options) {
96
+ if (data.video && !options.disabled) {
97
+ data.files[data.index][options.name || 'preview'] = data.video;
98
+ }
99
+ return data;
100
+ }
101
+
102
+ }
103
+
104
+ });
105
+
106
+ }));
@@ -0,0 +1,1426 @@
1
+ /*
2
+ * jQuery File Upload Plugin 5.40.1
3
+ * https://github.com/blueimp/jQuery-File-Upload
4
+ *
5
+ * Copyright 2010, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /* jshint nomen:false */
13
+ /* global define, window, document, location, Blob, FormData */
14
+
15
+ (function (factory) {
16
+ 'use strict';
17
+ if (typeof define === 'function' && define.amd) {
18
+ // Register as an anonymous AMD module:
19
+ define([
20
+ 'jquery',
21
+ 'jquery.ui.widget'
22
+ ], factory);
23
+ } else {
24
+ // Browser globals:
25
+ factory(window.jQuery);
26
+ }
27
+ }(function ($) {
28
+ 'use strict';
29
+
30
+ // Detect file input support, based on
31
+ // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
32
+ $.support.fileInput = !(new RegExp(
33
+ // Handle devices which give false positives for the feature detection:
34
+ '(Android (1\\.[0156]|2\\.[01]))' +
35
+ '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
36
+ '|(w(eb)?OSBrowser)|(webOS)' +
37
+ '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
38
+ ).test(window.navigator.userAgent) ||
39
+ // Feature detection for all other devices:
40
+ $('<input type="file">').prop('disabled'));
41
+
42
+ // The FileReader API is not actually used, but works as feature detection,
43
+ // as some Safari versions (5?) support XHR file uploads via the FormData API,
44
+ // but not non-multipart XHR file uploads.
45
+ // window.XMLHttpRequestUpload is not available on IE10, so we check for
46
+ // window.ProgressEvent instead to detect XHR2 file upload capability:
47
+ $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
48
+ $.support.xhrFormDataFileUpload = !!window.FormData;
49
+
50
+ // Detect support for Blob slicing (required for chunked uploads):
51
+ $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
52
+ Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
53
+
54
+ // The fileupload widget listens for change events on file input fields defined
55
+ // via fileInput setting and paste or drop events of the given dropZone.
56
+ // In addition to the default jQuery Widget methods, the fileupload widget
57
+ // exposes the "add" and "send" methods, to add or directly send files using
58
+ // the fileupload API.
59
+ // By default, files added via file input selection, paste, drag & drop or
60
+ // "add" method are uploaded immediately, but it is possible to override
61
+ // the "add" callback option to queue file uploads.
62
+ $.widget('blueimp.fileupload', {
63
+
64
+ options: {
65
+ // The drop target element(s), by the default the complete document.
66
+ // Set to null to disable drag & drop support:
67
+ dropZone: $(document),
68
+ // The paste target element(s), by the default the complete document.
69
+ // Set to null to disable paste support:
70
+ pasteZone: $(document),
71
+ // The file input field(s), that are listened to for change events.
72
+ // If undefined, it is set to the file input fields inside
73
+ // of the widget element on plugin initialization.
74
+ // Set to null to disable the change listener.
75
+ fileInput: undefined,
76
+ // By default, the file input field is replaced with a clone after
77
+ // each input field change event. This is required for iframe transport
78
+ // queues and allows change events to be fired for the same file
79
+ // selection, but can be disabled by setting the following option to false:
80
+ replaceFileInput: true,
81
+ // The parameter name for the file form data (the request argument name).
82
+ // If undefined or empty, the name property of the file input field is
83
+ // used, or "files[]" if the file input name property is also empty,
84
+ // can be a string or an array of strings:
85
+ paramName: undefined,
86
+ // By default, each file of a selection is uploaded using an individual
87
+ // request for XHR type uploads. Set to false to upload file
88
+ // selections in one request each:
89
+ singleFileUploads: true,
90
+ // To limit the number of files uploaded with one XHR request,
91
+ // set the following option to an integer greater than 0:
92
+ limitMultiFileUploads: undefined,
93
+ // The following option limits the number of files uploaded with one
94
+ // XHR request to keep the request size under or equal to the defined
95
+ // limit in bytes:
96
+ limitMultiFileUploadSize: undefined,
97
+ // Multipart file uploads add a number of bytes to each uploaded file,
98
+ // therefore the following option adds an overhead for each file used
99
+ // in the limitMultiFileUploadSize configuration:
100
+ limitMultiFileUploadSizeOverhead: 512,
101
+ // Set the following option to true to issue all file upload requests
102
+ // in a sequential order:
103
+ sequentialUploads: false,
104
+ // To limit the number of concurrent uploads,
105
+ // set the following option to an integer greater than 0:
106
+ limitConcurrentUploads: undefined,
107
+ // Set the following option to true to force iframe transport uploads:
108
+ forceIframeTransport: false,
109
+ // Set the following option to the location of a redirect url on the
110
+ // origin server, for cross-domain iframe transport uploads:
111
+ redirect: undefined,
112
+ // The parameter name for the redirect url, sent as part of the form
113
+ // data and set to 'redirect' if this option is empty:
114
+ redirectParamName: undefined,
115
+ // Set the following option to the location of a postMessage window,
116
+ // to enable postMessage transport uploads:
117
+ postMessage: undefined,
118
+ // By default, XHR file uploads are sent as multipart/form-data.
119
+ // The iframe transport is always using multipart/form-data.
120
+ // Set to false to enable non-multipart XHR uploads:
121
+ multipart: true,
122
+ // To upload large files in smaller chunks, set the following option
123
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
124
+ // or the browser does not support the required Blob API, files will
125
+ // be uploaded as a whole.
126
+ maxChunkSize: undefined,
127
+ // When a non-multipart upload or a chunked multipart upload has been
128
+ // aborted, this option can be used to resume the upload by setting
129
+ // it to the size of the already uploaded bytes. This option is most
130
+ // useful when modifying the options object inside of the "add" or
131
+ // "send" callbacks, as the options are cloned for each file upload.
132
+ uploadedBytes: undefined,
133
+ // By default, failed (abort or error) file uploads are removed from the
134
+ // global progress calculation. Set the following option to false to
135
+ // prevent recalculating the global progress data:
136
+ recalculateProgress: true,
137
+ // Interval in milliseconds to calculate and trigger progress events:
138
+ progressInterval: 100,
139
+ // Interval in milliseconds to calculate progress bitrate:
140
+ bitrateInterval: 500,
141
+ // By default, uploads are started automatically when adding files:
142
+ autoUpload: true,
143
+
144
+ // Error and info messages:
145
+ messages: {
146
+ uploadedBytes: 'Uploaded bytes exceed file size'
147
+ },
148
+
149
+ // Translation function, gets the message key to be translated
150
+ // and an object with context specific data as arguments:
151
+ i18n: function (message, context) {
152
+ message = this.messages[message] || message.toString();
153
+ if (context) {
154
+ $.each(context, function (key, value) {
155
+ message = message.replace('{' + key + '}', value);
156
+ });
157
+ }
158
+ return message;
159
+ },
160
+
161
+ // Additional form data to be sent along with the file uploads can be set
162
+ // using this option, which accepts an array of objects with name and
163
+ // value properties, a function returning such an array, a FormData
164
+ // object (for XHR file uploads), or a simple object.
165
+ // The form of the first fileInput is given as parameter to the function:
166
+ formData: function (form) {
167
+ return form.serializeArray();
168
+ },
169
+
170
+ // The add callback is invoked as soon as files are added to the fileupload
171
+ // widget (via file input selection, drag & drop, paste or add API call).
172
+ // If the singleFileUploads option is enabled, this callback will be
173
+ // called once for each file in the selection for XHR file uploads, else
174
+ // once for each file selection.
175
+ //
176
+ // The upload starts when the submit method is invoked on the data parameter.
177
+ // The data object contains a files property holding the added files
178
+ // and allows you to override plugin options as well as define ajax settings.
179
+ //
180
+ // Listeners for this callback can also be bound the following way:
181
+ // .bind('fileuploadadd', func);
182
+ //
183
+ // data.submit() returns a Promise object and allows to attach additional
184
+ // handlers using jQuery's Deferred callbacks:
185
+ // data.submit().done(func).fail(func).always(func);
186
+ add: function (e, data) {
187
+ if (e.isDefaultPrevented()) {
188
+ return false;
189
+ }
190
+ if (data.autoUpload || (data.autoUpload !== false &&
191
+ $(this).fileupload('option', 'autoUpload'))) {
192
+ data.process().done(function () {
193
+ data.submit();
194
+ });
195
+ }
196
+ },
197
+
198
+ // Other callbacks:
199
+
200
+ // Callback for the submit event of each file upload:
201
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
202
+
203
+ // Callback for the start of each file upload request:
204
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
205
+
206
+ // Callback for successful uploads:
207
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
208
+
209
+ // Callback for failed (abort or error) uploads:
210
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
211
+
212
+ // Callback for completed (success, abort or error) requests:
213
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
214
+
215
+ // Callback for upload progress events:
216
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
217
+
218
+ // Callback for global upload progress events:
219
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
220
+
221
+ // Callback for uploads start, equivalent to the global ajaxStart event:
222
+ // start: function (e) {}, // .bind('fileuploadstart', func);
223
+
224
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
225
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
226
+
227
+ // Callback for change events of the fileInput(s):
228
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
229
+
230
+ // Callback for paste events to the pasteZone(s):
231
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
232
+
233
+ // Callback for drop events of the dropZone(s):
234
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
235
+
236
+ // Callback for dragover events of the dropZone(s):
237
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
238
+
239
+ // Callback for the start of each chunk upload request:
240
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
241
+
242
+ // Callback for successful chunk uploads:
243
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
244
+
245
+ // Callback for failed (abort or error) chunk uploads:
246
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
247
+
248
+ // Callback for completed (success, abort or error) chunk upload requests:
249
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
250
+
251
+ // The plugin options are used as settings object for the ajax calls.
252
+ // The following are jQuery ajax settings required for the file uploads:
253
+ processData: false,
254
+ contentType: false,
255
+ cache: false
256
+ },
257
+
258
+ // A list of options that require reinitializing event listeners and/or
259
+ // special initialization code:
260
+ _specialOptions: [
261
+ 'fileInput',
262
+ 'dropZone',
263
+ 'pasteZone',
264
+ 'multipart',
265
+ 'forceIframeTransport'
266
+ ],
267
+
268
+ _blobSlice: $.support.blobSlice && function () {
269
+ var slice = this.slice || this.webkitSlice || this.mozSlice;
270
+ return slice.apply(this, arguments);
271
+ },
272
+
273
+ _BitrateTimer: function () {
274
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
275
+ this.loaded = 0;
276
+ this.bitrate = 0;
277
+ this.getBitrate = function (now, loaded, interval) {
278
+ var timeDiff = now - this.timestamp;
279
+ if (!this.bitrate || !interval || timeDiff > interval) {
280
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
281
+ this.loaded = loaded;
282
+ this.timestamp = now;
283
+ }
284
+ return this.bitrate;
285
+ };
286
+ },
287
+
288
+ _isXHRUpload: function (options) {
289
+ return !options.forceIframeTransport &&
290
+ ((!options.multipart && $.support.xhrFileUpload) ||
291
+ $.support.xhrFormDataFileUpload);
292
+ },
293
+
294
+ _getFormData: function (options) {
295
+ var formData;
296
+ if ($.type(options.formData) === 'function') {
297
+ return options.formData(options.form);
298
+ }
299
+ if ($.isArray(options.formData)) {
300
+ return options.formData;
301
+ }
302
+ if ($.type(options.formData) === 'object') {
303
+ formData = [];
304
+ $.each(options.formData, function (name, value) {
305
+ formData.push({name: name, value: value});
306
+ });
307
+ return formData;
308
+ }
309
+ return [];
310
+ },
311
+
312
+ _getTotal: function (files) {
313
+ var total = 0;
314
+ $.each(files, function (index, file) {
315
+ total += file.size || 1;
316
+ });
317
+ return total;
318
+ },
319
+
320
+ _initProgressObject: function (obj) {
321
+ var progress = {
322
+ loaded: 0,
323
+ total: 0,
324
+ bitrate: 0
325
+ };
326
+ if (obj._progress) {
327
+ $.extend(obj._progress, progress);
328
+ } else {
329
+ obj._progress = progress;
330
+ }
331
+ },
332
+
333
+ _initResponseObject: function (obj) {
334
+ var prop;
335
+ if (obj._response) {
336
+ for (prop in obj._response) {
337
+ if (obj._response.hasOwnProperty(prop)) {
338
+ delete obj._response[prop];
339
+ }
340
+ }
341
+ } else {
342
+ obj._response = {};
343
+ }
344
+ },
345
+
346
+ _onProgress: function (e, data) {
347
+ if (e.lengthComputable) {
348
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
349
+ loaded;
350
+ if (data._time && data.progressInterval &&
351
+ (now - data._time < data.progressInterval) &&
352
+ e.loaded !== e.total) {
353
+ return;
354
+ }
355
+ data._time = now;
356
+ loaded = Math.floor(
357
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
358
+ ) + (data.uploadedBytes || 0);
359
+ // Add the difference from the previously loaded state
360
+ // to the global loaded counter:
361
+ this._progress.loaded += (loaded - data._progress.loaded);
362
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
363
+ now,
364
+ this._progress.loaded,
365
+ data.bitrateInterval
366
+ );
367
+ data._progress.loaded = data.loaded = loaded;
368
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
369
+ now,
370
+ loaded,
371
+ data.bitrateInterval
372
+ );
373
+ // Trigger a custom progress event with a total data property set
374
+ // to the file size(s) of the current upload and a loaded data
375
+ // property calculated accordingly:
376
+ this._trigger(
377
+ 'progress',
378
+ $.Event('progress', {delegatedEvent: e}),
379
+ data
380
+ );
381
+ // Trigger a global progress event for all current file uploads,
382
+ // including ajax calls queued for sequential file uploads:
383
+ this._trigger(
384
+ 'progressall',
385
+ $.Event('progressall', {delegatedEvent: e}),
386
+ this._progress
387
+ );
388
+ }
389
+ },
390
+
391
+ _initProgressListener: function (options) {
392
+ var that = this,
393
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
394
+ // Accesss to the native XHR object is required to add event listeners
395
+ // for the upload progress event:
396
+ if (xhr.upload) {
397
+ $(xhr.upload).bind('progress', function (e) {
398
+ var oe = e.originalEvent;
399
+ // Make sure the progress event properties get copied over:
400
+ e.lengthComputable = oe.lengthComputable;
401
+ e.loaded = oe.loaded;
402
+ e.total = oe.total;
403
+ that._onProgress(e, options);
404
+ });
405
+ options.xhr = function () {
406
+ return xhr;
407
+ };
408
+ }
409
+ },
410
+
411
+ _isInstanceOf: function (type, obj) {
412
+ // Cross-frame instanceof check
413
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
414
+ },
415
+
416
+ _initXHRData: function (options) {
417
+ var that = this,
418
+ formData,
419
+ file = options.files[0],
420
+ // Ignore non-multipart setting if not supported:
421
+ multipart = options.multipart || !$.support.xhrFileUpload,
422
+ paramName = $.type(options.paramName) === 'array' ?
423
+ options.paramName[0] : options.paramName;
424
+ options.headers = $.extend({}, options.headers);
425
+ if (options.contentRange) {
426
+ options.headers['Content-Range'] = options.contentRange;
427
+ }
428
+ if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
429
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
430
+ encodeURI(file.name) + '"';
431
+ }
432
+ if (!multipart) {
433
+ options.contentType = file.type || 'application/octet-stream';
434
+ options.data = options.blob || file;
435
+ } else if ($.support.xhrFormDataFileUpload) {
436
+ if (options.postMessage) {
437
+ // window.postMessage does not allow sending FormData
438
+ // objects, so we just add the File/Blob objects to
439
+ // the formData array and let the postMessage window
440
+ // create the FormData object out of this array:
441
+ formData = this._getFormData(options);
442
+ if (options.blob) {
443
+ formData.push({
444
+ name: paramName,
445
+ value: options.blob
446
+ });
447
+ } else {
448
+ $.each(options.files, function (index, file) {
449
+ formData.push({
450
+ name: ($.type(options.paramName) === 'array' &&
451
+ options.paramName[index]) || paramName,
452
+ value: file
453
+ });
454
+ });
455
+ }
456
+ } else {
457
+ if (that._isInstanceOf('FormData', options.formData)) {
458
+ formData = options.formData;
459
+ } else {
460
+ formData = new FormData();
461
+ $.each(this._getFormData(options), function (index, field) {
462
+ formData.append(field.name, field.value);
463
+ });
464
+ }
465
+ if (options.blob) {
466
+ formData.append(paramName, options.blob, file.name);
467
+ } else {
468
+ $.each(options.files, function (index, file) {
469
+ // This check allows the tests to run with
470
+ // dummy objects:
471
+ if (that._isInstanceOf('File', file) ||
472
+ that._isInstanceOf('Blob', file)) {
473
+ formData.append(
474
+ ($.type(options.paramName) === 'array' &&
475
+ options.paramName[index]) || paramName,
476
+ file,
477
+ file.uploadName || file.name
478
+ );
479
+ }
480
+ });
481
+ }
482
+ }
483
+ options.data = formData;
484
+ }
485
+ // Blob reference is not needed anymore, free memory:
486
+ options.blob = null;
487
+ },
488
+
489
+ _initIframeSettings: function (options) {
490
+ var targetHost = $('<a></a>').prop('href', options.url).prop('host');
491
+ // Setting the dataType to iframe enables the iframe transport:
492
+ options.dataType = 'iframe ' + (options.dataType || '');
493
+ // The iframe transport accepts a serialized array as form data:
494
+ options.formData = this._getFormData(options);
495
+ // Add redirect url to form data on cross-domain uploads:
496
+ if (options.redirect && targetHost && targetHost !== location.host) {
497
+ options.formData.push({
498
+ name: options.redirectParamName || 'redirect',
499
+ value: options.redirect
500
+ });
501
+ }
502
+ },
503
+
504
+ _initDataSettings: function (options) {
505
+ if (this._isXHRUpload(options)) {
506
+ if (!this._chunkedUpload(options, true)) {
507
+ if (!options.data) {
508
+ this._initXHRData(options);
509
+ }
510
+ this._initProgressListener(options);
511
+ }
512
+ if (options.postMessage) {
513
+ // Setting the dataType to postmessage enables the
514
+ // postMessage transport:
515
+ options.dataType = 'postmessage ' + (options.dataType || '');
516
+ }
517
+ } else {
518
+ this._initIframeSettings(options);
519
+ }
520
+ },
521
+
522
+ _getParamName: function (options) {
523
+ var fileInput = $(options.fileInput),
524
+ paramName = options.paramName;
525
+ if (!paramName) {
526
+ paramName = [];
527
+ fileInput.each(function () {
528
+ var input = $(this),
529
+ name = input.prop('name') || 'files[]',
530
+ i = (input.prop('files') || [1]).length;
531
+ while (i) {
532
+ paramName.push(name);
533
+ i -= 1;
534
+ }
535
+ });
536
+ if (!paramName.length) {
537
+ paramName = [fileInput.prop('name') || 'files[]'];
538
+ }
539
+ } else if (!$.isArray(paramName)) {
540
+ paramName = [paramName];
541
+ }
542
+ return paramName;
543
+ },
544
+
545
+ _initFormSettings: function (options) {
546
+ // Retrieve missing options from the input field and the
547
+ // associated form, if available:
548
+ if (!options.form || !options.form.length) {
549
+ options.form = $(options.fileInput.prop('form'));
550
+ // If the given file input doesn't have an associated form,
551
+ // use the default widget file input's form:
552
+ if (!options.form.length) {
553
+ options.form = $(this.options.fileInput.prop('form'));
554
+ }
555
+ }
556
+ options.paramName = this._getParamName(options);
557
+ if (!options.url) {
558
+ options.url = options.form.prop('action') || location.href;
559
+ }
560
+ // The HTTP request method must be "POST" or "PUT":
561
+ options.type = (options.type ||
562
+ ($.type(options.form.prop('method')) === 'string' &&
563
+ options.form.prop('method')) || ''
564
+ ).toUpperCase();
565
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
566
+ options.type !== 'PATCH') {
567
+ options.type = 'POST';
568
+ }
569
+ if (!options.formAcceptCharset) {
570
+ options.formAcceptCharset = options.form.attr('accept-charset');
571
+ }
572
+ },
573
+
574
+ _getAJAXSettings: function (data) {
575
+ var options = $.extend({}, this.options, data);
576
+ this._initFormSettings(options);
577
+ this._initDataSettings(options);
578
+ return options;
579
+ },
580
+
581
+ // jQuery 1.6 doesn't provide .state(),
582
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
583
+ _getDeferredState: function (deferred) {
584
+ if (deferred.state) {
585
+ return deferred.state();
586
+ }
587
+ if (deferred.isResolved()) {
588
+ return 'resolved';
589
+ }
590
+ if (deferred.isRejected()) {
591
+ return 'rejected';
592
+ }
593
+ return 'pending';
594
+ },
595
+
596
+ // Maps jqXHR callbacks to the equivalent
597
+ // methods of the given Promise object:
598
+ _enhancePromise: function (promise) {
599
+ promise.success = promise.done;
600
+ promise.error = promise.fail;
601
+ promise.complete = promise.always;
602
+ return promise;
603
+ },
604
+
605
+ // Creates and returns a Promise object enhanced with
606
+ // the jqXHR methods abort, success, error and complete:
607
+ _getXHRPromise: function (resolveOrReject, context, args) {
608
+ var dfd = $.Deferred(),
609
+ promise = dfd.promise();
610
+ context = context || this.options.context || promise;
611
+ if (resolveOrReject === true) {
612
+ dfd.resolveWith(context, args);
613
+ } else if (resolveOrReject === false) {
614
+ dfd.rejectWith(context, args);
615
+ }
616
+ promise.abort = dfd.promise;
617
+ return this._enhancePromise(promise);
618
+ },
619
+
620
+ // Adds convenience methods to the data callback argument:
621
+ _addConvenienceMethods: function (e, data) {
622
+ var that = this,
623
+ getPromise = function (args) {
624
+ return $.Deferred().resolveWith(that, args).promise();
625
+ };
626
+ data.process = function (resolveFunc, rejectFunc) {
627
+ if (resolveFunc || rejectFunc) {
628
+ data._processQueue = this._processQueue =
629
+ (this._processQueue || getPromise([this])).pipe(
630
+ function () {
631
+ if (data.errorThrown) {
632
+ return $.Deferred()
633
+ .rejectWith(that, [data]).promise();
634
+ }
635
+ return getPromise(arguments);
636
+ }
637
+ ).pipe(resolveFunc, rejectFunc);
638
+ }
639
+ return this._processQueue || getPromise([this]);
640
+ };
641
+ data.submit = function () {
642
+ if (this.state() !== 'pending') {
643
+ data.jqXHR = this.jqXHR =
644
+ (that._trigger(
645
+ 'submit',
646
+ $.Event('submit', {delegatedEvent: e}),
647
+ this
648
+ ) !== false) && that._onSend(e, this);
649
+ }
650
+ return this.jqXHR || that._getXHRPromise();
651
+ };
652
+ data.abort = function () {
653
+ if (this.jqXHR) {
654
+ return this.jqXHR.abort();
655
+ }
656
+ this.errorThrown = 'abort';
657
+ that._trigger('fail', null, this);
658
+ return that._getXHRPromise(false);
659
+ };
660
+ data.state = function () {
661
+ if (this.jqXHR) {
662
+ return that._getDeferredState(this.jqXHR);
663
+ }
664
+ if (this._processQueue) {
665
+ return that._getDeferredState(this._processQueue);
666
+ }
667
+ };
668
+ data.processing = function () {
669
+ return !this.jqXHR && this._processQueue && that
670
+ ._getDeferredState(this._processQueue) === 'pending';
671
+ };
672
+ data.progress = function () {
673
+ return this._progress;
674
+ };
675
+ data.response = function () {
676
+ return this._response;
677
+ };
678
+ },
679
+
680
+ // Parses the Range header from the server response
681
+ // and returns the uploaded bytes:
682
+ _getUploadedBytes: function (jqXHR) {
683
+ var range = jqXHR.getResponseHeader('Range'),
684
+ parts = range && range.split('-'),
685
+ upperBytesPos = parts && parts.length > 1 &&
686
+ parseInt(parts[1], 10);
687
+ return upperBytesPos && upperBytesPos + 1;
688
+ },
689
+
690
+ // Uploads a file in multiple, sequential requests
691
+ // by splitting the file up in multiple blob chunks.
692
+ // If the second parameter is true, only tests if the file
693
+ // should be uploaded in chunks, but does not invoke any
694
+ // upload requests:
695
+ _chunkedUpload: function (options, testOnly) {
696
+ options.uploadedBytes = options.uploadedBytes || 0;
697
+ var that = this,
698
+ file = options.files[0],
699
+ fs = file.size,
700
+ ub = options.uploadedBytes,
701
+ mcs = options.maxChunkSize || fs,
702
+ slice = this._blobSlice,
703
+ dfd = $.Deferred(),
704
+ promise = dfd.promise(),
705
+ jqXHR,
706
+ upload;
707
+ if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
708
+ options.data) {
709
+ return false;
710
+ }
711
+ if (testOnly) {
712
+ return true;
713
+ }
714
+ if (ub >= fs) {
715
+ file.error = options.i18n('uploadedBytes');
716
+ return this._getXHRPromise(
717
+ false,
718
+ options.context,
719
+ [null, 'error', file.error]
720
+ );
721
+ }
722
+ // The chunk upload method:
723
+ upload = function () {
724
+ // Clone the options object for each chunk upload:
725
+ var o = $.extend({}, options),
726
+ currentLoaded = o._progress.loaded;
727
+ o.blob = slice.call(
728
+ file,
729
+ ub,
730
+ ub + mcs,
731
+ file.type
732
+ );
733
+ // Store the current chunk size, as the blob itself
734
+ // will be dereferenced after data processing:
735
+ o.chunkSize = o.blob.size;
736
+ // Expose the chunk bytes position range:
737
+ o.contentRange = 'bytes ' + ub + '-' +
738
+ (ub + o.chunkSize - 1) + '/' + fs;
739
+ // Process the upload data (the blob and potential form data):
740
+ that._initXHRData(o);
741
+ // Add progress listeners for this chunk upload:
742
+ that._initProgressListener(o);
743
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
744
+ that._getXHRPromise(false, o.context))
745
+ .done(function (result, textStatus, jqXHR) {
746
+ ub = that._getUploadedBytes(jqXHR) ||
747
+ (ub + o.chunkSize);
748
+ // Create a progress event if no final progress event
749
+ // with loaded equaling total has been triggered
750
+ // for this chunk:
751
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
752
+ that._onProgress($.Event('progress', {
753
+ lengthComputable: true,
754
+ loaded: ub - o.uploadedBytes,
755
+ total: ub - o.uploadedBytes
756
+ }), o);
757
+ }
758
+ options.uploadedBytes = o.uploadedBytes = ub;
759
+ o.result = result;
760
+ o.textStatus = textStatus;
761
+ o.jqXHR = jqXHR;
762
+ that._trigger('chunkdone', null, o);
763
+ that._trigger('chunkalways', null, o);
764
+ if (ub < fs) {
765
+ // File upload not yet complete,
766
+ // continue with the next chunk:
767
+ upload();
768
+ } else {
769
+ dfd.resolveWith(
770
+ o.context,
771
+ [result, textStatus, jqXHR]
772
+ );
773
+ }
774
+ })
775
+ .fail(function (jqXHR, textStatus, errorThrown) {
776
+ o.jqXHR = jqXHR;
777
+ o.textStatus = textStatus;
778
+ o.errorThrown = errorThrown;
779
+ that._trigger('chunkfail', null, o);
780
+ that._trigger('chunkalways', null, o);
781
+ dfd.rejectWith(
782
+ o.context,
783
+ [jqXHR, textStatus, errorThrown]
784
+ );
785
+ });
786
+ };
787
+ this._enhancePromise(promise);
788
+ promise.abort = function () {
789
+ return jqXHR.abort();
790
+ };
791
+ upload();
792
+ return promise;
793
+ },
794
+
795
+ _beforeSend: function (e, data) {
796
+ if (this._active === 0) {
797
+ // the start callback is triggered when an upload starts
798
+ // and no other uploads are currently running,
799
+ // equivalent to the global ajaxStart event:
800
+ this._trigger('start');
801
+ // Set timer for global bitrate progress calculation:
802
+ this._bitrateTimer = new this._BitrateTimer();
803
+ // Reset the global progress values:
804
+ this._progress.loaded = this._progress.total = 0;
805
+ this._progress.bitrate = 0;
806
+ }
807
+ // Make sure the container objects for the .response() and
808
+ // .progress() methods on the data object are available
809
+ // and reset to their initial state:
810
+ this._initResponseObject(data);
811
+ this._initProgressObject(data);
812
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
813
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
814
+ data._progress.bitrate = data.bitrate = 0;
815
+ this._active += 1;
816
+ // Initialize the global progress values:
817
+ this._progress.loaded += data.loaded;
818
+ this._progress.total += data.total;
819
+ },
820
+
821
+ _onDone: function (result, textStatus, jqXHR, options) {
822
+ var total = options._progress.total,
823
+ response = options._response;
824
+ if (options._progress.loaded < total) {
825
+ // Create a progress event if no final progress event
826
+ // with loaded equaling total has been triggered:
827
+ this._onProgress($.Event('progress', {
828
+ lengthComputable: true,
829
+ loaded: total,
830
+ total: total
831
+ }), options);
832
+ }
833
+ response.result = options.result = result;
834
+ response.textStatus = options.textStatus = textStatus;
835
+ response.jqXHR = options.jqXHR = jqXHR;
836
+ this._trigger('done', null, options);
837
+ },
838
+
839
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
840
+ var response = options._response;
841
+ if (options.recalculateProgress) {
842
+ // Remove the failed (error or abort) file upload from
843
+ // the global progress calculation:
844
+ this._progress.loaded -= options._progress.loaded;
845
+ this._progress.total -= options._progress.total;
846
+ }
847
+ response.jqXHR = options.jqXHR = jqXHR;
848
+ response.textStatus = options.textStatus = textStatus;
849
+ response.errorThrown = options.errorThrown = errorThrown;
850
+ this._trigger('fail', null, options);
851
+ },
852
+
853
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
854
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
855
+ // options object via done and fail callbacks
856
+ this._trigger('always', null, options);
857
+ },
858
+
859
+ _onSend: function (e, data) {
860
+ if (!data.submit) {
861
+ this._addConvenienceMethods(e, data);
862
+ }
863
+ var that = this,
864
+ jqXHR,
865
+ aborted,
866
+ slot,
867
+ pipe,
868
+ options = that._getAJAXSettings(data),
869
+ send = function () {
870
+ that._sending += 1;
871
+ // Set timer for bitrate progress calculation:
872
+ options._bitrateTimer = new that._BitrateTimer();
873
+ jqXHR = jqXHR || (
874
+ ((aborted || that._trigger(
875
+ 'send',
876
+ $.Event('send', {delegatedEvent: e}),
877
+ options
878
+ ) === false) &&
879
+ that._getXHRPromise(false, options.context, aborted)) ||
880
+ that._chunkedUpload(options) || $.ajax(options)
881
+ ).done(function (result, textStatus, jqXHR) {
882
+ that._onDone(result, textStatus, jqXHR, options);
883
+ }).fail(function (jqXHR, textStatus, errorThrown) {
884
+ that._onFail(jqXHR, textStatus, errorThrown, options);
885
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
886
+ that._onAlways(
887
+ jqXHRorResult,
888
+ textStatus,
889
+ jqXHRorError,
890
+ options
891
+ );
892
+ that._sending -= 1;
893
+ that._active -= 1;
894
+ if (options.limitConcurrentUploads &&
895
+ options.limitConcurrentUploads > that._sending) {
896
+ // Start the next queued upload,
897
+ // that has not been aborted:
898
+ var nextSlot = that._slots.shift();
899
+ while (nextSlot) {
900
+ if (that._getDeferredState(nextSlot) === 'pending') {
901
+ nextSlot.resolve();
902
+ break;
903
+ }
904
+ nextSlot = that._slots.shift();
905
+ }
906
+ }
907
+ if (that._active === 0) {
908
+ // The stop callback is triggered when all uploads have
909
+ // been completed, equivalent to the global ajaxStop event:
910
+ that._trigger('stop');
911
+ }
912
+ });
913
+ return jqXHR;
914
+ };
915
+ this._beforeSend(e, options);
916
+ if (this.options.sequentialUploads ||
917
+ (this.options.limitConcurrentUploads &&
918
+ this.options.limitConcurrentUploads <= this._sending)) {
919
+ if (this.options.limitConcurrentUploads > 1) {
920
+ slot = $.Deferred();
921
+ this._slots.push(slot);
922
+ pipe = slot.pipe(send);
923
+ } else {
924
+ this._sequence = this._sequence.pipe(send, send);
925
+ pipe = this._sequence;
926
+ }
927
+ // Return the piped Promise object, enhanced with an abort method,
928
+ // which is delegated to the jqXHR object of the current upload,
929
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
930
+ pipe.abort = function () {
931
+ aborted = [undefined, 'abort', 'abort'];
932
+ if (!jqXHR) {
933
+ if (slot) {
934
+ slot.rejectWith(options.context, aborted);
935
+ }
936
+ return send();
937
+ }
938
+ return jqXHR.abort();
939
+ };
940
+ return this._enhancePromise(pipe);
941
+ }
942
+ return send();
943
+ },
944
+
945
+ _onAdd: function (e, data) {
946
+ var that = this,
947
+ result = true,
948
+ options = $.extend({}, this.options, data),
949
+ files = data.files,
950
+ filesLength = files.length,
951
+ limit = options.limitMultiFileUploads,
952
+ limitSize = options.limitMultiFileUploadSize,
953
+ overhead = options.limitMultiFileUploadSizeOverhead,
954
+ batchSize = 0,
955
+ paramName = this._getParamName(options),
956
+ paramNameSet,
957
+ paramNameSlice,
958
+ fileSet,
959
+ i,
960
+ j = 0;
961
+ if (limitSize && (!filesLength || files[0].size === undefined)) {
962
+ limitSize = undefined;
963
+ }
964
+ if (!(options.singleFileUploads || limit || limitSize) ||
965
+ !this._isXHRUpload(options)) {
966
+ fileSet = [files];
967
+ paramNameSet = [paramName];
968
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
969
+ fileSet = [];
970
+ paramNameSet = [];
971
+ for (i = 0; i < filesLength; i += limit) {
972
+ fileSet.push(files.slice(i, i + limit));
973
+ paramNameSlice = paramName.slice(i, i + limit);
974
+ if (!paramNameSlice.length) {
975
+ paramNameSlice = paramName;
976
+ }
977
+ paramNameSet.push(paramNameSlice);
978
+ }
979
+ } else if (!options.singleFileUploads && limitSize) {
980
+ fileSet = [];
981
+ paramNameSet = [];
982
+ for (i = 0; i < filesLength; i = i + 1) {
983
+ batchSize += files[i].size + overhead;
984
+ if (i + 1 === filesLength ||
985
+ ((batchSize + files[i + 1].size + overhead) > limitSize) ||
986
+ (limit && i + 1 - j >= limit)) {
987
+ fileSet.push(files.slice(j, i + 1));
988
+ paramNameSlice = paramName.slice(j, i + 1);
989
+ if (!paramNameSlice.length) {
990
+ paramNameSlice = paramName;
991
+ }
992
+ paramNameSet.push(paramNameSlice);
993
+ j = i + 1;
994
+ batchSize = 0;
995
+ }
996
+ }
997
+ } else {
998
+ paramNameSet = paramName;
999
+ }
1000
+ data.originalFiles = files;
1001
+ $.each(fileSet || files, function (index, element) {
1002
+ var newData = $.extend({}, data);
1003
+ newData.files = fileSet ? element : [element];
1004
+ newData.paramName = paramNameSet[index];
1005
+ that._initResponseObject(newData);
1006
+ that._initProgressObject(newData);
1007
+ that._addConvenienceMethods(e, newData);
1008
+ result = that._trigger(
1009
+ 'add',
1010
+ $.Event('add', {delegatedEvent: e}),
1011
+ newData
1012
+ );
1013
+ return result;
1014
+ });
1015
+ return result;
1016
+ },
1017
+
1018
+ _replaceFileInput: function (input) {
1019
+ var inputClone = input.clone(true);
1020
+ $('<form></form>').append(inputClone)[0].reset();
1021
+ // Detaching allows to insert the fileInput on another form
1022
+ // without loosing the file input value:
1023
+ input.after(inputClone).detach();
1024
+ // Avoid memory leaks with the detached file input:
1025
+ $.cleanData(input.unbind('remove'));
1026
+ // Replace the original file input element in the fileInput
1027
+ // elements set with the clone, which has been copied including
1028
+ // event handlers:
1029
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
1030
+ if (el === input[0]) {
1031
+ return inputClone[0];
1032
+ }
1033
+ return el;
1034
+ });
1035
+ // If the widget has been initialized on the file input itself,
1036
+ // override this.element with the file input clone:
1037
+ if (input[0] === this.element[0]) {
1038
+ this.element = inputClone;
1039
+ }
1040
+ },
1041
+
1042
+ _handleFileTreeEntry: function (entry, path) {
1043
+ var that = this,
1044
+ dfd = $.Deferred(),
1045
+ errorHandler = function (e) {
1046
+ if (e && !e.entry) {
1047
+ e.entry = entry;
1048
+ }
1049
+ // Since $.when returns immediately if one
1050
+ // Deferred is rejected, we use resolve instead.
1051
+ // This allows valid files and invalid items
1052
+ // to be returned together in one set:
1053
+ dfd.resolve([e]);
1054
+ },
1055
+ dirReader;
1056
+ path = path || '';
1057
+ if (entry.isFile) {
1058
+ if (entry._file) {
1059
+ // Workaround for Chrome bug #149735
1060
+ entry._file.relativePath = path;
1061
+ dfd.resolve(entry._file);
1062
+ } else {
1063
+ entry.file(function (file) {
1064
+ file.relativePath = path;
1065
+ dfd.resolve(file);
1066
+ }, errorHandler);
1067
+ }
1068
+ } else if (entry.isDirectory) {
1069
+ dirReader = entry.createReader();
1070
+ dirReader.readEntries(function (entries) {
1071
+ that._handleFileTreeEntries(
1072
+ entries,
1073
+ path + entry.name + '/'
1074
+ ).done(function (files) {
1075
+ dfd.resolve(files);
1076
+ }).fail(errorHandler);
1077
+ }, errorHandler);
1078
+ } else {
1079
+ // Return an empy list for file system items
1080
+ // other than files or directories:
1081
+ dfd.resolve([]);
1082
+ }
1083
+ return dfd.promise();
1084
+ },
1085
+
1086
+ _handleFileTreeEntries: function (entries, path) {
1087
+ var that = this;
1088
+ return $.when.apply(
1089
+ $,
1090
+ $.map(entries, function (entry) {
1091
+ return that._handleFileTreeEntry(entry, path);
1092
+ })
1093
+ ).pipe(function () {
1094
+ return Array.prototype.concat.apply(
1095
+ [],
1096
+ arguments
1097
+ );
1098
+ });
1099
+ },
1100
+
1101
+ _getDroppedFiles: function (dataTransfer) {
1102
+ dataTransfer = dataTransfer || {};
1103
+ var items = dataTransfer.items;
1104
+ if (items && items.length && (items[0].webkitGetAsEntry ||
1105
+ items[0].getAsEntry)) {
1106
+ return this._handleFileTreeEntries(
1107
+ $.map(items, function (item) {
1108
+ var entry;
1109
+ if (item.webkitGetAsEntry) {
1110
+ entry = item.webkitGetAsEntry();
1111
+ if (entry) {
1112
+ // Workaround for Chrome bug #149735:
1113
+ entry._file = item.getAsFile();
1114
+ }
1115
+ return entry;
1116
+ }
1117
+ return item.getAsEntry();
1118
+ })
1119
+ );
1120
+ }
1121
+ return $.Deferred().resolve(
1122
+ $.makeArray(dataTransfer.files)
1123
+ ).promise();
1124
+ },
1125
+
1126
+ _getSingleFileInputFiles: function (fileInput) {
1127
+ fileInput = $(fileInput);
1128
+ var entries = fileInput.prop('webkitEntries') ||
1129
+ fileInput.prop('entries'),
1130
+ files,
1131
+ value;
1132
+ if (entries && entries.length) {
1133
+ return this._handleFileTreeEntries(entries);
1134
+ }
1135
+ files = $.makeArray(fileInput.prop('files'));
1136
+ if (!files.length) {
1137
+ value = fileInput.prop('value');
1138
+ if (!value) {
1139
+ return $.Deferred().resolve([]).promise();
1140
+ }
1141
+ // If the files property is not available, the browser does not
1142
+ // support the File API and we add a pseudo File object with
1143
+ // the input value as name with path information removed:
1144
+ files = [{name: value.replace(/^.*\\/, '')}];
1145
+ } else if (files[0].name === undefined && files[0].fileName) {
1146
+ // File normalization for Safari 4 and Firefox 3:
1147
+ $.each(files, function (index, file) {
1148
+ file.name = file.fileName;
1149
+ file.size = file.fileSize;
1150
+ });
1151
+ }
1152
+ return $.Deferred().resolve(files).promise();
1153
+ },
1154
+
1155
+ _getFileInputFiles: function (fileInput) {
1156
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
1157
+ return this._getSingleFileInputFiles(fileInput);
1158
+ }
1159
+ return $.when.apply(
1160
+ $,
1161
+ $.map(fileInput, this._getSingleFileInputFiles)
1162
+ ).pipe(function () {
1163
+ return Array.prototype.concat.apply(
1164
+ [],
1165
+ arguments
1166
+ );
1167
+ });
1168
+ },
1169
+
1170
+ _onChange: function (e) {
1171
+ var that = this,
1172
+ data = {
1173
+ fileInput: $(e.target),
1174
+ form: $(e.target.form)
1175
+ };
1176
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1177
+ data.files = files;
1178
+ if (that.options.replaceFileInput) {
1179
+ that._replaceFileInput(data.fileInput);
1180
+ }
1181
+ if (that._trigger(
1182
+ 'change',
1183
+ $.Event('change', {delegatedEvent: e}),
1184
+ data
1185
+ ) !== false) {
1186
+ that._onAdd(e, data);
1187
+ }
1188
+ });
1189
+ },
1190
+
1191
+ _onPaste: function (e) {
1192
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
1193
+ e.originalEvent.clipboardData.items,
1194
+ data = {files: []};
1195
+ if (items && items.length) {
1196
+ $.each(items, function (index, item) {
1197
+ var file = item.getAsFile && item.getAsFile();
1198
+ if (file) {
1199
+ data.files.push(file);
1200
+ }
1201
+ });
1202
+ if (this._trigger(
1203
+ 'paste',
1204
+ $.Event('paste', {delegatedEvent: e}),
1205
+ data
1206
+ ) !== false) {
1207
+ this._onAdd(e, data);
1208
+ }
1209
+ }
1210
+ },
1211
+
1212
+ _onDrop: function (e) {
1213
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1214
+ var that = this,
1215
+ dataTransfer = e.dataTransfer,
1216
+ data = {};
1217
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1218
+ e.preventDefault();
1219
+ this._getDroppedFiles(dataTransfer).always(function (files) {
1220
+ data.files = files;
1221
+ if (that._trigger(
1222
+ 'drop',
1223
+ $.Event('drop', {delegatedEvent: e}),
1224
+ data
1225
+ ) !== false) {
1226
+ that._onAdd(e, data);
1227
+ }
1228
+ });
1229
+ }
1230
+ },
1231
+
1232
+ _onDragOver: function (e) {
1233
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1234
+ var dataTransfer = e.dataTransfer;
1235
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
1236
+ this._trigger(
1237
+ 'dragover',
1238
+ $.Event('dragover', {delegatedEvent: e})
1239
+ ) !== false) {
1240
+ e.preventDefault();
1241
+ dataTransfer.dropEffect = 'copy';
1242
+ }
1243
+ },
1244
+
1245
+ _initEventHandlers: function () {
1246
+ if (this._isXHRUpload(this.options)) {
1247
+ this._on(this.options.dropZone, {
1248
+ dragover: this._onDragOver,
1249
+ drop: this._onDrop
1250
+ });
1251
+ this._on(this.options.pasteZone, {
1252
+ paste: this._onPaste
1253
+ });
1254
+ }
1255
+ if ($.support.fileInput) {
1256
+ this._on(this.options.fileInput, {
1257
+ change: this._onChange
1258
+ });
1259
+ }
1260
+ },
1261
+
1262
+ _destroyEventHandlers: function () {
1263
+ this._off(this.options.dropZone, 'dragover drop');
1264
+ this._off(this.options.pasteZone, 'paste');
1265
+ this._off(this.options.fileInput, 'change');
1266
+ },
1267
+
1268
+ _setOption: function (key, value) {
1269
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
1270
+ if (reinit) {
1271
+ this._destroyEventHandlers();
1272
+ }
1273
+ this._super(key, value);
1274
+ if (reinit) {
1275
+ this._initSpecialOptions();
1276
+ this._initEventHandlers();
1277
+ }
1278
+ },
1279
+
1280
+ _initSpecialOptions: function () {
1281
+ var options = this.options;
1282
+ if (options.fileInput === undefined) {
1283
+ options.fileInput = this.element.is('input[type="file"]') ?
1284
+ this.element : this.element.find('input[type="file"]');
1285
+ } else if (!(options.fileInput instanceof $)) {
1286
+ options.fileInput = $(options.fileInput);
1287
+ }
1288
+ if (!(options.dropZone instanceof $)) {
1289
+ options.dropZone = $(options.dropZone);
1290
+ }
1291
+ if (!(options.pasteZone instanceof $)) {
1292
+ options.pasteZone = $(options.pasteZone);
1293
+ }
1294
+ },
1295
+
1296
+ _getRegExp: function (str) {
1297
+ var parts = str.split('/'),
1298
+ modifiers = parts.pop();
1299
+ parts.shift();
1300
+ return new RegExp(parts.join('/'), modifiers);
1301
+ },
1302
+
1303
+ _isRegExpOption: function (key, value) {
1304
+ return key !== 'url' && $.type(value) === 'string' &&
1305
+ /^\/.*\/[igm]{0,3}$/.test(value);
1306
+ },
1307
+
1308
+ _initDataAttributes: function () {
1309
+ var that = this,
1310
+ options = this.options,
1311
+ clone = $(this.element[0].cloneNode(false));
1312
+ // Initialize options set via HTML5 data-attributes:
1313
+ $.each(
1314
+ clone.data(),
1315
+ function (key, value) {
1316
+ var dataAttributeName = 'data-' +
1317
+ // Convert camelCase to hyphen-ated key:
1318
+ key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
1319
+ if (clone.attr(dataAttributeName)) {
1320
+ if (that._isRegExpOption(key, value)) {
1321
+ value = that._getRegExp(value);
1322
+ }
1323
+ options[key] = value;
1324
+ }
1325
+ }
1326
+ );
1327
+ },
1328
+
1329
+ _create: function () {
1330
+ this._initDataAttributes();
1331
+ this._initSpecialOptions();
1332
+ this._slots = [];
1333
+ this._sequence = this._getXHRPromise(true);
1334
+ this._sending = this._active = 0;
1335
+ this._initProgressObject(this);
1336
+ this._initEventHandlers();
1337
+ },
1338
+
1339
+ // This method is exposed to the widget API and allows to query
1340
+ // the number of active uploads:
1341
+ active: function () {
1342
+ return this._active;
1343
+ },
1344
+
1345
+ // This method is exposed to the widget API and allows to query
1346
+ // the widget upload progress.
1347
+ // It returns an object with loaded, total and bitrate properties
1348
+ // for the running uploads:
1349
+ progress: function () {
1350
+ return this._progress;
1351
+ },
1352
+
1353
+ // This method is exposed to the widget API and allows adding files
1354
+ // using the fileupload API. The data parameter accepts an object which
1355
+ // must have a files property and can contain additional options:
1356
+ // .fileupload('add', {files: filesList});
1357
+ add: function (data) {
1358
+ var that = this;
1359
+ if (!data || this.options.disabled) {
1360
+ return;
1361
+ }
1362
+ if (data.fileInput && !data.files) {
1363
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1364
+ data.files = files;
1365
+ that._onAdd(null, data);
1366
+ });
1367
+ } else {
1368
+ data.files = $.makeArray(data.files);
1369
+ this._onAdd(null, data);
1370
+ }
1371
+ },
1372
+
1373
+ // This method is exposed to the widget API and allows sending files
1374
+ // using the fileupload API. The data parameter accepts an object which
1375
+ // must have a files or fileInput property and can contain additional options:
1376
+ // .fileupload('send', {files: filesList});
1377
+ // The method returns a Promise object for the file upload call.
1378
+ send: function (data) {
1379
+ if (data && !this.options.disabled) {
1380
+ if (data.fileInput && !data.files) {
1381
+ var that = this,
1382
+ dfd = $.Deferred(),
1383
+ promise = dfd.promise(),
1384
+ jqXHR,
1385
+ aborted;
1386
+ promise.abort = function () {
1387
+ aborted = true;
1388
+ if (jqXHR) {
1389
+ return jqXHR.abort();
1390
+ }
1391
+ dfd.reject(null, 'abort', 'abort');
1392
+ return promise;
1393
+ };
1394
+ this._getFileInputFiles(data.fileInput).always(
1395
+ function (files) {
1396
+ if (aborted) {
1397
+ return;
1398
+ }
1399
+ if (!files.length) {
1400
+ dfd.reject();
1401
+ return;
1402
+ }
1403
+ data.files = files;
1404
+ jqXHR = that._onSend(null, data).then(
1405
+ function (result, textStatus, jqXHR) {
1406
+ dfd.resolve(result, textStatus, jqXHR);
1407
+ },
1408
+ function (jqXHR, textStatus, errorThrown) {
1409
+ dfd.reject(jqXHR, textStatus, errorThrown);
1410
+ }
1411
+ );
1412
+ }
1413
+ );
1414
+ return this._enhancePromise(promise);
1415
+ }
1416
+ data.files = $.makeArray(data.files);
1417
+ if (data.files.length) {
1418
+ return this._onSend(null, data);
1419
+ }
1420
+ }
1421
+ return this._getXHRPromise(false, data && data.context);
1422
+ }
1423
+
1424
+ });
1425
+
1426
+ }));