vendor/assets/javascripts/uploader/jquery.fileupload-ui.js in rails-uploader-0.0.8 vs vendor/assets/javascripts/uploader/jquery.fileupload-ui.js in rails-uploader-0.1.0

- old
+ new

@@ -1,28 +1,28 @@ /* - * jQuery File Upload User Interface Plugin 6.6.4 + * jQuery File Upload User Interface Plugin 7.3 * 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 */ /*jslint nomen: true, unparam: true, regexp: true */ -/*global define, window, document, URL, webkitURL, FileReader */ +/*global define, window, URL, webkitURL, FileReader */ (function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // Register as an anonymous AMD module: define([ 'jquery', 'tmpl', 'load-image', - './jquery.fileupload-ip' + './jquery.fileupload-fp' ], factory); } else { // Browser globals: factory( window.jQuery, @@ -31,14 +31,13 @@ ); } }(function ($, tmpl, loadImage) { 'use strict'; - // The UI version extends the IP (image processing) version or the basic - // file upload widget and adds complete user interface interaction: - var parentWidget = ($.blueimpIP || $.blueimp).fileupload; - $.widget('blueimpUI.fileupload', parentWidget, { + // 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: @@ -68,29 +67,37 @@ previewAsCanvas: true, // 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', // 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) { - var that = $(this).data('fileupload'), + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), options = that.options, files = data.files; - $(this).fileupload('resize', data).done(data, function () { + $(this).fileupload('process', data).done(function () { that._adjustMaxNumberOfFiles(-files.length); - data.isAdjusted = true; + data.maxNumberOfFilesAdjusted = true; data.files.valid = data.isValidated = that._validate(files); - data.context = that._renderUpload(files) - .appendTo(options.filesContainer) - .data('data', data); - that._renderPreviews(files, data.context); + data.context = that._renderUpload(files).data('data', data); + options.filesContainer[ + options.prependFiles ? 'prepend' : 'append' + ](data.context); + that._renderPreviews(data); that._forceReflow(data.context); that._transition(data.context).done( function () { if ((that._trigger('added', e, data) !== false) && (options.autoUpload || data.autoUpload) && @@ -101,14 +108,16 @@ ); }); }, // Callback for the start of each file upload request: send: function (e, data) { - var that = $(this).data('fileupload'); + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'); if (!data.isValidated) { - if (!data.isAdjusted) { + if (!data.maxNumberOfFilesAdjusted) { that._adjustMaxNumberOfFiles(-data.files.length); + data.maxNumberOfFilesAdjusted = true; } if (!that._validate(data.files)) { return false; } } @@ -119,159 +128,243 @@ // the progress to 100%, showing the full animated bar: data.context .find('.progress').addClass( !$.support.transition && 'progress-animated' ) + .attr('aria-valuenow', 100) .find('.bar').css( 'width', - parseInt(100, 10) + '%' + '100%' ); } return that._trigger('sent', e, data); }, // Callback for successful uploads: done: function (e, data) { - var that = $(this).data('fileupload'), + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + files = that._getFilesFromResponse(data), template, - preview; - + deferred; if (data.context) { data.context.each(function (index) { - var file = ($.isArray(data.result) && - data.result[index]) || {error: 'emptyResult'}; + var file = files[index] || + {error: 'Empty file upload result'}, + deferred = that._addFinishedDeferreds(); if (file.error) { that._adjustMaxNumberOfFiles(1); } that._transition($(this)).done( function () { var node = $(this); template = that._renderDownload([file]) - .css('height', node.height()) .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(data.result) + if (files.length) { + $.each(files, function (index, file) { + if (data.maxNumberOfFilesAdjusted && file.error) { + that._adjustMaxNumberOfFiles(1); + } else if (!data.maxNumberOfFilesAdjusted && + !file.error) { + that._adjustMaxNumberOfFiles(-1); + } + }); + data.maxNumberOfFilesAdjusted = true; + } + template = that._renderDownload(files) .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) { - var that = $(this).data('fileupload'), - template; - that._adjustMaxNumberOfFiles(data.files.length); + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + template, + deferred; + if (data.maxNumberOfFilesAdjusted) { + that._adjustMaxNumberOfFiles(data.files.length); + } if (data.context) { data.context.each(function (index) { if (data.errorThrown !== 'abort') { var file = data.files[index]; file.error = file.error || data.errorThrown || true; + 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') { - that._adjustMaxNumberOfFiles(-data.files.length); data.context = that._renderUpload(data.files) .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 (data.context) { - data.context.find('.progress .bar').css( - 'width', - parseInt(data.loaded / data.total * 100, 10) + '%' - ); + var progress = parseInt(data.loaded / data.total * 100, 10); + data.context.find('.progress') + .attr('aria-valuenow', progress) + .find('.bar').css( + 'width', + progress + '%' + ); } }, // Callback for global upload progress events: progressall: function (e, data) { - $(this).find('.fileupload-buttonbar .progress .bar').css( - 'width', - parseInt(data.loaded / data.total * 100, 10) + '%' - ); + var $this = $(this), + progress = parseInt(data.loaded / data.total * 100, 10), + 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) + .find('.bar').css( + 'width', + progress + '%' + ); }, // Callback for uploads start, equivalent to the global ajaxStart event: start: function (e) { - var that = $(this).data('fileupload'); - that._transition($(this).find('.fileupload-buttonbar .progress')).done( + 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) { - var that = $(this).data('fileupload'); - that._transition($(this).find('.fileupload-buttonbar .progress')).done( - function () { - $(this).find('.bar').css('width', '0%'); + 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') + .find('.bar').css('width', '0%'); + $(this).find('.progress-extended').html('&nbsp;'); + deferred.resolve(); } ); }, // Callback for file deletion: destroy: function (e, data) { - var that = $(this).data('fileupload'); + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'); if (data.url) { $.ajax(data); + that._adjustMaxNumberOfFiles(1); } - that._adjustMaxNumberOfFiles(1); that._transition(data.context).done( function () { $(this).remove(); that._trigger('destroyed', e, data); } ); } }, + _resetFinishedDeferreds: function () { + this._finishedUploads = []; + }, + + _addFinishedDeferreds: function (deferred) { + if (!deferred) { + deferred = $.Deferred(); + } + this._finishedUploads.push(deferred); + return deferred; + }, + + _getFinishedDeferreds: function () { + return this._finishedUploads; + }, + + _getFilesFromResponse: function (data) { + if (data.result && $.isArray(data.result.files)) { + return data.result.files; + } + return []; + }, + // 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'), @@ -309,34 +402,76 @@ 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 = parseInt(seconds / 86400, 10); + 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); + }, + _hasError: function (file) { if (file.error) { return file.error; } // The number of added files is subtracted from // maxNumberOfFiles before validation, so we check if // maxNumberOfFiles is below 0 (instead of below 1): if (this.options.maxNumberOfFiles < 0) { - return 'maxNumberOfFiles'; + return 'Maximum number of files exceeded'; } // Files are accepted if either the file type or the file name // matches against the acceptFileTypes regular expression, as // only browsers with support for the File API report the type: if (!(this.options.acceptFileTypes.test(file.type) || this.options.acceptFileTypes.test(file.name))) { - return 'acceptFileTypes'; + return 'Filetype not allowed'; } if (this.options.maxFileSize && file.size > this.options.maxFileSize) { - return 'maxFileSize'; + return 'File is too big'; } if (typeof file.size === 'number' && file.size < this.options.minFileSize) { - return 'minFileSize'; + return 'File is too small'; } return null; }, _validate: function (files) { @@ -361,57 +496,60 @@ options: this.options }); if (result instanceof $) { return result; } - return $(this.options.templatesContainer).html(result).children(); }, _renderPreview: function (file, node) { var that = this, options = this.options, - deferred = $.Deferred(); + dfd = $.Deferred(); return ((loadImage && loadImage( file, function (img) { node.append(img); that._forceReflow(node); that._transition(node).done(function () { - deferred.resolveWith(node); + dfd.resolveWith(node); }); - if (!$.contains(document.body, node[0])) { + if (!$.contains(that.document[0].body, node[0])) { // If the element is not part of the DOM, // transition events are not triggered, // so we have to resolve manually: - deferred.resolveWith(node); + dfd.resolveWith(node); } }, { maxWidth: options.previewMaxWidth, maxHeight: options.previewMaxHeight, canvas: options.previewAsCanvas } - )) || deferred.resolveWith(node)) && deferred; + )) || dfd.resolveWith(node)) && dfd; }, - _renderPreviews: function (files, nodes) { + _renderPreviews: function (data) { var that = this, options = this.options; - nodes.find('.preview span').each(function (index, element) { - var file = files[index]; + data.context.find('.preview span').each(function (index, element) { + var file = data.files[index]; if (options.previewSourceFileTypes.test(file.type) && ($.type(options.previewSourceMaxFileSize) !== 'number' || file.size < options.previewSourceMaxFileSize)) { that._processingQueue = that._processingQueue.pipe(function () { - var deferred = $.Deferred(); + var dfd = $.Deferred(), + ev = $.Event('previewdone', { + target: element + }); that._renderPreview(file, $(element)).done( function () { - deferred.resolveWith(that); + that._trigger(ev.type, ev, data); + dfd.resolveWith(that); } ); - return deferred.promise(); + return dfd.promise(); }); } }); return this._processingQueue; }, @@ -430,139 +568,125 @@ ).find('a[download]').each(this._enableDragToDesktop).end(); }, _startHandler: function (e) { e.preventDefault(); - var button = $(this), + var button = $(e.currentTarget), template = button.closest('.template-upload'), data = template.data('data'); if (data && data.submit && !data.jqXHR && data.submit()) { button.prop('disabled', true); } }, _cancelHandler: function (e) { e.preventDefault(); - var template = $(this).closest('.template-upload'), + var template = $(e.currentTarget).closest('.template-upload'), data = template.data('data') || {}; if (!data.jqXHR) { data.errorThrown = 'abort'; - e.data.fileupload._trigger('fail', e, data); + this._trigger('fail', e, data); } else { data.jqXHR.abort(); } }, _deleteHandler: function (e) { e.preventDefault(); - var button = $(this); - e.data.fileupload._trigger('destroy', e, { + var button = $(e.currentTarget); + this._trigger('destroy', e, $.extend({ context: button.closest('.template-download'), - url: button.attr('data-url'), - type: button.attr('data-type') || 'DELETE', - dataType: e.data.fileupload.options.dataType - }); + type: 'DELETE', + dataType: this.options.dataType + }, button.data())); }, _forceReflow: function (node) { - this._reflow = $.support.transition && - node.length && node[0].offsetWidth; + return $.support.transition && node.length && + node[0].offsetWidth; }, _transition: function (node) { - var that = this, - deferred = $.Deferred(); + var dfd = $.Deferred(); if ($.support.transition && node.hasClass('fade')) { 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); - deferred.resolveWith(node); + dfd.resolveWith(node); } } ).toggleClass('in'); } else { node.toggleClass('in'); - deferred.resolveWith(node); + dfd.resolveWith(node); } - return deferred; + return dfd; }, _initButtonBarEventHandlers: function () { var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), - filesList = this.options.filesContainer, - ns = this.options.namespace; - fileUploadButtonBar.find('.start') - .bind('click.' + ns, function (e) { + filesList = this.options.filesContainer; + this._on(fileUploadButtonBar.find('.start'), { + click: function (e) { e.preventDefault(); filesList.find('.start button').click(); - }); - fileUploadButtonBar.find('.cancel') - .bind('click.' + ns, function (e) { + } + }); + this._on(fileUploadButtonBar.find('.cancel'), { + click: function (e) { e.preventDefault(); filesList.find('.cancel button').click(); - }); - fileUploadButtonBar.find('.delete') - .bind('click.' + ns, function (e) { + } + }); + this._on(fileUploadButtonBar.find('.delete'), { + click: function (e) { e.preventDefault(); filesList.find('.delete input:checked') .siblings('button').click(); fileUploadButtonBar.find('.toggle') .prop('checked', false); - }); - fileUploadButtonBar.find('.toggle') - .bind('change.' + ns, function (e) { + } + }); + this._on(fileUploadButtonBar.find('.toggle'), { + change: function (e) { filesList.find('.delete input').prop( 'checked', - $(this).is(':checked') + $(e.currentTarget).is(':checked') ); - }); + } + }); }, _destroyButtonBarEventHandlers: function () { - this.element.find('.fileupload-buttonbar button') - .unbind('click.' + this.options.namespace); - this.element.find('.fileupload-buttonbar .toggle') - .unbind('change.' + this.options.namespace); + this._off( + this.element.find('.fileupload-buttonbar button'), + 'click' + ); + this._off( + this.element.find('.fileupload-buttonbar .toggle'), + 'change.' + ); }, _initEventHandlers: function () { - parentWidget.prototype._initEventHandlers.call(this); - var eventData = {fileupload: this}; - this.options.filesContainer - .delegate( - '.start button', - 'click.' + this.options.namespace, - eventData, - this._startHandler - ) - .delegate( - '.cancel a', - 'click.' + this.options.namespace, - eventData, - this._cancelHandler - ) - .delegate( - '.delete a', - 'click.' + this.options.namespace, - eventData, - this._deleteHandler - ); + this._super(); + this._on(this.options.filesContainer, { + 'click .start a': this._startHandler, + 'click .cancel a': this._cancelHandler, + 'click .delete a': this._deleteHandler + }); this._initButtonBarEventHandlers(); }, _destroyEventHandlers: function () { - var options = this.options; this._destroyButtonBarEventHandlers(); - options.filesContainer - .undelegate('.start button', 'click.' + options.namespace) - .undelegate('.cancel a', 'click.' + options.namespace) - .undelegate('.delete a', 'click.' + options.namespace); - parentWidget.prototype._destroyEventHandlers.call(this); + this._off(this.options.filesContainer, 'click'); + this._super(); }, _enableFileInputButton: function () { this.element.find('.fileinput-button input') .prop('disabled', false) @@ -575,11 +699,11 @@ .parent().addClass('disabled'); }, _initTemplates: function () { var options = this.options; - options.templatesContainer = document.createElement( + options.templatesContainer = this.document[0].createElement( options.filesContainer.prop('nodeName') ); if (tmpl) { if (options.uploadTemplateId) { options.uploadTemplate = tmpl(options.uploadTemplateId); @@ -597,40 +721,78 @@ } else if (!(options.filesContainer instanceof $)) { options.filesContainer = $(options.filesContainer); } }, + _stringToRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _initRegExpOptions: function () { + var options = this.options; + if ($.type(options.acceptFileTypes) === 'string') { + options.acceptFileTypes = this._stringToRegExp( + options.acceptFileTypes + ); + } + if ($.type(options.previewSourceFileTypes) === 'string') { + options.previewSourceFileTypes = this._stringToRegExp( + options.previewSourceFileTypes + ); + } + }, + _initSpecialOptions: function () { - parentWidget.prototype._initSpecialOptions.call(this); + this._super(); this._initFilesContainer(); this._initTemplates(); + this._initRegExpOptions(); }, + _setOption: function (key, value) { + this._super(key, value); + if (key === 'maxNumberOfFiles') { + this._adjustMaxNumberOfFiles(0); + } + }, + _create: function () { - parentWidget.prototype._create.call(this); + this._super(); this._refreshOptionsList.push( 'filesContainer', 'uploadTemplateId', 'downloadTemplateId' ); - if (!$.blueimpIP) { + if (!this._processingQueue) { this._processingQueue = $.Deferred().resolveWith(this).promise(); - this.resize = function () { + this.process = function () { return this._processingQueue; }; } + this._resetFinishedDeferreds(); }, enable: function () { - parentWidget.prototype.enable.call(this); - this.element.find('input, button').prop('disabled', false); - this._enableFileInputButton(); + 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 () { - this.element.find('input, button').prop('disabled', true); - this._disableFileInputButton(); - parentWidget.prototype.disable.call(this); + if (!this.options.disabled) { + this.element.find('input, button').prop('disabled', true); + this._disableFileInputButton(); + } + this._super(); } }); }));