qiniu_form 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1427 @@
1
+ /*
2
+ * jQuery File Upload Plugin 5.40.1
3
+ * https://github.com/blueimp/jQuery-File-Upload
4
+ *
5
+ * Copyright 2010, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /* jshint nomen:false */
13
+ /* global define, window, document, location, Blob, FormData */
14
+
15
+ (function (factory) {
16
+ 'use strict';
17
+ if (typeof define === 'function') {
18
+ // Register as an anonymous AMD module:
19
+ define([
20
+ 'jquery',
21
+ 'sortable'
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) || !this._isXHRUpload(options)) {
965
+ fileSet = [files];
966
+ paramNameSet = [paramName];
967
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
968
+ fileSet = [];
969
+ paramNameSet = [];
970
+ for (i = 0; i < filesLength; i += limit) {
971
+ fileSet.push(files.slice(i, i + limit));
972
+ paramNameSlice = paramName.slice(i, i + limit);
973
+ if (!paramNameSlice.length) {
974
+ paramNameSlice = paramName;
975
+ }
976
+ paramNameSet.push(paramNameSlice);
977
+ }
978
+ } else if (!options.singleFileUploads && limitSize) {
979
+ fileSet = [];
980
+ paramNameSet = [];
981
+ for (i = 0; i < filesLength; i = i + 1) {
982
+ batchSize += files[i].size + overhead;
983
+ if (i + 1 === filesLength ||
984
+ ((batchSize + files[i + 1].size + overhead) > limitSize) ||
985
+ (limit && i + 1 - j >= limit)) {
986
+ fileSet.push(files.slice(j, i + 1));
987
+ paramNameSlice = paramName.slice(j, i + 1);
988
+ if (!paramNameSlice.length) {
989
+ paramNameSlice = paramName;
990
+ }
991
+ paramNameSet.push(paramNameSlice);
992
+ j = i + 1;
993
+ batchSize = 0;
994
+ }
995
+ }
996
+ } else {
997
+ paramNameSet = paramName;
998
+ }
999
+ data.originalFiles = files;
1000
+ $.each(fileSet || files, function (index, element) {
1001
+ var newData = $.extend({}, data);
1002
+ newData.files = fileSet ? element : [element];
1003
+ newData.paramName = paramNameSet[index];
1004
+ that._initResponseObject(newData);
1005
+ that._initProgressObject(newData);
1006
+ that._addConvenienceMethods(e, newData);
1007
+ result = that._trigger(
1008
+ 'add',
1009
+ $.Event('add', {delegatedEvent: e}),
1010
+ newData
1011
+ );
1012
+ return result;
1013
+ });
1014
+ return result;
1015
+ },
1016
+
1017
+ _replaceFileInput: function (input) {
1018
+ var inputClone = input.clone(true);
1019
+ $('<form></form>').append(inputClone)[0].reset();
1020
+ // Detaching allows to insert the fileInput on another form
1021
+ // without loosing the file input value:
1022
+ input.after(inputClone).detach();
1023
+ // Avoid memory leaks with the detached file input:
1024
+ $.cleanData(input.unbind('remove'));
1025
+ // Replace the original file input element in the fileInput
1026
+ // elements set with the clone, which has been copied including
1027
+ // event handlers:
1028
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
1029
+ if (el === input[0]) {
1030
+ return inputClone[0];
1031
+ }
1032
+ return el;
1033
+ });
1034
+ // If the widget has been initialized on the file input itself,
1035
+ // override this.element with the file input clone:
1036
+ if (input[0] === this.element[0]) {
1037
+ this.element = inputClone;
1038
+ }
1039
+ },
1040
+
1041
+ _handleFileTreeEntry: function (entry, path) {
1042
+ var that = this,
1043
+ dfd = $.Deferred(),
1044
+ errorHandler = function (e) {
1045
+ if (e && !e.entry) {
1046
+ e.entry = entry;
1047
+ }
1048
+ // Since $.when returns immediately if one
1049
+ // Deferred is rejected, we use resolve instead.
1050
+ // This allows valid files and invalid items
1051
+ // to be returned together in one set:
1052
+ dfd.resolve([e]);
1053
+ },
1054
+ dirReader;
1055
+ path = path || '';
1056
+ if (entry.isFile) {
1057
+ if (entry._file) {
1058
+ // Workaround for Chrome bug #149735
1059
+ entry._file.relativePath = path;
1060
+ dfd.resolve(entry._file);
1061
+ } else {
1062
+ entry.file(function (file) {
1063
+ file.relativePath = path;
1064
+ dfd.resolve(file);
1065
+ }, errorHandler);
1066
+ }
1067
+ } else if (entry.isDirectory) {
1068
+ dirReader = entry.createReader();
1069
+ dirReader.readEntries(function (entries) {
1070
+ that._handleFileTreeEntries(
1071
+ entries,
1072
+ path + entry.name + '/'
1073
+ ).done(function (files) {
1074
+ dfd.resolve(files);
1075
+ }).fail(errorHandler);
1076
+ }, errorHandler);
1077
+ } else {
1078
+ // Return an empy list for file system items
1079
+ // other than files or directories:
1080
+ dfd.resolve([]);
1081
+ }
1082
+ return dfd.promise();
1083
+ },
1084
+
1085
+ _handleFileTreeEntries: function (entries, path) {
1086
+ var that = this;
1087
+ return $.when.apply(
1088
+ $,
1089
+ $.map(entries, function (entry) {
1090
+ return that._handleFileTreeEntry(entry, path);
1091
+ })
1092
+ ).pipe(function () {
1093
+ return Array.prototype.concat.apply(
1094
+ [],
1095
+ arguments
1096
+ );
1097
+ });
1098
+ },
1099
+
1100
+ _getDroppedFiles: function (dataTransfer) {
1101
+ dataTransfer = dataTransfer || {};
1102
+ var items = dataTransfer.items;
1103
+ if (items && items.length && (items[0].webkitGetAsEntry ||
1104
+ items[0].getAsEntry)) {
1105
+ return this._handleFileTreeEntries(
1106
+ $.map(items, function (item) {
1107
+ var entry;
1108
+ if (item.webkitGetAsEntry) {
1109
+ entry = item.webkitGetAsEntry();
1110
+ if (entry) {
1111
+ // Workaround for Chrome bug #149735:
1112
+ entry._file = item.getAsFile();
1113
+ }
1114
+ return entry;
1115
+ }
1116
+ return item.getAsEntry();
1117
+ })
1118
+ );
1119
+ }
1120
+ return $.Deferred().resolve(
1121
+ $.makeArray(dataTransfer.files)
1122
+ ).promise();
1123
+ },
1124
+
1125
+ _getSingleFileInputFiles: function (fileInput) {
1126
+ fileInput = $(fileInput);
1127
+ var entries = fileInput.prop('webkitEntries') ||
1128
+ fileInput.prop('entries'),
1129
+ files,
1130
+ value;
1131
+ if (entries && entries.length) {
1132
+ return this._handleFileTreeEntries(entries);
1133
+ }
1134
+ files = $.makeArray(fileInput.prop('files'));
1135
+ if (!files.length) {
1136
+ value = fileInput.prop('value');
1137
+ if (!value) {
1138
+ return $.Deferred().resolve([]).promise();
1139
+ }
1140
+ // If the files property is not available, the browser does not
1141
+ // support the File API and we add a pseudo File object with
1142
+ // the input value as name with path information removed:
1143
+ files = [
1144
+ {name: value.replace(/^.*\\/, '')}
1145
+ ];
1146
+ } else if (files[0].name === undefined && files[0].fileName) {
1147
+ // File normalization for Safari 4 and Firefox 3:
1148
+ $.each(files, function (index, file) {
1149
+ file.name = file.fileName;
1150
+ file.size = file.fileSize;
1151
+ });
1152
+ }
1153
+ return $.Deferred().resolve(files).promise();
1154
+ },
1155
+
1156
+ _getFileInputFiles: function (fileInput) {
1157
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
1158
+ return this._getSingleFileInputFiles(fileInput);
1159
+ }
1160
+ return $.when.apply(
1161
+ $,
1162
+ $.map(fileInput, this._getSingleFileInputFiles)
1163
+ ).pipe(function () {
1164
+ return Array.prototype.concat.apply(
1165
+ [],
1166
+ arguments
1167
+ );
1168
+ });
1169
+ },
1170
+
1171
+ _onChange: function (e) {
1172
+ var that = this,
1173
+ data = {
1174
+ fileInput: $(e.target),
1175
+ form: $(e.target.form)
1176
+ };
1177
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1178
+ data.files = files;
1179
+ if (that.options.replaceFileInput) {
1180
+ that._replaceFileInput(data.fileInput);
1181
+ }
1182
+ if (that._trigger(
1183
+ 'change',
1184
+ $.Event('change', {delegatedEvent: e}),
1185
+ data
1186
+ ) !== false) {
1187
+ that._onAdd(e, data);
1188
+ }
1189
+ });
1190
+ },
1191
+
1192
+ _onPaste: function (e) {
1193
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
1194
+ e.originalEvent.clipboardData.items,
1195
+ data = {files: []};
1196
+ if (items && items.length) {
1197
+ $.each(items, function (index, item) {
1198
+ var file = item.getAsFile && item.getAsFile();
1199
+ if (file) {
1200
+ data.files.push(file);
1201
+ }
1202
+ });
1203
+ if (this._trigger(
1204
+ 'paste',
1205
+ $.Event('paste', {delegatedEvent: e}),
1206
+ data
1207
+ ) !== false) {
1208
+ this._onAdd(e, data);
1209
+ }
1210
+ }
1211
+ },
1212
+
1213
+ _onDrop: function (e) {
1214
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1215
+ var that = this,
1216
+ dataTransfer = e.dataTransfer,
1217
+ data = {};
1218
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1219
+ e.preventDefault();
1220
+ this._getDroppedFiles(dataTransfer).always(function (files) {
1221
+ data.files = files;
1222
+ if (that._trigger(
1223
+ 'drop',
1224
+ $.Event('drop', {delegatedEvent: e}),
1225
+ data
1226
+ ) !== false) {
1227
+ that._onAdd(e, data);
1228
+ }
1229
+ });
1230
+ }
1231
+ },
1232
+
1233
+ _onDragOver: function (e) {
1234
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1235
+ var dataTransfer = e.dataTransfer;
1236
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
1237
+ this._trigger(
1238
+ 'dragover',
1239
+ $.Event('dragover', {delegatedEvent: e})
1240
+ ) !== false) {
1241
+ e.preventDefault();
1242
+ dataTransfer.dropEffect = 'copy';
1243
+ }
1244
+ },
1245
+
1246
+ _initEventHandlers: function () {
1247
+ if (this._isXHRUpload(this.options)) {
1248
+ this._on(this.options.dropZone, {
1249
+ dragover: this._onDragOver,
1250
+ drop: this._onDrop
1251
+ });
1252
+ this._on(this.options.pasteZone, {
1253
+ paste: this._onPaste
1254
+ });
1255
+ }
1256
+ if ($.support.fileInput) {
1257
+ this._on(this.options.fileInput, {
1258
+ change: this._onChange
1259
+ });
1260
+ }
1261
+ },
1262
+
1263
+ _destroyEventHandlers: function () {
1264
+ this._off(this.options.dropZone, 'dragover drop');
1265
+ this._off(this.options.pasteZone, 'paste');
1266
+ this._off(this.options.fileInput, 'change');
1267
+ },
1268
+
1269
+ _setOption: function (key, value) {
1270
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
1271
+ if (reinit) {
1272
+ this._destroyEventHandlers();
1273
+ }
1274
+ this._super(key, value);
1275
+ if (reinit) {
1276
+ this._initSpecialOptions();
1277
+ this._initEventHandlers();
1278
+ }
1279
+ },
1280
+
1281
+ _initSpecialOptions: function () {
1282
+ var options = this.options;
1283
+ if (options.fileInput === undefined) {
1284
+ options.fileInput = this.element.is('input[type="file"]') ?
1285
+ this.element : this.element.find('input[type="file"]');
1286
+ } else if (!(options.fileInput instanceof $)) {
1287
+ options.fileInput = $(options.fileInput);
1288
+ }
1289
+ if (!(options.dropZone instanceof $)) {
1290
+ options.dropZone = $(options.dropZone);
1291
+ }
1292
+ if (!(options.pasteZone instanceof $)) {
1293
+ options.pasteZone = $(options.pasteZone);
1294
+ }
1295
+ },
1296
+
1297
+ _getRegExp: function (str) {
1298
+ var parts = str.split('/'),
1299
+ modifiers = parts.pop();
1300
+ parts.shift();
1301
+ return new RegExp(parts.join('/'), modifiers);
1302
+ },
1303
+
1304
+ _isRegExpOption: function (key, value) {
1305
+ return key !== 'url' && $.type(value) === 'string' &&
1306
+ /^\/.*\/[igm]{0,3}$/.test(value);
1307
+ },
1308
+
1309
+ _initDataAttributes: function () {
1310
+ var that = this,
1311
+ options = this.options,
1312
+ clone = $(this.element[0].cloneNode(false));
1313
+ // Initialize options set via HTML5 data-attributes:
1314
+ $.each(
1315
+ clone.data(),
1316
+ function (key, value) {
1317
+ var dataAttributeName = 'data-' +
1318
+ // Convert camelCase to hyphen-ated key:
1319
+ key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
1320
+ if (clone.attr(dataAttributeName)) {
1321
+ if (that._isRegExpOption(key, value)) {
1322
+ value = that._getRegExp(value);
1323
+ }
1324
+ options[key] = value;
1325
+ }
1326
+ }
1327
+ );
1328
+ },
1329
+
1330
+ _create: function () {
1331
+ this._initDataAttributes();
1332
+ this._initSpecialOptions();
1333
+ this._slots = [];
1334
+ this._sequence = this._getXHRPromise(true);
1335
+ this._sending = this._active = 0;
1336
+ this._initProgressObject(this);
1337
+ this._initEventHandlers();
1338
+ },
1339
+
1340
+ // This method is exposed to the widget API and allows to query
1341
+ // the number of active uploads:
1342
+ active: function () {
1343
+ return this._active;
1344
+ },
1345
+
1346
+ // This method is exposed to the widget API and allows to query
1347
+ // the widget upload progress.
1348
+ // It returns an object with loaded, total and bitrate properties
1349
+ // for the running uploads:
1350
+ progress: function () {
1351
+ return this._progress;
1352
+ },
1353
+
1354
+ // This method is exposed to the widget API and allows adding files
1355
+ // using the fileupload API. The data parameter accepts an object which
1356
+ // must have a files property and can contain additional options:
1357
+ // .fileupload('add', {files: filesList});
1358
+ add: function (data) {
1359
+ var that = this;
1360
+ if (!data || this.options.disabled) {
1361
+ return;
1362
+ }
1363
+ if (data.fileInput && !data.files) {
1364
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1365
+ data.files = files;
1366
+ that._onAdd(null, data);
1367
+ });
1368
+ } else {
1369
+ data.files = $.makeArray(data.files);
1370
+ this._onAdd(null, data);
1371
+ }
1372
+ },
1373
+
1374
+ // This method is exposed to the widget API and allows sending files
1375
+ // using the fileupload API. The data parameter accepts an object which
1376
+ // must have a files or fileInput property and can contain additional options:
1377
+ // .fileupload('send', {files: filesList});
1378
+ // The method returns a Promise object for the file upload call.
1379
+ send: function (data) {
1380
+ if (data && !this.options.disabled) {
1381
+ if (data.fileInput && !data.files) {
1382
+ var that = this,
1383
+ dfd = $.Deferred(),
1384
+ promise = dfd.promise(),
1385
+ jqXHR,
1386
+ aborted;
1387
+ promise.abort = function () {
1388
+ aborted = true;
1389
+ if (jqXHR) {
1390
+ return jqXHR.abort();
1391
+ }
1392
+ dfd.reject(null, 'abort', 'abort');
1393
+ return promise;
1394
+ };
1395
+ this._getFileInputFiles(data.fileInput).always(
1396
+ function (files) {
1397
+ if (aborted) {
1398
+ return;
1399
+ }
1400
+ if (!files.length) {
1401
+ dfd.reject();
1402
+ return;
1403
+ }
1404
+ data.files = files;
1405
+ jqXHR = that._onSend(null, data).then(
1406
+ function (result, textStatus, jqXHR) {
1407
+ dfd.resolve(result, textStatus, jqXHR);
1408
+ },
1409
+ function (jqXHR, textStatus, errorThrown) {
1410
+ dfd.reject(jqXHR, textStatus, errorThrown);
1411
+ }
1412
+ );
1413
+ }
1414
+ );
1415
+ return this._enhancePromise(promise);
1416
+ }
1417
+ data.files = $.makeArray(data.files);
1418
+ if (data.files.length) {
1419
+ return this._onSend(null, data);
1420
+ }
1421
+ }
1422
+ return this._getXHRPromise(false, data && data.context);
1423
+ }
1424
+
1425
+ });
1426
+
1427
+ }));