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