/** * jquery.ui.plupload.js * * Copyright 2009, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing * * Depends: * jquery.ui.core.js * jquery.ui.widget.js * * Optionally: * jquery.ui.button.js * jquery.ui.progressbar.js * jquery.ui.sortable.js */ // JSLint defined globals /*global window:false, document:false, plupload:false, jQuery:false */ (function(window, document, plupload, $, undef) { var uploaders = {}; function _(str) { return plupload.translate(str) || str; } function renderUI(obj) { obj.html( '
' + '
' + '
' + '
' + '
' + '
' + _('Select files') + '
' + '
' + _('Add files to the upload queue and click the start button.') + '
' + '
' + '
' + '
' + '' + '' + '' + '' + '' + '' + '' + '
' + _('Filename') + '' + _('Status') + '' + _('Size') + ' 
' + '
' + '
' + '
' + '' + '' + '' + '' + '' + '' + '' + '
' + '
' + '
' + '
' + '' + '
' ); } $.widget("ui.plupload", { contents_bak: '', runtime: null, options: { browse_button_hover: 'ui-state-hover', browse_button_active: 'ui-state-active', // widget specific dragdrop : true, multiple_queues: true, // re-use widget by default buttons: { browse: true, start: true, stop: true }, autostart: false, sortable: false, rename: false, max_file_count: 0 // unlimited }, FILE_COUNT_ERROR: -9001, _create: function() { var self = this, id, uploader; id = this.element.attr('id'); if (!id) { id = plupload.guid(); this.element.attr('id', id); } this.id = id; // backup the elements initial state this.contents_bak = this.element.html(); renderUI(this.element); // container, just in case this.container = $('.plupload_container', this.element).attr('id', id + '_container'); // list of files, may become sortable this.filelist = $('.plupload_filelist_content', this.container) .attr({ id: id + '_filelist', unselectable: 'on' }); // buttons this.browse_button = $('.plupload_add', this.container).attr('id', id + '_browse'); this.start_button = $('.plupload_start', this.container).attr('id', id + '_start'); this.stop_button = $('.plupload_stop', this.container).attr('id', id + '_stop'); if ($.ui.button) { this.browse_button.button({ icons: { primary: 'ui-icon-circle-plus' } }); this.start_button.button({ icons: { primary: 'ui-icon-circle-arrow-e' }, disabled: true }); this.stop_button.button({ icons: { primary: 'ui-icon-circle-close' } }); } // progressbar this.progressbar = $('.plupload_progress_container', this.container); if ($.ui.progressbar) { this.progressbar.progressbar(); } // counter this.counter = $('.plupload_count', this.element) .attr({ id: id + '_count', name: id + '_count' }); // initialize uploader instance uploader = this.uploader = uploaders[id] = new plupload.Uploader($.extend({ container: id , browse_button: id + '_browse' }, this.options)); // do not show UI if no runtime can be initialized uploader.bind('Error', function(up, err) { if (err.code === plupload.INIT_ERROR) { self.destroy(); } }); uploader.bind('Init', function(up, res) { // all buttons are optional, so they can be disabled and hidden if (!self.options.buttons.browse) { self.browse_button.button('disable').hide(); up.disableBrowse(true); } if (!self.options.buttons.start) { self.start_button.button('disable').hide(); } if (!self.options.buttons.stop) { self.stop_button.button('disable').hide(); } if (!self.options.unique_names && self.options.rename) { self._enableRenaming(); } if (uploader.features.dragdrop && self.options.dragdrop) { self._enableDragAndDrop(); } self.container.attr('title', _('Using runtime: ') + (self.runtime = res.runtime)); self.start_button.click(function(e) { if (!$(this).button('option', 'disabled')) { self.start(); } e.preventDefault(); }); self.stop_button.click(function(e) { self.stop(); e.preventDefault(); }); }); // check if file count doesn't exceed the limit if (self.options.max_file_count) { uploader.bind('FilesAdded', function(up, selectedFiles) { var removed = [], selectedCount = selectedFiles.length; var extraCount = up.files.length + selectedCount - self.options.max_file_count; if (extraCount > 0) { removed = selectedFiles.splice(selectedCount - extraCount, extraCount); up.trigger('Error', { code : self.FILE_COUNT_ERROR, message : _('File count error.'), file : removed }); } }); } // uploader internal events must run first uploader.init(); uploader.bind('FilesAdded', function(up, files) { self._trigger('selected', null, { up: up, files: files } ); if (self.options.autostart) { // set a little delay to make sure that QueueChanged triggered by the core has time to complete setTimeout(function() { self.start(); }, 10); } }); uploader.bind('FilesRemoved', function(up, files) { self._trigger('removed', null, { up: up, files: files } ); }); uploader.bind('QueueChanged', function() { self._updateFileList(); }); uploader.bind('StateChanged', function() { self._handleState(); }); uploader.bind('UploadFile', function(up, file) { self._handleFileStatus(file); }); uploader.bind('FileUploaded', function(up, file) { self._handleFileStatus(file); self._trigger('uploaded', null, { up: up, file: file } ); }); uploader.bind('UploadProgress', function(up, file) { // Set file specific progress $('#' + file.id) .find('.plupload_file_status') .html(file.percent + '%') .end() .find('.plupload_file_size') .html(plupload.formatSize(file.size)); self._handleFileStatus(file); self._updateTotalProgress(); self._trigger('progress', null, { up: up, file: file } ); }); uploader.bind('UploadComplete', function(up, files) { self._trigger('complete', null, { up: up, files: files } ); }); uploader.bind('Error', function(up, err) { var file = err.file, message, details; if (file) { message = '' + err.message + ''; details = err.details; if (details) { message += "
" + err.details + ""; } else { switch (err.code) { case plupload.FILE_EXTENSION_ERROR: details = _("File: %s").replace('%s', file.name); break; case plupload.FILE_SIZE_ERROR: details = _("File: %f, size: %s, max file size: %m").replace(/%([fsm])/g, function($0, $1) { switch ($1) { case 'f': return file.name; case 's': return file.size; case 'm': return plupload.parseSize(self.options.max_file_size); } }); break; case self.FILE_COUNT_ERROR: details = _("Upload element accepts only %d file(s) at a time. Extra files were stripped.") .replace('%d', self.options.max_file_count); break; case plupload.IMAGE_FORMAT_ERROR : details = plupload.translate('Image format either wrong or not supported.'); break; case plupload.IMAGE_MEMORY_ERROR : details = plupload.translate('Runtime ran out of available memory.'); break; case plupload.IMAGE_DIMENSIONS_ERROR : details = plupload.translate('Resoultion out of boundaries! %s runtime supports images only up to %wx%hpx.').replace(/%([swh])/g, function($0, $1) { switch ($1) { case 's': return up.runtime; case 'w': return up.features.maxWidth; case 'h': return up.features.maxHeight; } }); break; case plupload.HTTP_ERROR: details = _("Upload URL might be wrong or doesn't exist"); break; } message += "
" + details + ""; } self.notify('error', message); self._trigger('error', null, { up: up, file: file, error: message } ); } }); }, _setOption: function(key, value) { var self = this; if (key == 'buttons' && typeof(value) == 'object') { value = $.extend(self.options.buttons, value); if (!value.browse) { self.browse_button.button('disable').hide(); up.disableBrowse(true); } else { self.browse_button.button('enable').show(); up.disableBrowse(false); } if (!value.start) { self.start_button.button('disable').hide(); } else { self.start_button.button('enable').show(); } if (!value.stop) { self.stop_button.button('disable').hide(); } else { self.start_button.button('enable').show(); } } self.uploader.settings[key] = value; }, start: function() { this.uploader.start(); this._trigger('start', null); }, stop: function() { this.uploader.stop(); this._trigger('stop', null); }, getFile: function(id) { var file; if (typeof id === 'number') { file = this.uploader.files[id]; } else { file = this.uploader.getFile(id); } return file; }, removeFile: function(id) { var file = this.getFile(id); if (file) { this.uploader.removeFile(file); } }, clearQueue: function() { this.uploader.splice(); }, getUploader: function() { return this.uploader; }, refresh: function() { this.uploader.refresh(); }, _handleState: function() { var self = this, up = this.uploader; if (up.state === plupload.STARTED) { $(self.start_button).button('disable'); $([]) .add(self.stop_button) .add('.plupload_started') .removeClass('plupload_hidden'); $('.plupload_upload_status', self.element).html( _('Uploaded %d/%d files').replace('%d/%d', up.total.uploaded+'/'+up.files.length) ); $('.plupload_header_content', self.element).addClass('plupload_header_content_bw'); } else { $([]) .add(self.stop_button) .add('.plupload_started') .addClass('plupload_hidden'); if (self.options.multiple_queues) { $(self.start_button).button('enable'); $('.plupload_header_content', self.element).removeClass('plupload_header_content_bw'); } self._updateFileList(); } }, _handleFileStatus: function(file) { var actionClass, iconClass; // since this method might be called asynchronously, file row might not yet be rendered if (!$('#' + file.id).length) { return; } switch (file.status) { case plupload.DONE: actionClass = 'plupload_done'; iconClass = 'ui-icon ui-icon-circle-check'; break; case plupload.FAILED: actionClass = 'ui-state-error plupload_failed'; iconClass = 'ui-icon ui-icon-alert'; break; case plupload.QUEUED: actionClass = 'plupload_delete'; iconClass = 'ui-icon ui-icon-circle-minus'; break; case plupload.UPLOADING: actionClass = 'ui-state-highlight plupload_uploading'; iconClass = 'ui-icon ui-icon-circle-arrow-w'; // scroll uploading file into the view if its bottom boundary is out of it var scroller = $('.plupload_scroll', this.container), scrollTop = scroller.scrollTop(), scrollerHeight = scroller.height(), rowOffset = $('#' + file.id).position().top + $('#' + file.id).height(); if (scrollerHeight < rowOffset) { scroller.scrollTop(scrollTop + rowOffset - scrollerHeight); } break; } actionClass += ' ui-state-default plupload_file'; $('#' + file.id) .attr('class', actionClass) .find('.ui-icon') .attr('class', iconClass); }, _updateTotalProgress: function() { var up = this.uploader; this.progressbar.progressbar('value', up.total.percent); this.element .find('.plupload_total_status') .html(up.total.percent + '%') .end() .find('.plupload_total_file_size') .html(plupload.formatSize(up.total.size)) .end() .find('.plupload_upload_status') .html(_('Uploaded %d/%d files').replace('%d/%d', up.total.uploaded+'/'+up.files.length)); }, _updateFileList: function() { var self = this, up = this.uploader, filelist = this.filelist, count = 0, id, prefix = this.id + '_', fields; // destroy sortable if enabled if ($.ui.sortable && this.options.sortable) { $('tbody.ui-sortable', filelist).sortable('destroy'); } filelist.empty(); $.each(up.files, function(i, file) { fields = ''; id = prefix + count; if (file.status === plupload.DONE) { if (file.target_name) { fields += ''; } fields += ''; fields += ''; count++; self.counter.val(count); } filelist.append( '' + '' + file.name + '' + '' + file.percent + '%' + '' + plupload.formatSize(file.size) + '' + '
' + fields + '' + '' ); self._handleFileStatus(file); $('#' + file.id + '.plupload_delete .ui-icon, #' + file.id + '.plupload_done .ui-icon') .click(function(e) { $('#' + file.id).remove(); up.removeFile(file); e.preventDefault(); }); self._trigger('updatelist', null, filelist); }); if (up.total.queued === 0) { $('.ui-button-text', self.browse_button).html(_('Add Files')); } else { $('.ui-button-text', self.browse_button).html(_('%d files queued').replace('%d', up.total.queued)); } if (up.files.length === (up.total.uploaded + up.total.failed)) { self.start_button.button('disable'); } else { self.start_button.button('enable'); } // Scroll to end of file list filelist[0].scrollTop = filelist[0].scrollHeight; self._updateTotalProgress(); if (!up.files.length && up.features.dragdrop && up.settings.dragdrop) { // Re-add drag message if there are no files $('#' + id + '_filelist').append('' + _("Drag files here.") + ''); } else { // Otherwise re-initialize sortable if (self.options.sortable && $.ui.sortable) { self._enableSortingList(); } } }, _enableRenaming: function() { var self = this; this.filelist.on('click', '.plupload_delete .plupload_file_name span', function(e) { var targetSpan = $(e.target), file, parts, name, ext = ""; // Get file name and split out name and extension file = self.uploader.getFile(targetSpan.parents('tr')[0].id); name = file.name; parts = /^(.+)(\.[^.]+)$/.exec(name); if (parts) { name = parts[1]; ext = parts[2]; } // Display input element targetSpan.hide().after(''); targetSpan.next().val(name).focus().blur(function() { targetSpan.show().next().remove(); }).keydown(function(e) { var targetInput = $(this); if ($.inArray(e.keyCode, [13, 27]) !== -1) { e.preventDefault(); // Rename file and glue extension back on if (e.keyCode === 13) { file.name = targetInput.val() + ext; targetSpan.html(file.name); } targetInput.blur(); } }); }); }, _enableDragAndDrop: function() { this.filelist.append('' + _("Drag files here.") + ''); this.filelist.parent().attr('id', this.id + '_dropbox'); this.uploader.settings.drop_element = this.options.drop_element = this.id + '_dropbox'; }, _enableSortingList: function() { var idxStart, self = this; if ($('tbody tr', this.filelist).length < 2) { return; } $('tbody', this.filelist).sortable({ containment: 'parent', items: '.plupload_delete', helper: function(e, el) { return el.clone(true).find('td:not(.plupload_file_name)').remove().end().css('width', '100%'); }, stop: function(e, ui) { var i, length, idx, files = []; $.each($(this).sortable('toArray'), function(i, id) { files[files.length] = self.uploader.getFile(id); }); files.unshift(files.length); files.unshift(0); // re-populate files array Array.prototype.splice.apply(self.uploader.files, files); } }); }, notify: function(type, message) { var popup = $( '
' + '' + '

' + message + '

' + '
' ); popup .addClass('ui-state-' + (type === 'error' ? 'error' : 'highlight')) .find('p .ui-icon') .addClass('ui-icon-' + (type === 'error' ? 'alert' : 'info')) .end() .find('.plupload_message_close') .click(function() { popup.remove(); }) .end(); $('.plupload_header_content', this.container).append(popup); }, destroy: function() { // unbind all button events $('.plupload_button', this.element).unbind(); // destroy buttons if ($.ui.button) { $('.plupload_add, .plupload_start, .plupload_stop', this.container) .button('destroy'); } // destroy progressbar if ($.ui.progressbar) { this.progressbar.progressbar('destroy'); } // destroy sortable behavior if ($.ui.sortable && this.options.sortable) { $('tbody', this.filelist).sortable('destroy'); } // destroy uploader instance this.uploader.destroy(); // restore the elements initial state this.element .empty() .html(this.contents_bak); this.contents_bak = ''; $.Widget.prototype.destroy.apply(this); } }); } (window, document, plupload, jQuery));