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