/* * 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: * http://www.opensource.org/licenses/MIT */ /* jshint nomen:false */ /* global define, require, window */ ;(function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // Register as an anonymous AMD module: define([ 'jquery', '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('tmpl') ); } 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 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('.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; var options = that.options; if (options.singular) { options.filesContainer.html(data.context || '') }; 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) { if (!deferred) { deferred = $.Deferred(); } this._finishedUploads.push(deferred); return deferred; }, _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.bind('dragstart', function (e) { try { e.originalEvent.dataTransfer.setData( 'DownloadURL', [type, name, url].join(':') ); } catch (ignore) {} }); }, _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).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(); }, _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) { var dfd = $.Deferred(); if ($.support.transition && node.hasClass('fade') && node.is(':visible')) { node.bind( $.support.transition.end, function (e) { // Make sure we don't respond to other transitions events // in the container element, e.g. from button elements: if (e.target === node[0]) { node.unbind($.support.transition.end); dfd.resolveWith(node); } } ).toggleClass('in'); } else { node.toggleClass('in'); 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').click(); } }); this._on(fileUploadButtonBar.find('.cancel'), { click: function (e) { e.preventDefault(); filesList.find('.cancel').click(); } }); this._on(fileUploadButtonBar.find('.delete'), { click: function (e) { e.preventDefault(); filesList.find('.toggle:checked') .closest('.template-download') .find('.delete').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 .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(); } }); }));