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