1/* 2 * jQuery File Upload User Interface 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 14(function (factory) { 15 'use strict'; 16 if (typeof define === 'function' && define.amd) { 17 // Register as an anonymous AMD module: 18 define([ 19 'jquery', 20 'blueimp-tmpl', 21 './jquery.fileupload-image', 22 './jquery.fileupload-audio', 23 './jquery.fileupload-video', 24 './jquery.fileupload-validate' 25 ], factory); 26 } else if (typeof exports === 'object') { 27 // Node/CommonJS: 28 factory( 29 require('jquery'), 30 require('blueimp-tmpl'), 31 require('./jquery.fileupload-image'), 32 require('./jquery.fileupload-audio'), 33 require('./jquery.fileupload-video'), 34 require('./jquery.fileupload-validate') 35 ); 36 } else { 37 // Browser globals: 38 factory(window.jQuery, window.tmpl); 39 } 40})(function ($, tmpl) { 41 'use strict'; 42 43 $.blueimp.fileupload.prototype._specialOptions.push( 44 'filesContainer', 45 'uploadTemplateId', 46 'downloadTemplateId' 47 ); 48 49 // The UI version extends the file upload widget 50 // and adds complete user interface interaction: 51 $.widget('blueimp.fileupload', $.blueimp.fileupload, { 52 options: { 53 // By default, files added to the widget are uploaded as soon 54 // as the user clicks on the start buttons. To enable automatic 55 // uploads, set the following option to true: 56 autoUpload: false, 57 // The class to show/hide UI elements: 58 showElementClass: 'in', 59 // The ID of the upload template: 60 uploadTemplateId: 'template-upload', 61 // The ID of the download template: 62 downloadTemplateId: 'template-download', 63 // The container for the list of files. If undefined, it is set to 64 // an element with class "files" inside of the widget element: 65 filesContainer: undefined, 66 // By default, files are appended to the files container. 67 // Set the following option to true, to prepend files instead: 68 prependFiles: false, 69 // The expected data type of the upload response, sets the dataType 70 // option of the $.ajax upload requests: 71 dataType: 'json', 72 73 // Error and info messages: 74 messages: { 75 unknownError: 'Unknown error' 76 }, 77 78 // Function returning the current number of files, 79 // used by the maxNumberOfFiles validation: 80 getNumberOfFiles: function () { 81 return this.filesContainer.children().not('.processing').length; 82 }, 83 84 // Callback to retrieve the list of files from the server response: 85 getFilesFromResponse: function (data) { 86 if (data.result && $.isArray(data.result.files)) { 87 return data.result.files; 88 } 89 return []; 90 }, 91 92 // The add callback is invoked as soon as files are added to the fileupload 93 // widget (via file input selection, drag & drop or add API call). 94 // See the basic file upload widget for more information: 95 add: function (e, data) { 96 if (e.isDefaultPrevented()) { 97 return false; 98 } 99 var $this = $(this), 100 that = $this.data('blueimp-fileupload') || $this.data('fileupload'), 101 options = that.options; 102 data.context = that 103 ._renderUpload(data.files) 104 .data('data', data) 105 .addClass('processing'); 106 options.filesContainer[options.prependFiles ? 'prepend' : 'append']( 107 data.context 108 ); 109 that._forceReflow(data.context); 110 that._transition(data.context); 111 data 112 .process(function () { 113 return $this.fileupload('process', data); 114 }) 115 .always(function () { 116 data.context 117 .each(function (index) { 118 $(this) 119 .find('.size') 120 .text(that._formatFileSize(data.files[index].size)); 121 }) 122 .removeClass('processing'); 123 that._renderPreviews(data); 124 }) 125 .done(function () { 126 data.context.find('.edit,.start').prop('disabled', false); 127 if ( 128 that._trigger('added', e, data) !== false && 129 (options.autoUpload || data.autoUpload) && 130 data.autoUpload !== false 131 ) { 132 data.submit(); 133 } 134 }) 135 .fail(function () { 136 if (data.files.error) { 137 data.context.each(function (index) { 138 var error = data.files[index].error; 139 if (error) { 140 $(this).find('.error').text(error); 141 } 142 }); 143 } 144 }); 145 }, 146 // Callback for the start of each file upload request: 147 send: function (e, data) { 148 if (e.isDefaultPrevented()) { 149 return false;
150 } 151 var that = 152 $(this).data('blueimp-fileupload') || $(this).data('fileupload'); 153 if ( 154 data.context && 155 data.dataType && 156 data.dataType.substr(0, 6) === 'iframe' 157 ) { 158 // Iframe Transport does not support progress events. 159 // In lack of an indeterminate progress bar, we set 160 // the progress to 100%, showing the full animated bar: 161 data.context 162 .find('.progress') 163 .addClass(!$.support.transition && 'progress-animated') 164 .attr('aria-valuenow', 100) 165 .children() 166 .first() 167 .css('width', '100%'); 168 } 169 return that._trigger('sent', e, data); 170 }, 171 // Callback for successful uploads: 172 done: function (e, data) { 173 if (e.isDefaultPrevented()) { 174 return false; 175 } 176 var that = 177 $(this).data('blueimp-fileupload') || $(this).data('fileupload'), 178 getFilesFromResponse = 179 data.getFilesFromResponse || that.options.getFilesFromResponse, 180 files = getFilesFromResponse(data), 181 template, 182 deferred; 183 if (data.context) { 184 data.context.each(function (index) { 185 var file = files[index] || { error: 'Empty file upload result' }; 186 deferred = that._addFinishedDeferreds(); 187 that._transition($(this)).done(function () { 188 var node = $(this); 189 template = that._renderDownload([file]).replaceAll(node); 190 that._forceReflow(template); 191 that._transition(template).done(function () { 192 data.context = $(this); 193 that._trigger('completed', e, data); 194 that._trigger('finished', e, data); 195 deferred.resolve(); 196 }); 197 }); 198 }); 199 } else { 200 template = that 201 ._renderDownload(files) 202 [that.options.prependFiles ? 'prependTo' : 'appendTo']( 203 that.options.filesContainer 204 ); 205 that._forceReflow(template); 206 deferred = that._addFinishedDeferreds(); 207 that._transition(template).done(function () { 208 data.context = $(this); 209 that._trigger('completed', e, data); 210 that._trigger('finished', e, data); 211 deferred.resolve(); 212 }); 213 } 214 }, 215 // Callback for failed (abort or error) uploads: 216 fail: function (e, data) { 217 if (e.isDefaultPrevented()) { 218 return false; 219 } 220 var that = 221 $(this).data('blueimp-fileupload') || $(this).data('fileupload'), 222 template, 223 deferred; 224 if (data.context) { 225 data.context.each(function (index) { 226 if (data.errorThrown !== 'abort') { 227 var file = data.files[index]; 228 file.error = 229 file.error || data.errorThrown || data.i18n('unknownError'); 230 deferred = that._addFinishedDeferreds(); 231 that._transition($(this)).done(function () { 232 var node = $(this); 233 template = that._renderDownload([file]).replaceAll(node); 234 that._forceReflow(template); 235 that._transition(template).done(function () { 236 data.context = $(this); 237 that._trigger('failed', e, data); 238 that._trigger('finished', e, data); 239 deferred.resolve(); 240 }); 241 }); 242 } else { 243 deferred = that._addFinishedDeferreds(); 244 that._transition($(this)).done(function () { 245 $(this).remove(); 246 that._trigger('failed', e, data); 247 that._trigger('finished', e, data); 248 deferred.resolve(); 249 }); 250 } 251 }); 252 } else if (data.errorThrown !== 'abort') { 253 data.context = that 254 ._renderUpload(data.files) 255 [that.options.prependFiles ? 'prependTo' : 'appendTo']( 256 that.options.filesContainer 257 ) 258 .data('data', data); 259 that._forceReflow(data.context); 260 deferred = that._addFinishedDeferreds(); 261 that._transition(data.context).done(function () { 262 data.context = $(this); 263 that._trigger('failed', e, data); 264 that._trigger('finished', e, data); 265 deferred.resolve(); 266 }); 267 } else { 268 that._trigger('failed', e, data); 269 that._trigger('finished', e, data); 270 that._addFinishedDeferreds().resolve(); 271 } 272 }, 273 // Callback for upload progress events: 274 progress: function (e, data) { 275 if (e.isDefaultPrevented()) { 276 return false;
277 } 278 var progress = Math.floor((data.loaded / data.total) * 100); 279 if (data.context) { 280 data.context.each(function () { 281 $(this) 282 .find('.progress') 283 .attr('aria-valuenow', progress) 284 .children() 285 .first() 286 .css('width', progress + '%'); 287 }); 288 } 289 }, 290 // Callback for global upload progress events: 291 progressall: function (e, data) { 292 if (e.isDefaultPrevented()) { 293 return false; 294 } 295 var $this = $(this), 296 progress = Math.floor((data.loaded / data.total) * 100), 297 globalProgressNode = $this.find('.fileupload-progress'), 298 extendedProgressNode = globalProgressNode.find('.progress-extended'); 299 if (extendedProgressNode.length) { 300 extendedProgressNode.html( 301 ( 302 $this.data('blueimp-fileupload') || $this.data('fileupload') 303 )._renderExtendedProgress(data) 304 ); 305 } 306 globalProgressNode 307 .find('.progress') 308 .attr('aria-valuenow', progress) 309 .children() 310 .first() 311 .css('width', progress + '%'); 312 }, 313 // Callback for uploads start, equivalent to the global ajaxStart event: 314 start: function (e) { 315 if (e.isDefaultPrevented()) { 316 return false; 317 } 318 var that = 319 $(this).data('blueimp-fileupload') || $(this).data('fileupload'); 320 that._resetFinishedDeferreds(); 321 that 322 ._transition($(this).find('.fileupload-progress')) 323 .done(function () { 324 that._trigger('started', e); 325 }); 326 }, 327 // Callback for uploads stop, equivalent to the global ajaxStop event: 328 stop: function (e) { 329 if (e.isDefaultPrevented()) { 330 return false; 331 } 332 var that = 333 $(this).data('blueimp-fileupload') || $(this).data('fileupload'), 334 deferred = that._addFinishedDeferreds();
335 $.when.apply($, that._getFinishedDeferreds()).done(function () { 336 that._trigger('stopped', e); 337 }); 338 that 339 ._transition($(this).find('.fileupload-progress')) 340 .done(function () { 341 $(this) 342 .find('.progress') 343 .attr('aria-valuenow', '0') 344 .children() 345 .first() 346 .css('width', '0%'); 347 $(this).find('.progress-extended').html(' '); 348 deferred.resolve(); 349 }); 350 }, 351 processstart: function (e) { 352 if (e.isDefaultPrevented()) { 353 return false; 354 } 355 $(this).addClass('fileupload-processing'); 356 }, 357 processstop: function (e) { 358 if (e.isDefaultPrevented()) { 359 return false; 360 } 361 $(this).removeClass('fileupload-processing'); 362 }, 363 // Callback for file deletion: 364 destroy: function (e, data) { 365 if (e.isDefaultPrevented()) { 366 return false; 367 } 368 var that = 369 $(this).data('blueimp-fileupload') || $(this).data('fileupload'), 370 removeNode = function () { 371 that._transition(data.context).done(function () { 372 $(this).remove(); 373 that._trigger('destroyed', e, data); 374 }); 375 }; 376 if (data.url) { 377 data.dataType = data.dataType || that.options.dataType; 378 $.ajax(data) 379 .done(removeNode) 380 .fail(function () { 381 that._trigger('destroyfailed', e, data); 382 }); 383 } else { 384 removeNode(); 385 } 386 } 387 }, 388 389 _resetFinishedDeferreds: function () { 390 this._finishedUploads = []; 391 }, 392 393 _addFinishedDeferreds: function (deferred) { 394 // eslint-disable-next-line new-cap 395 var promise = deferred || $.Deferred(); 396 this._finishedUploads.push(promise); 397 return promise; 398 }, 399 400 _getFinishedDeferreds: function () { 401 return this._finishedUploads; 402 }, 403 404 // Link handler, that allows to download files 405 // by drag & drop of the links to the desktop: 406 _enableDragToDesktop: function () { 407 var link = $(this), 408 url = link.prop('href'), 409 name = link.prop('download'), 410 type = 'application/octet-stream'; 411 link.on('dragstart', function (e) { 412 try { 413 e.originalEvent.dataTransfer.setData( 414 'DownloadURL', 415 [type, name, url].join(':') 416 ); 417 } catch (ignore) { 418 // Ignore exceptions 419 } 420 }); 421 }, 422 423 _formatFileSize: function (bytes) { 424 if (typeof bytes !== 'number') { 425 return ''; 426 } 427 if (bytes >= 1000000000) { 428 return (bytes / 1000000000).toFixed(2) + ' GB'; 429 } 430 if (bytes >= 1000000) { 431 return (bytes / 1000000).toFixed(2) + ' MB'; 432 } 433 return (bytes / 1000).toFixed(2) + ' KB'; 434 }, 435 436 _formatBitrate: function (bits) { 437 if (typeof bits !== 'number') { 438 return ''; 439 } 440 if (bits >= 1000000000) { 441 return (bits / 1000000000).toFixed(2) + ' Gbit/s'; 442 } 443 if (bits >= 1000000) { 444 return (bits / 1000000).toFixed(2) + ' Mbit/s'; 445 } 446 if (bits >= 1000) { 447 return (bits / 1000).toFixed(2) + ' kbit/s'; 448 } 449 return bits.toFixed(2) + ' bit/s'; 450 }, 451 452 _formatTime: function (seconds) { 453 var date = new Date(seconds * 1000), 454 days = Math.floor(seconds / 86400); 455 days = days ? days + 'd ' : ''; 456 return ( 457 days + 458 ('0' + date.getUTCHours()).slice(-2) + 459 ':' + 460 ('0' + date.getUTCMinutes()).slice(-2) + 461 ':' + 462 ('0' + date.getUTCSeconds()).slice(-2) 463 ); 464 }, 465 466 _formatPercentage: function (floatValue) { 467 return (floatValue * 100).toFixed(2) + ' %'; 468 }, 469 470 _renderExtendedProgress: function (data) { 471 return ( 472 this._formatBitrate(data.bitrate) + 473 ' | ' + 474 this._formatTime(((data.total - data.loaded) * 8) / data.bitrate) + 475 ' | ' + 476 this._formatPercentage(data.loaded / data.total) + 477 ' | ' + 478 this._formatFileSize(data.loaded) + 479 ' / ' + 480 this._formatFileSize(data.total) 481 ); 482 }, 483 484 _renderTemplate: function (func, files) { 485 if (!func) { 486 return $(); 487 } 488 var result = func({ 489 files: files, 490 formatFileSize: this._formatFileSize, 491 options: this.options 492 }); 493 if (result instanceof $) { 494 return result; 495 } 496 return $(this.options.templatesContainer).html(result).children(); 497 }, 498 499 _renderPreviews: function (data) { 500 data.context.find('.preview').each(function (index, elm) { 501 $(elm).empty().append(data.files[index].preview); 502 }); 503 }, 504 505 _renderUpload: function (files) { 506 return this._renderTemplate(this.options.uploadTemplate, files); 507 }, 508 509 _renderDownload: function (files) { 510 return this._renderTemplate(this.options.downloadTemplate, files) 511 .find('a[download]') 512 .each(this._enableDragToDesktop) 513 .end(); 514 }, 515 516 _editHandler: function (e) { 517 e.preventDefault(); 518 if (!this.options.edit) return; 519 var that = this, 520 button = $(e.currentTarget), 521 template = button.closest('.template-upload'), 522 data = template.data('data'), 523 index = button.data().index; 524 this.options.edit(data.files[index]).then(function (file) { 525 if (!file) return; 526 data.files[index] = file; 527 data.context.addClass('processing'); 528 template.find('.edit,.start').prop('disabled', true); 529 $(that.element) 530 .fileupload('process', data) 531 .always(function () { 532 template 533 .find('.size') 534 .text(that._formatFileSize(data.files[index].size)); 535 data.context.removeClass('processing'); 536 that._renderPreviews(data); 537 }) 538 .done(function () { 539 template.find('.edit,.start').prop('disabled', false); 540 }) 541 .fail(function () { 542 template.find('.edit').prop('disabled', false); 543 var error = data.files[index].error; 544 if (error) { 545 template.find('.error').text(error); 546 } 547 }); 548 }); 549 }, 550 551 _startHandler: function (e) { 552 e.preventDefault(); 553 var button = $(e.currentTarget), 554 template = button.closest('.template-upload'), 555 data = template.data('data'); 556 button.prop('disabled', true); 557 if (data && data.submit) { 558 data.submit(); 559 } 560 }, 561 562 _cancelHandler: function (e) { 563 e.preventDefault(); 564 var template = $(e.currentTarget).closest( 565 '.template-upload,.template-download' 566 ), 567 data = template.data('data') || {}; 568 data.context = data.context || template; 569 if (data.abort) { 570 data.abort(); 571 } else { 572 data.errorThrown = 'abort'; 573 this._trigger('fail', e, data); 574 } 575 }, 576 577 _deleteHandler: function (e) { 578 e.preventDefault(); 579 var button = $(e.currentTarget); 580 this._trigger( 581 'destroy', 582 e, 583 $.extend( 584 { 585 context: button.closest('.template-download'), 586 type: 'DELETE' 587 }, 588 button.data() 589 ) 590 ); 591 }, 592 593 _forceReflow: function (node) { 594 return $.support.transition && node.length && node[0].offsetWidth; 595 }, 596 597 _transition: function (node) { 598 // eslint-disable-next-line new-cap 599 var dfd = $.Deferred(); 600 if ( 601 $.support.transition && 602 node.hasClass('fade') && 603 node.is(':visible') 604 ) { 605 var transitionEndHandler = function (e) { 606 // Make sure we don't respond to other transition events 607 // in the container element, e.g. from button elements: 608 if (e.target === node[0]) { 609 node.off($.support.transition.end, transitionEndHandler); 610 dfd.resolveWith(node); 611 } 612 }; 613 node 614 .on($.support.transition.end, transitionEndHandler) 615 .toggleClass(this.options.showElementClass); 616 } else { 617 node.toggleClass(this.options.showElementClass); 618 dfd.resolveWith(node); 619 } 620 return dfd; 621 }, 622 623 _initButtonBarEventHandlers: function () { 624 var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), 625 filesList = this.options.filesContainer; 626 this._on(fileUploadButtonBar.find('.start'), { 627 click: function (e) { 628 e.preventDefault(); 629 filesList.find('.start').click(); 630 } 631 }); 632 this._on(fileUploadButtonBar.find('.cancel'), { 633 click: function (e) { 634 e.preventDefault(); 635 filesList.find('.cancel').click(); 636 } 637 }); 638 this._on(fileUploadButtonBar.find('.delete'), { 639 click: function (e) { 640 e.preventDefault(); 641 filesList 642 .find('.toggle:checked') 643 .closest('.template-download') 644 .find('.delete') 645 .click(); 646 fileUploadButtonBar.find('.toggle').prop('checked', false); 647 } 648 }); 649 this._on(fileUploadButtonBar.find('.toggle'), { 650 change: function (e) { 651 filesList 652 .find('.toggle') 653 .prop('checked', $(e.currentTarget).is(':checked')); 654 } 655 }); 656 }, 657 658 _destroyButtonBarEventHandlers: function () { 659 this._off( 660 this.element 661 .find('.fileupload-buttonbar') 662 .find('.start, .cancel, .delete'), 663 'click' 664 ); 665 this._off(this.element.find('.fileupload-buttonbar .toggle'), 'change.'); 666 }, 667 668 _initEventHandlers: function () { 669 this._super(); 670 this._on(this.options.filesContainer, { 671 'click .edit': this._editHandler, 672 'click .start': this._startHandler, 673 'click .cancel': this._cancelHandler, 674 'click .delete': this._deleteHandler 675 }); 676 this._initButtonBarEventHandlers(); 677 }, 678 679 _destroyEventHandlers: function () { 680 this._destroyButtonBarEventHandlers(); 681 this._off(this.options.filesContainer, 'click'); 682 this._super(); 683 }, 684 685 _enableFileInputButton: function () { 686 this.element 687 .find('.fileinput-button input') 688 .prop('disabled', false) 689 .parent() 690 .removeClass('disabled'); 691 }, 692 693 _disableFileInputButton: function () { 694 this.element 695 .find('.fileinput-button input') 696 .prop('disabled', true) 697 .parent() 698 .addClass('disabled'); 699 }, 700 701 _initTemplates: function () { 702 var options = this.options; 703 options.templatesContainer = this.document[0].createElement( 704 options.filesContainer.prop('nodeName') 705 ); 706 if (tmpl) { 707 if (options.uploadTemplateId) { 708 options.uploadTemplate = tmpl(options.uploadTemplateId); 709 } 710 if (options.downloadTemplateId) { 711 options.downloadTemplate = tmpl(options.downloadTemplateId); 712 } 713 } 714 }, 715 716 _initFilesContainer: function () { 717 var options = this.options; 718 if (options.filesContainer === undefined) { 719 options.filesContainer = this.element.find('.files'); 720 } else if (!(options.filesContainer instanceof $)) { 721 options.filesContainer = $(options.filesContainer); 722 } 723 }, 724 725 _initSpecialOptions: function () { 726 this._super(); 727 this._initFilesContainer(); 728 this._initTemplates(); 729 }, 730 731 _create: function () { 732 this._super(); 733 this._resetFinishedDeferreds(); 734 if (!$.support.fileInput) { 735 this._disableFileInputButton(); 736 } 737 }, 738 739 enable: function () { 740 var wasDisabled = false;
741 if (this.options.disabled) { 742 wasDisabled = true; 743 } 744 this._super(); 745 if (wasDisabled) { 746 this.element.find('input, button').prop('disabled', false); 747 this._enableFileInputButton(); 748 } 749 }, 750 751 disable: function () { 752 if (!this.options.disabled) { 753 this.element.find('input, button').prop('disabled', true); 754 this._disableFileInputButton(); 755 } 756 this._super(); 757 } 758 }); 759});
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.