wagn 1.19.0 → 1.19.1

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