/* * jQuery File Upload User Interface Plugin * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan * https://blueimp.net * * Licensed under the MIT license: * https://opensource.org/licenses/MIT */ /* global define, require */ (function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // Register as an anonymous AMD module: define([ 'jquery', 'blueimp-tmpl', './jquery.fileupload-image', './jquery.fileupload-audio', './jquery.fileupload-video', './jquery.fileupload-validate' ], factory); } else if (typeof exports === 'object') { // Node/CommonJS: factory( require('jquery'), require('blueimp-tmpl'), require('./jquery.fileupload-image'), require('./jquery.fileupload-audio'), require('./jquery.fileupload-video'), require('./jquery.fileupload-validate') ); } else { // Browser globals: factory(window.jQuery, window.tmpl); } })(function ($, tmpl) { 'use strict'; $.blueimp.fileupload.prototype._specialOptions.push( 'filesContainer', 'uploadTemplateId', 'downloadTemplateId' ); // The UI version extends the file upload widget // and adds complete user interface interaction: $.widget('blueimp.fileupload', $.blueimp.fileupload, { options: { // By default, files added to the widget are uploaded as soon // as the user clicks on the start buttons. To enable automatic // uploads, set the following option to true: autoUpload: false, // The class to show/hide UI elements: showElementClass: 'in', // The ID of the upload template: uploadTemplateId: 'template-upload', // The ID of the download template: downloadTemplateId: 'template-download', // The container for the list of files. If undefined, it is set to // an element with class "files" inside of the widget element: filesContainer: undefined, // By default, files are appended to the files container. // Set the following option to true, to prepend files instead: prependFiles: false, // The expected data type of the upload response, sets the dataType // option of the $.ajax upload requests: dataType: 'json', // Error and info messages: messages: { unknownError: 'Unknown error' }, // Function returning the current number of files, // used by the maxNumberOfFiles validation: getNumberOfFiles: function () { return this.filesContainer.children().not('.processing').length; }, // Callback to retrieve the list of files from the server response: getFilesFromResponse: function (data) { if (data.result && $.isArray(data.result.files)) { return data.result.files; } return []; }, // The add callback is invoked as soon as files are added to the fileupload // widget (via file input selection, drag & drop or add API call). // See the basic file upload widget for more information: add: function (e, data) { if (e.isDefaultPrevented()) { return false; } var $this = $(this), that = $this.data('blueimp-fileupload') || $this.data('fileupload'), options = that.options; data.context = that ._renderUpload(data.files) .data('data', data) .addClass('processing'); options.filesContainer[options.prependFiles ? 'prepend' : 'append']( data.context ); that._forceReflow(data.context); that._transition(data.context); data .process(function () { return $this.fileupload('process', data); }) .always(function () { data.context .each(function (index) { $(this) .find('.size') .text(that._formatFileSize(data.files[index].size)); }) .removeClass('processing'); that._renderPreviews(data); }) .done(function () { data.context.find('.edit,.start').prop('disabled', false); if ( that._trigger('added', e, data) !== false && (options.autoUpload || data.autoUpload) && data.autoUpload !== false ) { data.submit(); } }) .fail(function () { if (data.files.error) { data.context.each(function (index) { var error = data.files[index].error; if (error) { $(this).find('.error').text(error); } }); } }); }, // Callback for the start of each file upload request: send: function (e, data) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data('blueimp-fileupload') || $(this).data('fileupload'); if ( data.context && data.dataType && data.dataType.substr(0, 6) === 'iframe' ) { // Iframe Transport does not support progress events. // In lack of an indeterminate progress bar, we set // the progress to 100%, showing the full animated bar: data.context .find('.progress') .addClass(!$.support.transition && 'progress-animated') .attr('aria-valuenow', 100) .children() .first() .css('width', '100%'); } return that._trigger('sent', e, data); }, // Callback for successful uploads: done: function (e, data) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data('blueimp-fileupload') || $(this).data('fileupload'), getFilesFromResponse = data.getFilesFromResponse || that.options.getFilesFromResponse, files = getFilesFromResponse(data), template, deferred; if (data.context) { data.context.each(function (index) { var file = files[index] || { error: 'Empty file upload result' }; deferred = that._addFinishedDeferreds(); that._transition($(this)).done(function () { var node = $(this); template = that._renderDownload([file]).replaceAll(node); that._forceReflow(template); that._transition(template).done(function () { data.context = $(this); that._trigger('completed', e, data); that._trigger('finished', e, data); deferred.resolve(); }); }); }); } else { template = that ._renderDownload(files) [that.options.prependFiles ? 'prependTo' : 'appendTo']( that.options.filesContainer ); that._forceReflow(template); deferred = that._addFinishedDeferreds(); that._transition(template).done(function () { data.context = $(this); that._trigger('completed', e, data); that._trigger('finished', e, data); deferred.resolve(); }); } }, // Callback for failed (abort or error) uploads: fail: function (e, data) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data('blueimp-fileupload') || $(this).data('fileupload'), template, deferred; if (data.context) { data.context.each(function (index) { if (data.errorThrown !== 'abort') { var file = data.files[index]; file.error = file.error || data.errorThrown || data.i18n('unknownError'); deferred = that._addFinishedDeferreds(); that._transition($(this)).done(function () { var node = $(this); template = that._renderDownload([file]).replaceAll(node); that._forceReflow(template); that._transition(template).done(function () { data.context = $(this); that._trigger('failed', e, data); that._trigger('finished', e, data); deferred.resolve(); }); }); } else { deferred = that._addFinishedDeferreds(); that._transition($(this)).done(function () { $(this).remove(); that._trigger('failed', e, data); that._trigger('finished', e, data); deferred.resolve(); }); } }); } else if (data.errorThrown !== 'abort') { data.context = that ._renderUpload(data.files) [that.options.prependFiles ? 'prependTo' : 'appendTo']( that.options.filesContainer ) .data('data', data); that._forceReflow(data.context); deferred = that._addFinishedDeferreds(); that._transition(data.context).done(function () { data.context = $(this); that._trigger('failed', e, data); that._trigger('finished', e, data); deferred.resolve(); }); } else { that._trigger('failed', e, data); that._trigger('finished', e, data); that._addFinishedDeferreds().resolve(); } }, // Callback for upload progress events: progress: function (e, data) { if (e.isDefaultPrevented()) { return false; } var progress = Math.floor((data.loaded / data.total) * 100); if (data.context) { data.context.each(function () { $(this) .find('.progress') .attr('aria-valuenow', progress) .children() .first() .css('width', progress + '%'); }); } }, // Callback for global upload progress events: progressall: function (e, data) { if (e.isDefaultPrevented()) { return false; } var $this = $(this), progress = Math.floor((data.loaded / data.total) * 100), globalProgressNode = $this.find('.fileupload-progress'), extendedProgressNode = globalProgressNode.find('.progress-extended'); if (extendedProgressNode.length) { extendedProgressNode.html( ( $this.data('blueimp-fileupload') || $this.data('fileupload') )._renderExtendedProgress(data) ); } globalProgressNode .find('.progress') .attr('aria-valuenow', progress) .children() .first() .css('width', progress + '%'); }, // Callback for uploads start, equivalent to the global ajaxStart event: start: function (e) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data('blueimp-fileupload') || $(this).data('fileupload'); that._resetFinishedDeferreds(); that ._transition($(this).find('.fileupload-progress')) .done(function () { that._trigger('started', e); }); }, // Callback for uploads stop, equivalent to the global ajaxStop event: stop: function (e) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data('blueimp-fileupload') || $(this).data('fileupload'), deferred = that._addFinishedDeferreds(); $.when.apply($, that._getFinishedDeferreds()).done(function () { that._trigger('stopped', e); }); that ._transition($(this).find('.fileupload-progress')) .done(function () { $(this) .find('.progress') .attr('aria-valuenow', '0') .children() .first() .css('width', '0%'); $(this).find('.progress-extended').html(' '); deferred.resolve(); }); }, processstart: function (e) { if (e.isDefaultPrevented()) { return false; } $(this).addClass('fileupload-processing'); }, processstop: function (e) { if (e.isDefaultPrevented()) { return false; } $(this).removeClass('fileupload-processing'); }, // Callback for file deletion: destroy: function (e, data) { if (e.isDefaultPrevented()) { return false; } var that = $(this).data('blueimp-fileupload') || $(this).data('fileupload'), removeNode = function () { that._transition(data.context).done(function () { $(this).remove(); that._trigger('destroyed', e, data); }); }; if (data.url) { data.dataType = data.dataType || that.options.dataType; $.ajax(data) .done(removeNode) .fail(function () { that._trigger('destroyfailed', e, data); }); } else { removeNode(); } } }, _resetFinishedDeferreds: function () { this._finishedUploads = []; }, _addFinishedDeferreds: function (deferred) { // eslint-disable-next-line new-cap var promise = deferred || $.Deferred(); this._finishedUploads.push(promise); return promise; }, _getFinishedDeferreds: function () { return this._finishedUploads; }, // Link handler, that allows to download files // by drag & drop of the links to the desktop: _enableDragToDesktop: function () { var link = $(this), url = link.prop('href'), name = link.prop('download'), type = 'application/octet-stream'; link.on('dragstart', function (e) { try { e.originalEvent.dataTransfer.setData( 'DownloadURL', [type, name, url].join(':') ); } catch (ignore) { // Ignore exceptions } }); }, _formatFileSize: function (bytes) { if (typeof bytes !== 'number') { return ''; } if (bytes >= 1000000000) { return (bytes / 1000000000).toFixed(2) + ' GB'; } if (bytes >= 1000000) { return (bytes / 1000000).toFixed(2) + ' MB'; } return (bytes / 1000).toFixed(2) + ' KB'; }, _formatBitrate: function (bits) { if (typeof bits !== 'number') { return ''; } if (bits >= 1000000000) { return (bits / 1000000000).toFixed(2) + ' Gbit/s'; } if (bits >= 1000000) { return (bits / 1000000).toFixed(2) + ' Mbit/s'; } if (bits >= 1000) { return (bits / 1000).toFixed(2) + ' kbit/s'; } return bits.toFixed(2) + ' bit/s'; }, _formatTime: function (seconds) { var date = new Date(seconds * 1000), days = Math.floor(seconds / 86400); days = days ? days + 'd ' : ''; return ( days + ('0' + date.getUTCHours()).slice(-2) + ':' + ('0' + date.getUTCMinutes()).slice(-2) + ':' + ('0' + date.getUTCSeconds()).slice(-2) ); }, _formatPercentage: function (floatValue) { return (floatValue * 100).toFixed(2) + ' %'; }, _renderExtendedProgress: function (data) { return ( this._formatBitrate(data.bitrate) + ' | ' + this._formatTime(((data.total - data.loaded) * 8) / data.bitrate) + ' | ' + this._formatPercentage(data.loaded / data.total) + ' | ' + this._formatFileSize(data.loaded) + ' / ' + this._formatFileSize(data.total) ); }, _renderTemplate: function (func, files) { if (!func) { return $(); } var result = func({ files: files, formatFileSize: this._formatFileSize, options: this.options }); if (result instanceof $) { return result; } return $(this.options.templatesContainer).html(result).children(); }, _renderPreviews: function (data) { data.context.find('.preview').each(function (index, elm) { $(elm).empty().append(data.files[index].preview); }); }, _renderUpload: function (files) { return this._renderTemplate(this.options.uploadTemplate, files); }, _renderDownload: function (files) { return this._renderTemplate(this.options.downloadTemplate, files) .find('a[download]') .each(this._enableDragToDesktop) .end(); }, _editHandler: function (e) { e.preventDefault(); if (!this.options.edit) return; var that = this, button = $(e.currentTarget), template = button.closest('.template-upload'), data = template.data('data'), index = button.data().index; this.options.edit(data.files[index]).then(function (file) { if (!file) return; data.files[index] = file; data.context.addClass('processing'); template.find('.edit,.start').prop('disabled', true); $(that.element) .fileupload('process', data) .always(function () { template .find('.size') .text(that._formatFileSize(data.files[index].size)); data.context.removeClass('processing'); that._renderPreviews(data); }) .done(function () { template.find('.edit,.start').prop('disabled', false); }) .fail(function () { template.find('.edit').prop('disabled', false); var error = data.files[index].error; if (error) { template.find('.error').text(error); } }); }); }, _startHandler: function (e) { e.preventDefault(); var button = $(e.currentTarget), template = button.closest('.template-upload'), data = template.data('data'); button.prop('disabled', true); if (data && data.submit) { data.submit(); } }, _cancelHandler: function (e) { e.preventDefault(); var template = $(e.currentTarget).closest( '.template-upload,.template-download' ), data = template.data('data') || {}; data.context = data.context || template; if (data.abort) { data.abort(); } else { data.errorThrown = 'abort'; this._trigger('fail', e, data); } }, _deleteHandler: function (e) { e.preventDefault(); var button = $(e.currentTarget); this._trigger( 'destroy', e, $.extend( { context: button.closest('.template-download'), type: 'DELETE' }, button.data() ) ); }, _forceReflow: function (node) { return $.support.transition && node.length && node[0].offsetWidth; }, _transition: function (node) { // eslint-disable-next-line new-cap var dfd = $.Deferred(); if ( $.support.transition && node.hasClass('fade') && node.is(':visible') ) { var transitionEndHandler = function (e) { // Make sure we don't respond to other transition events // in the container element, e.g. from button elements: if (e.target === node[0]) { node.off($.support.transition.end, transitionEndHandler); dfd.resolveWith(node); } }; node .on($.support.transition.end, transitionEndHandler) .toggleClass(this.options.showElementClass); } else { node.toggleClass(this.options.showElementClass); dfd.resolveWith(node); } return dfd; }, _initButtonBarEventHandlers: function () { var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), filesList = this.options.filesContainer; this._on(fileUploadButtonBar.find('.start'), { click: function (e) { e.preventDefault(); filesList.find('.start').trigger('click'); } }); this._on(fileUploadButtonBar.find('.cancel'), { click: function (e) { e.preventDefault(); filesList.find('.cancel').trigger('click'); } }); this._on(fileUploadButtonBar.find('.delete'), { click: function (e) { e.preventDefault(); filesList .find('.toggle:checked') .closest('.template-download') .find('.delete') .trigger('click'); fileUploadButtonBar.find('.toggle').prop('checked', false); } }); this._on(fileUploadButtonBar.find('.toggle'), { change: function (e) { filesList .find('.toggle') .prop('checked', $(e.currentTarget).is(':checked')); } }); }, _destroyButtonBarEventHandlers: function () { this._off( this.element .find('.fileupload-buttonbar') .find('.start, .cancel, .delete'), 'click' ); this._off(this.element.find('.fileupload-buttonbar .toggle'), 'change.'); }, _initEventHandlers: function () { this._super(); this._on(this.options.filesContainer, { 'click .edit': this._editHandler, 'click .start': this._startHandler, 'click .cancel': this._cancelHandler, 'click .delete': this._deleteHandler }); this._initButtonBarEventHandlers(); }, _destroyEventHandlers: function () { this._destroyButtonBarEventHandlers(); this._off(this.options.filesContainer, 'click'); this._super(); }, _enableFileInputButton: function () { this.element .find('.fileinput-button input') .prop('disabled', false) .parent() .removeClass('disabled'); }, _disableFileInputButton: function () { this.element .find('.fileinput-button input') .prop('disabled', true) .parent() .addClass('disabled'); }, _initTemplates: function () { var options = this.options; options.templatesContainer = this.document[0].createElement( options.filesContainer.prop('nodeName') ); if (tmpl) { if (options.uploadTemplateId) { options.uploadTemplate = tmpl(options.uploadTemplateId); } if (options.downloadTemplateId) { options.downloadTemplate = tmpl(options.downloadTemplateId); } } }, _initFilesContainer: function () { var options = this.options; if (options.filesContainer === undefined) { options.filesContainer = this.element.find('.files'); } else if (!(options.filesContainer instanceof $)) { options.filesContainer = $(options.filesContainer); } }, _initSpecialOptions: function () { this._super(); this._initFilesContainer(); this._initTemplates(); }, _create: function () { this._super(); this._resetFinishedDeferreds(); if (!$.support.fileInput) { this._disableFileInputButton(); } }, enable: function () { var wasDisabled = false; if (this.options.disabled) { wasDisabled = true; } this._super(); if (wasDisabled) { this.element.find('input, button').prop('disabled', false); this._enableFileInputButton(); } }, disable: function () { if (!this.options.disabled) { this.element.find('input, button').prop('disabled', true); this._disableFileInputButton(); } this._super(); } }); });