| @@ -0,0 +1,1438 @@ |
| @@ -0,0 +1,1438 @@ |
| |
1
| +/* |
| |
2
| + * jQuery File Upload Plugin 5.40.3 |
| |
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
| + successHandler = function (entries) { |
| |
1056
| + that._handleFileTreeEntries( |
| |
1057
| + entries, |
| |
1058
| + path + entry.name + '/' |
| |
1059
| + ).done(function (files) { |
| |
1060
| + dfd.resolve(files); |
| |
1061
| + }).fail(errorHandler); |
| |
1062
| + }, |
| |
1063
| + readEntries = function () { |
| |
1064
| + dirReader.readEntries(function (results) { |
| |
1065
| + if (!results.length) { |
| |
1066
| + successHandler(entries); |
| |
1067
| + } else { |
| |
1068
| + entries = entries.concat(results); |
| |
1069
| + readEntries(); |
| |
1070
| + } |
| |
1071
| + }, errorHandler); |
| |
1072
| + }, |
| |
1073
| + dirReader, entries = []; |
| |
1074
| + path = path || ''; |
| |
1075
| + if (entry.isFile) { |
| |
1076
| + if (entry._file) { |
| |
1077
| + // Workaround for Chrome bug #149735 |
| |
1078
| + entry._file.relativePath = path; |
| |
1079
| + dfd.resolve(entry._file); |
| |
1080
| + } else { |
| |
1081
| + entry.file(function (file) { |
| |
1082
| + file.relativePath = path; |
| |
1083
| + dfd.resolve(file); |
| |
1084
| + }, errorHandler); |
| |
1085
| + } |
| |
1086
| + } else if (entry.isDirectory) { |
| |
1087
| + dirReader = entry.createReader(); |
| |
1088
| + readEntries(); |
| |
1089
| + } else { |
| |
1090
| + // Return an empy list for file system items |
| |
1091
| + // other than files or directories: |
| |
1092
| + dfd.resolve([]); |
| |
1093
| + } |
| |
1094
| + return dfd.promise(); |
| |
1095
| + }, |
| |
1096
| + |
| |
1097
| + _handleFileTreeEntries: function (entries, path) { |
| |
1098
| + var that = this; |
| |
1099
| + return $.when.apply( |
| |
1100
| + $, |
| |
1101
| + $.map(entries, function (entry) { |
| |
1102
| + return that._handleFileTreeEntry(entry, path); |
| |
1103
| + }) |
| |
1104
| + ).pipe(function () { |
| |
1105
| + return Array.prototype.concat.apply( |
| |
1106
| + [], |
| |
1107
| + arguments |
| |
1108
| + ); |
| |
1109
| + }); |
| |
1110
| + }, |
| |
1111
| + |
| |
1112
| + _getDroppedFiles: function (dataTransfer) { |
| |
1113
| + dataTransfer = dataTransfer || {}; |
| |
1114
| + var items = dataTransfer.items; |
| |
1115
| + if (items && items.length && (items[0].webkitGetAsEntry || |
| |
1116
| + items[0].getAsEntry)) { |
| |
1117
| + return this._handleFileTreeEntries( |
| |
1118
| + $.map(items, function (item) { |
| |
1119
| + var entry; |
| |
1120
| + if (item.webkitGetAsEntry) { |
| |
1121
| + entry = item.webkitGetAsEntry(); |
| |
1122
| + if (entry) { |
| |
1123
| + // Workaround for Chrome bug #149735: |
| |
1124
| + entry._file = item.getAsFile(); |
| |
1125
| + } |
| |
1126
| + return entry; |
| |
1127
| + } |
| |
1128
| + return item.getAsEntry(); |
| |
1129
| + }) |
| |
1130
| + ); |
| |
1131
| + } |
| |
1132
| + return $.Deferred().resolve( |
| |
1133
| + $.makeArray(dataTransfer.files) |
| |
1134
| + ).promise(); |
| |
1135
| + }, |
| |
1136
| + |
| |
1137
| + _getSingleFileInputFiles: function (fileInput) { |
| |
1138
| + fileInput = $(fileInput); |
| |
1139
| + var entries = fileInput.prop('webkitEntries') || |
| |
1140
| + fileInput.prop('entries'), |
| |
1141
| + files, |
| |
1142
| + value; |
| |
1143
| + if (entries && entries.length) { |
| |
1144
| + return this._handleFileTreeEntries(entries); |
| |
1145
| + } |
| |
1146
| + files = $.makeArray(fileInput.prop('files')); |
| |
1147
| + if (!files.length) { |
| |
1148
| + value = fileInput.prop('value'); |
| |
1149
| + if (!value) { |
| |
1150
| + return $.Deferred().resolve([]).promise(); |
| |
1151
| + } |
| |
1152
| + // If the files property is not available, the browser does not |
| |
1153
| + // support the File API and we add a pseudo File object with |
| |
1154
| + // the input value as name with path information removed: |
| |
1155
| + files = [{name: value.replace(/^.*\\/, '')}]; |
| |
1156
| + } else if (files[0].name === undefined && files[0].fileName) { |
| |
1157
| + // File normalization for Safari 4 and Firefox 3: |
| |
1158
| + $.each(files, function (index, file) { |
| |
1159
| + file.name = file.fileName; |
| |
1160
| + file.size = file.fileSize; |
| |
1161
| + }); |
| |
1162
| + } |
| |
1163
| + return $.Deferred().resolve(files).promise(); |
| |
1164
| + }, |
| |
1165
| + |
| |
1166
| + _getFileInputFiles: function (fileInput) { |
| |
1167
| + if (!(fileInput instanceof $) || fileInput.length === 1) { |
| |
1168
| + return this._getSingleFileInputFiles(fileInput); |
| |
1169
| + } |
| |
1170
| + return $.when.apply( |
| |
1171
| + $, |
| |
1172
| + $.map(fileInput, this._getSingleFileInputFiles) |
| |
1173
| + ).pipe(function () { |
| |
1174
| + return Array.prototype.concat.apply( |
| |
1175
| + [], |
| |
1176
| + arguments |
| |
1177
| + ); |
| |
1178
| + }); |
| |
1179
| + }, |
| |
1180
| + |
| |
1181
| + _onChange: function (e) { |
| |
1182
| + var that = this, |
| |
1183
| + data = { |
| |
1184
| + fileInput: $(e.target), |
| |
1185
| + form: $(e.target.form) |
| |
1186
| + }; |
| |
1187
| + this._getFileInputFiles(data.fileInput).always(function (files) { |
| |
1188
| + data.files = files; |
| |
1189
| + if (that.options.replaceFileInput) { |
| |
1190
| + that._replaceFileInput(data.fileInput); |
| |
1191
| + } |
| |
1192
| + if (that._trigger( |
| |
1193
| + 'change', |
| |
1194
| + $.Event('change', {delegatedEvent: e}), |
| |
1195
| + data |
| |
1196
| + ) !== false) { |
| |
1197
| + that._onAdd(e, data); |
| |
1198
| + } |
| |
1199
| + }); |
| |
1200
| + }, |
| |
1201
| + |
| |
1202
| + _onPaste: function (e) { |
| |
1203
| + var items = e.originalEvent && e.originalEvent.clipboardData && |
| |
1204
| + e.originalEvent.clipboardData.items, |
| |
1205
| + data = {files: []}; |
| |
1206
| + if (items && items.length) { |
| |
1207
| + $.each(items, function (index, item) { |
| |
1208
| + var file = item.getAsFile && item.getAsFile(); |
| |
1209
| + if (file) { |
| |
1210
| + data.files.push(file); |
| |
1211
| + } |
| |
1212
| + }); |
| |
1213
| + if (this._trigger( |
| |
1214
| + 'paste', |
| |
1215
| + $.Event('paste', {delegatedEvent: e}), |
| |
1216
| + data |
| |
1217
| + ) !== false) { |
| |
1218
| + this._onAdd(e, data); |
| |
1219
| + } |
| |
1220
| + } |
| |
1221
| + }, |
| |
1222
| + |
| |
1223
| + _onDrop: function (e) { |
| |
1224
| + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; |
| |
1225
| + var that = this, |
| |
1226
| + dataTransfer = e.dataTransfer, |
| |
1227
| + data = {}; |
| |
1228
| + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { |
| |
1229
| + e.preventDefault(); |
| |
1230
| + this._getDroppedFiles(dataTransfer).always(function (files) { |
| |
1231
| + data.files = files; |
| |
1232
| + if (that._trigger( |
| |
1233
| + 'drop', |
| |
1234
| + $.Event('drop', {delegatedEvent: e}), |
| |
1235
| + data |
| |
1236
| + ) !== false) { |
| |
1237
| + that._onAdd(e, data); |
| |
1238
| + } |
| |
1239
| + }); |
| |
1240
| + } |
| |
1241
| + }, |
| |
1242
| + |
| |
1243
| + _onDragOver: function (e) { |
| |
1244
| + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; |
| |
1245
| + var dataTransfer = e.dataTransfer; |
| |
1246
| + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && |
| |
1247
| + this._trigger( |
| |
1248
| + 'dragover', |
| |
1249
| + $.Event('dragover', {delegatedEvent: e}) |
| |
1250
| + ) !== false) { |
| |
1251
| + e.preventDefault(); |
| |
1252
| + dataTransfer.dropEffect = 'copy'; |
| |
1253
| + } |
| |
1254
| + }, |
| |
1255
| + |
| |
1256
| + _initEventHandlers: function () { |
| |
1257
| + if (this._isXHRUpload(this.options)) { |
| |
1258
| + this._on(this.options.dropZone, { |
| |
1259
| + dragover: this._onDragOver, |
| |
1260
| + drop: this._onDrop |
| |
1261
| + }); |
| |
1262
| + this._on(this.options.pasteZone, { |
| |
1263
| + paste: this._onPaste |
| |
1264
| + }); |
| |
1265
| + } |
| |
1266
| + if ($.support.fileInput) { |
| |
1267
| + this._on(this.options.fileInput, { |
| |
1268
| + change: this._onChange |
| |
1269
| + }); |
| |
1270
| + } |
| |
1271
| + }, |
| |
1272
| + |
| |
1273
| + _destroyEventHandlers: function () { |
| |
1274
| + this._off(this.options.dropZone, 'dragover drop'); |
| |
1275
| + this._off(this.options.pasteZone, 'paste'); |
| |
1276
| + this._off(this.options.fileInput, 'change'); |
| |
1277
| + }, |
| |
1278
| + |
| |
1279
| + _setOption: function (key, value) { |
| |
1280
| + var reinit = $.inArray(key, this._specialOptions) !== -1; |
| |
1281
| + if (reinit) { |
| |
1282
| + this._destroyEventHandlers(); |
| |
1283
| + } |
| |
1284
| + this._super(key, value); |
| |
1285
| + if (reinit) { |
| |
1286
| + this._initSpecialOptions(); |
| |
1287
| + this._initEventHandlers(); |
| |
1288
| + } |
| |
1289
| + }, |
| |
1290
| + |
| |
1291
| + _initSpecialOptions: function () { |
| |
1292
| + var options = this.options; |
| |
1293
| + if (options.fileInput === undefined) { |
| |
1294
| + options.fileInput = this.element.is('input[type="file"]') ? |
| |
1295
| + this.element : this.element.find('input[type="file"]'); |
| |
1296
| + } else if (!(options.fileInput instanceof $)) { |
| |
1297
| + options.fileInput = $(options.fileInput); |
| |
1298
| + } |
| |
1299
| + if (!(options.dropZone instanceof $)) { |
| |
1300
| + options.dropZone = $(options.dropZone); |
| |
1301
| + } |
| |
1302
| + if (!(options.pasteZone instanceof $)) { |
| |
1303
| + options.pasteZone = $(options.pasteZone); |
| |
1304
| + } |
| |
1305
| + }, |
| |
1306
| + |
| |
1307
| + _getRegExp: function (str) { |
| |
1308
| + var parts = str.split('/'), |
| |
1309
| + modifiers = parts.pop(); |
| |
1310
| + parts.shift(); |
| |
1311
| + return new RegExp(parts.join('/'), modifiers); |
| |
1312
| + }, |
| |
1313
| + |
| |
1314
| + _isRegExpOption: function (key, value) { |
| |
1315
| + return key !== 'url' && $.type(value) === 'string' && |
| |
1316
| + /^\/.*\/[igm]{0,3}$/.test(value); |
| |
1317
| + }, |
| |
1318
| + |
| |
1319
| + _initDataAttributes: function () { |
| |
1320
| + var that = this, |
| |
1321
| + options = this.options, |
| |
1322
| + clone = $(this.element[0].cloneNode(false)); |
| |
1323
| + // Initialize options set via HTML5 data-attributes: |
| |
1324
| + $.each( |
| |
1325
| + clone.data(), |
| |
1326
| + function (key, value) { |
| |
1327
| + var dataAttributeName = 'data-' + |
| |
1328
| + // Convert camelCase to hyphen-ated key: |
| |
1329
| + key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); |
| |
1330
| + if (clone.attr(dataAttributeName)) { |
| |
1331
| + if (that._isRegExpOption(key, value)) { |
| |
1332
| + value = that._getRegExp(value); |
| |
1333
| + } |
| |
1334
| + options[key] = value; |
| |
1335
| + } |
| |
1336
| + } |
| |
1337
| + ); |
| |
1338
| + }, |
| |
1339
| + |
| |
1340
| + _create: function () { |
| |
1341
| + this._initDataAttributes(); |
| |
1342
| + this._initSpecialOptions(); |
| |
1343
| + this._slots = []; |
| |
1344
| + this._sequence = this._getXHRPromise(true); |
| |
1345
| + this._sending = this._active = 0; |
| |
1346
| + this._initProgressObject(this); |
| |
1347
| + this._initEventHandlers(); |
| |
1348
| + }, |
| |
1349
| + |
| |
1350
| + // This method is exposed to the widget API and allows to query |
| |
1351
| + // the number of active uploads: |
| |
1352
| + active: function () { |
| |
1353
| + return this._active; |
| |
1354
| + }, |
| |
1355
| + |
| |
1356
| + // This method is exposed to the widget API and allows to query |
| |
1357
| + // the widget upload progress. |
| |
1358
| + // It returns an object with loaded, total and bitrate properties |
| |
1359
| + // for the running uploads: |
| |
1360
| + progress: function () { |
| |
1361
| + return this._progress; |
| |
1362
| + }, |
| |
1363
| + |
| |
1364
| + // This method is exposed to the widget API and allows adding files |
| |
1365
| + // using the fileupload API. The data parameter accepts an object which |
| |
1366
| + // must have a files property and can contain additional options: |
| |
1367
| + // .fileupload('add', {files: filesList}); |
| |
1368
| + add: function (data) { |
| |
1369
| + var that = this; |
| |
1370
| + if (!data || this.options.disabled) { |
| |
1371
| + return; |
| |
1372
| + } |
| |
1373
| + if (data.fileInput && !data.files) { |
| |
1374
| + this._getFileInputFiles(data.fileInput).always(function (files) { |
| |
1375
| + data.files = files; |
| |
1376
| + that._onAdd(null, data); |
| |
1377
| + }); |
| |
1378
| + } else { |
| |
1379
| + data.files = $.makeArray(data.files); |
| |
1380
| + this._onAdd(null, data); |
| |
1381
| + } |
| |
1382
| + }, |
| |
1383
| + |
| |
1384
| + // This method is exposed to the widget API and allows sending files |
| |
1385
| + // using the fileupload API. The data parameter accepts an object which |
| |
1386
| + // must have a files or fileInput property and can contain additional options: |
| |
1387
| + // .fileupload('send', {files: filesList}); |
| |
1388
| + // The method returns a Promise object for the file upload call. |
| |
1389
| + send: function (data) { |
| |
1390
| + if (data && !this.options.disabled) { |
| |
1391
| + if (data.fileInput && !data.files) { |
| |
1392
| + var that = this, |
| |
1393
| + dfd = $.Deferred(), |
| |
1394
| + promise = dfd.promise(), |
| |
1395
| + jqXHR, |
| |
1396
| + aborted; |
| |
1397
| + promise.abort = function () { |
| |
1398
| + aborted = true; |
| |
1399
| + if (jqXHR) { |
| |
1400
| + return jqXHR.abort(); |
| |
1401
| + } |
| |
1402
| + dfd.reject(null, 'abort', 'abort'); |
| |
1403
| + return promise; |
| |
1404
| + }; |
| |
1405
| + this._getFileInputFiles(data.fileInput).always( |
| |
1406
| + function (files) { |
| |
1407
| + if (aborted) { |
| |
1408
| + return; |
| |
1409
| + } |
| |
1410
| + if (!files.length) { |
| |
1411
| + dfd.reject(); |
| |
1412
| + return; |
| |
1413
| + } |
| |
1414
| + data.files = files; |
| |
1415
| + jqXHR = that._onSend(null, data); |
| |
1416
| + jqXHR.then( |
| |
1417
| + function (result, textStatus, jqXHR) { |
| |
1418
| + dfd.resolve(result, textStatus, jqXHR); |
| |
1419
| + }, |
| |
1420
| + function (jqXHR, textStatus, errorThrown) { |
| |
1421
| + dfd.reject(jqXHR, textStatus, errorThrown); |
| |
1422
| + } |
| |
1423
| + ); |
| |
1424
| + } |
| |
1425
| + ); |
| |
1426
| + return this._enhancePromise(promise); |
| |
1427
| + } |
| |
1428
| + data.files = $.makeArray(data.files); |
| |
1429
| + if (data.files.length) { |
| |
1430
| + return this._onSend(null, data); |
| |
1431
| + } |
| |
1432
| + } |
| |
1433
| + return this._getXHRPromise(false, data && data.context); |
| |
1434
| + } |
| |
1435
| + |
| |
1436
| + }); |
| |
1437
| + |
| |
1438
| +})); |