PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.5
VikBooking Hotel Booking Engine & PMS v1.6.5
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / resources / js_upload / jquery.fileupload.js

jquery.fileupload.js in VikBooking Hotel Booking Engine & PMS 1.6.5, at admin/resources/js_upload/jquery.fileupload.js

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