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