UNPKG

aolists-webtop

Version:

Web interface for aoLists

751 lines (680 loc) 20.6 kB
// vim: ts=4:sw=4:nu:fdc=4:nospell /** * Ext.ux.form.UploadPanel * * @author Ing. Jozef Sakáloš * @version $Id: Ext.ux.UploadPanel.js 310 2008-08-14 17:23:48Z jozo $ * @date 13. March 2008 * * @license Ext.ux.form.UploadPanel is licensed under the terms of * the Open Source LGPL 3.0 license. Commercial use is permitted to the extent * that the code/component(s) do NOT become part of another Open Source or Commercially * licensed development library or toolkit without explicit permission. * * License details: http://www.gnu.org/licenses/lgpl.html */ /*global Ext */ /** * @class Ext.ux.UploadPanel * @extends Ext.Panel */ Ext.ux.UploadPanel = Ext.extend(Ext.Panel, { // configuration options overridable from outside // {{{ /** * @cfg {String} addIconCls icon class for add (file browse) button */ addIconCls: 'icon-plus' /** * @cfg {String} addText Text on Add button */ , addText: 'Add' /** * @cfg {Object} baseParams This object is not used directly by FileTreePanel but it is * propagated to lower level objects instead. Included here for convenience. */ /** * @cfg {String} bodyStyle style to use for panel body */ , bodyStyle: 'padding:2px' /** * @cfg {String} buttonsAt Where buttons are placed. Valid values are tbar, bbar, body (defaults to 'tbar') */ , buttonsAt: 'tbar' /** * @cfg {String} clickRemoveText */ , clickRemoveText: 'Click to remove' /** * @cfg {String} clickStopText */ , clickStopText: 'Click to stop' /** * @cfg {String} emptyText empty text for dataview */ , emptyText: 'No files' /** * @cfg {Boolean} enableProgress true to enable querying server for progress information * Passed to underlying uploader. Included here for convenience. */ , enableProgress: true /** * @cfg {String} errorText */ , errorText: 'Error' /** * @cfg {String} fileCls class prefix to use for file type classes */ , fileCls: 'file' /** * @cfg {String} fileQueuedText File upload status text */ , fileQueuedText: 'File <b>{0}</b> is queued for upload' /** * @cfg {String} fileDoneText File upload status text */ , fileDoneText: 'File <b>{0}</b> has been successfully uploaded' /** * @cfg {String} fileFailedText File upload status text */ , fileFailedText: 'File <b>{0}</b> failed to upload' /** * @cfg {String} fileStoppedText File upload status text */ , fileStoppedText: 'File <b>{0}</b> stopped by user' /** * @cfg {String} fileUploadingText File upload status text */ , fileUploadingText: 'Uploading file <b>{0}</b>' /** * @cfg {Number} maxFileSize Maximum upload file size in bytes * This config property is propagated down to uploader for convenience */ , maxFileSize: 524288 /** * @cfg {Number} Maximum file name length for short file names */ , maxLength: 18 /** * @cfg {String} removeAllIconCls iconClass to use for Remove All button (defaults to 'icon-cross' */ , removeAllIconCls: 'icon-cross' /** * @cfg {String} removeAllText text to use for Remove All button tooltip */ , removeAllText: 'Remove All' /** * @cfg {String} removeIconCls icon class to use for remove file icon */ , removeIconCls: 'icon-minus' /** * @cfg {String} removeText Remove text */ , removeText: 'Remove' /** * @cfg {String} selectedClass class for selected item of DataView */ , selectedClass: 'ux-up-item-selected' /** * @cfg {Boolean} singleUpload true to upload files in one form, false to upload one by one * This config property is propagated down to uploader for convenience */ , singleUpload: false /** * @cfg {String} stopAllText */ , stopAllText: 'Stop All' /** * @cfg {String} stopIconCls icon class to use for stop */ , stopIconCls: 'icon-stop' /** * @cfg {String/Ext.XTemplate} tpl Template for DataView. */ /** * @cfg {String} uploadText Upload text */ , uploadText: 'Upload' /** * @cfg {String} uploadIconCls icon class to use for upload button */ , uploadIconCls: 'icon-upload' /** * @cfg {String} workingIconCls iconClass to use for busy indicator */ , workingIconCls: 'icon-working' // }}} // overrides // {{{ , initComponent: function () { // {{{ // create buttons // add (file browse button) configuration var addCfg = { xtype: 'browsebutton' , text: this.addText + '...' , iconCls: this.addIconCls , scope: this , handler: this.onAddFile }; // upload button configuration var upCfg = { xtype: 'button' , iconCls: this.uploadIconCls , text: this.uploadText , scope: this , handler: this.onUpload , disabled: true }; // remove all button configuration var removeAllCfg = { xtype: 'button' , iconCls: this.removeAllIconCls , tooltip: this.removeAllText , scope: this , handler: this.onRemoveAllClick , disabled: true }; // todo: either to cancel buttons in body or implement it if ('body' !== this.buttonsAt) { this[this.buttonsAt] = [addCfg, upCfg, '->', removeAllCfg]; } // }}} // {{{ // create store // fields for record var fields = [ { name: 'id', type: 'text', system: true } , { name: 'shortName', type: 'text', system: true } , { name: 'fileName', type: 'text', system: true } , { name: 'filePath', type: 'text', system: true } , { name: 'fileCls', type: 'text', system: true } , { name: 'input', system: true } , { name: 'form', system: true } , { name: 'state', type: 'text', system: true } , { name: 'error', type: 'text', system: true } , { name: 'progressId', type: 'int', system: true } , { name: 'bytesTotal', type: 'int', system: true } , { name: 'bytesUploaded', type: 'int', system: true } , { name: 'estSec', type: 'int', system: true } , { name: 'filesUploaded', type: 'int', system: true } , { name: 'speedAverage', type: 'int', system: true } , { name: 'speedLast', type: 'int', system: true } , { name: 'timeLast', type: 'int', system: true } , { name: 'timeStart', type: 'int', system: true } , { name: 'pctComplete', type: 'int', system: true } ]; // add custom fields if passed if (Ext.isArray(this.customFields)) { fields.push(this.customFields); } // create store this.store = new Ext.data.SimpleStore({ id: 0 , fields: fields , data: [] }); // }}} // {{{ // create view Ext.apply(this, { items: [{ xtype: 'dataview' , itemSelector: 'div.ux-up-item' , store: this.store , selectedClass: this.selectedClass , singleSelect: true , emptyText: this.emptyText , tpl: this.tpl || new Ext.XTemplate( '<tpl for=".">' + '<div class="ux-up-item">' // + '<div class="ux-up-indicator">&#160;</div>' + '<div class="ux-up-icon-file {fileCls}">&#160;</div>' + '<div class="ux-up-text x-unselectable" qtip="{fileName}">{shortName}</div>' + '<div id="remove-{[values.input.id]}" class="ux-up-icon-state ux-up-icon-{state}"' + 'qtip="{[this.scope.getQtip(values)]}">&#160;</div>' + '</div>' + '</tpl>' , { scope: this } ) , listeners: { click: { scope: this, fn: this.onViewClick } } }] }); // }}} // call parent Ext.ux.UploadPanel.superclass.initComponent.apply(this, arguments); // save useful references this.view = this.items.itemAt(0); // {{{ // add events this.addEvents( /** * Fires before the file is added to store. Return false to cancel the add * @event beforefileadd * @param {Ext.ux.UploadPanel} this * @param {Ext.Element} input (type=file) being added */ 'beforefileadd' /** * Fires after the file is added to the store * @event fileadd * @param {Ext.ux.UploadPanel} this * @param {Ext.data.Store} store * @param {Ext.data.Record} Record (containing the input) that has been added to the store */ , 'fileadd' /** * Fires before the file is removed from the store. Return false to cancel the remove * @event beforefileremove * @param {Ext.ux.UploadPanel} this * @param {Ext.data.Store} store * @param {Ext.data.Record} Record (containing the input) that is being removed from the store */ , 'beforefileremove' /** * Fires after the record (file) has been removed from the store * @event fileremove * @param {Ext.ux.UploadPanel} this * @param {Ext.data.Store} store */ , 'fileremove' /** * Fires before all files are removed from the store (queue). Return false to cancel the clear. * Events for individual files being removed are suspended while clearing the queue. * @event beforequeueclear * @param {Ext.ux.UploadPanel} this * @param {Ext.data.Store} store */ , 'beforequeueclear' /** * Fires after the store (queue) has been cleared * Events for individual files being removed are suspended while clearing the queue. * @event queueclear * @param {Ext.ux.UploadPanel} this * @param {Ext.data.Store} store */ , 'queueclear' /** * Fires after the upload button is clicked but before any upload is started * Return false to cancel the event * @param {Ext.ux.UploadPanel} this */ , 'beforeupload' ); // }}} // {{{ // relay view events this.relayEvents(this.view, [ 'beforeclick' , 'beforeselect' , 'click' , 'containerclick' , 'contextmenu' , 'dblclick' , 'selectionchange' ]); // }}} // create uploader var config = { store: this.store , singleUpload: this.singleUpload , maxFileSize: this.maxFileSize , enableProgress: this.enableProgress , url: this.url , path: this.path // ADDED BY ECANDIDUS , objid: this.objid }; if (this.baseParams) { config.baseParams = this.baseParams; } this.uploader = new Ext.ux.FileUploader(config); // relay uploader events this.relayEvents(this.uploader, [ 'beforeallstart' , 'allfinished' , 'progress' ]); // install event handlers this.on({ beforeallstart: { scope: this, fn: function () { this.uploading = true; this.updateButtons(); } } , allfinished: { scope: this, fn: function () { this.uploading = false; this.updateButtons(); } } , progress: { fn: this.onProgress.createDelegate(this) } }); } // eo function initComponent // }}} // {{{ /** * onRender override, saves references to buttons * @private */ , onRender: function () { // call parent Ext.ux.UploadPanel.superclass.onRender.apply(this, arguments); // save useful references var tb = 'tbar' === this.buttonsAt ? this.getTopToolbar() : this.getBottomToolbar(); this.addBtn = Ext.getCmp(tb.items.first().id); this.uploadBtn = Ext.getCmp(tb.items.itemAt(1).id); this.removeAllBtn = Ext.getCmp(tb.items.last().id); } // eo function onRender // }}} // added methods // {{{ /** * called by XTemplate to get qtip depending on state * @private * @param {Object} values XTemplate values */ , getQtip: function (values) { var qtip = ''; switch (values.state) { case 'queued': qtip = String.format(this.fileQueuedText, values.fileName); qtip += '<br>' + this.clickRemoveText; break; case 'uploading': qtip = String.format(this.fileUploadingText, values.fileName); qtip += '<br>' + values.pctComplete + '% done'; qtip += '<br>' + this.clickStopText; break; case 'done': qtip = String.format(this.fileDoneText, values.fileName); qtip += '<br>' + this.clickRemoveText; break; case 'failed': qtip = String.format(this.fileFailedText, values.fileName); qtip += '<br>' + this.errorText + ':' + values.error; qtip += '<br>' + this.clickRemoveText; break; case 'stopped': qtip = String.format(this.fileStoppedText, values.fileName); qtip += '<br>' + this.clickRemoveText; break; } return qtip; } // eo function getQtip // }}} // {{{ /** * get file name * @private * @param {Ext.Element} inp Input element containing the full file path * @return {String} */ , getFileName: function (inp) { return inp.getValue().split(/[\/\\]/).pop(); } // eo function getFileName // }}} // {{{ /** * get file path (excluding the file name) * @private * @param {Ext.Element} inp Input element containing the full file path * @return {String} */ , getFilePath: function (inp) { return inp.getValue().replace(/[^\/\\]+$/, ''); } // eo function getFilePath // }}} // {{{ /** * returns file class based on name extension * @private * @param {String} name File name to get class of * @return {String} class to use for file type icon */ , getFileCls: function (name) { var atmp = name.split('.'); if (1 === atmp.length) { return this.fileCls; } else { return this.fileCls + '-' + atmp.pop().toLowerCase(); } } // }}} // {{{ /** * called when file is added - adds file to store * @private * @param {Ext.ux.BrowseButton} */ , onAddFile: function (bb) { if (true !== this.eventsSuspended && false === this.fireEvent('beforefileadd', this, bb.getInputFile())) { return; } var inp = bb.detachInputFile(); inp.addClass('x-hidden'); var fileName = this.getFileName(inp); // create new record and add it to store var rec = new this.store.recordType({ input: inp , fileName: fileName , filePath: this.getFilePath(inp) , shortName: Ext.util.Format.ellipsis(fileName, this.maxLength) , fileCls: this.getFileCls(fileName) , state: 'queued' }, inp.id); rec.commit(); this.store.add(rec); this.syncShadow(); this.uploadBtn.enable(); this.removeAllBtn.enable(); if (true !== this.eventsSuspended) { this.fireEvent('fileadd', this, this.store, rec); } this.doLayout(); } // eo onAddFile // }}} // {{{ /** * destroys child components * @private */ , onDestroy: function () { // destroy uploader if (this.uploader) { this.uploader.stopAll(); this.uploader.purgeListeners(); this.uploader = null; } // destroy view if (this.view) { this.view.purgeListeners(); this.view.destroy(); this.view = null; } // destroy store if (this.store) { this.store.purgeListeners(); this.store.destroy(); this.store = null; } } // eo function onDestroy // }}} // {{{ /** * progress event handler * @private * @param {Ext.ux.FileUploader} uploader * @param {Object} data progress data * @param {Ext.data.Record} record */ , onProgress: function (uploader, data, record) { var bytesTotal, bytesUploaded, pctComplete, state, idx, item, width, pgWidth; if (record) { state = record.get('state'); bytesTotal = record.get('bytesTotal') || 1; bytesUploaded = record.get('bytesUploaded') || 0; if ('uploading' === state) { pctComplete = Math.round(1000 * bytesUploaded / bytesTotal) / 10; } else if ('done' === state) { pctComplete = 100; } else { pctComplete = 0; } record.set('pctComplete', pctComplete); idx = this.store.indexOf(record); item = Ext.get(this.view.getNode(idx)); if (item) { width = item.getWidth(); item.applyStyles({ 'background-position': width * pctComplete / 100 + 'px' }); } } } // eo function onProgress // }}} // {{{ /** * called when file remove icon is clicked - performs the remove * @private * @param {Ext.data.Record} */ , onRemoveFile: function (record) { if (true !== this.eventsSuspended && false === this.fireEvent('beforefileremove', this, this.store, record)) { return; } // remove DOM elements var inp = record.get('input'); var wrap = inp.up('em'); inp.remove(); if (wrap) { wrap.remove(); } // remove record from store this.store.remove(record); var count = this.store.getCount(); this.uploadBtn.setDisabled(!count); this.removeAllBtn.setDisabled(!count); if (true !== this.eventsSuspended) { this.fireEvent('fileremove', this, this.store); this.syncShadow(); } } // eo function onRemoveFile // }}} // {{{ /** * Remove All/Stop All button click handler * @private */ , onRemoveAllClick: function (btn) { if (true === this.uploading) { this.stopAll(); } else { this.removeAll(); } } // eo function onRemoveAllClick , stopAll: function () { this.uploader.stopAll(); } // eo function stopAll // }}} // {{{ /** * DataView click handler * @private */ , onViewClick: function (view, index, node, e) { var t = e.getTarget('div:any(.ux-up-icon-queued|.ux-up-icon-failed|.ux-up-icon-done|.ux-up-icon-stopped)'); if (t) { this.onRemoveFile(this.store.getAt(index)); } t = e.getTarget('div.ux-up-icon-uploading'); if (t) { this.uploader.stopUpload(this.store.getAt(index)); } } // eo function onViewClick // }}} // {{{ /** * tells uploader to upload * @private */ , onUpload: function () { if (true !== this.eventsSuspended && false === this.fireEvent('beforeupload', this)) { return false; } this.uploader.upload(); } // eo function onUpload // }}} // {{{ /** * url setter */ , setUrl: function (url) { this.url = url; this.uploader.setUrl(url); } // eo function setUrl // }}} // {{{ /** * path setter */ , setPath: function (path) { this.uploader.setPath(path); } // eo function setPath // }}} // {{{ /** * Updates buttons states depending on uploading state * @private */ , updateButtons: function () { if (true === this.uploading) { this.addBtn.disable(); this.uploadBtn.disable(); this.removeAllBtn.setIconClass(this.stopIconCls); this.removeAllBtn.getEl().child(this.removeAllBtn.buttonSelector).dom[this.removeAllBtn.tooltipType] = this.stopAllText; } else { this.addBtn.enable(); this.uploadBtn.enable(); this.removeAllBtn.setIconClass(this.removeAllIconCls); this.removeAllBtn.getEl().child(this.removeAllBtn.buttonSelector).dom[this.removeAllBtn.tooltipType] = this.removeAllText; } } // eo function updateButtons // }}} // {{{ /** * Removes all files from store and destroys file inputs */ , removeAll: function () { var suspendState = this.eventsSuspended; if (false !== this.eventsSuspended && false === this.fireEvent('beforequeueclear', this, this.store)) { return false; } this.suspendEvents(); this.store.each(this.onRemoveFile, this); this.eventsSuspended = suspendState; if (true !== this.eventsSuspended) { this.fireEvent('queueclear', this, this.store); } this.syncShadow(); } // eo function removeAll // }}} // {{{ /** * synchronize context menu shadow if we're in contextmenu * @private */ , syncShadow: function () { if (this.contextmenu && this.contextmenu.shadow) { this.contextmenu.getEl().shadow.show(this.contextmenu.getEl()); } } // eo function syncShadow // }}} }); // eo extend // register xtype Ext.reg('uploadpanel', Ext.ux.UploadPanel); // eof