// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
 * Ext.ux.form.UploadPanel
 *
 * @author  Ing. Jozef Sakáloš
 * @version $Id: Ext.ux.UploadPanel.js 94 2008-03-24 01:04:27Z 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 file'

	/**
	 * @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: 'Delete file'

	/**
	 * @cfg {String} clickStopText
	 */
	,clickStopText: 'Cancel uploading'

	/**
	 * @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} store_url true to enable querying server for progress information
	 * Passed to underlying uploader. Included here for convenience.
	 */
	,store_url: 'GetFaxUploadFile.action'
	
	,delete_url: 'GetFaxUploadFile!delete.action'
	
	,file_num: 1
	/**
	 * @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: 'The File is required!'

	/**
	 * @cfg {String} fileDoneText File upload status text
	 */
	,fileDoneText: 'File <b>{0}</b> has been uploaded successfully.'

	/**
	 * @cfg {String} fileFailedText File upload status text
	 */
	,fileFailedText: 'Uploading file <b>{0}</b> has failed!'

	/**
	 * @cfg {String} fileStoppedText File upload status text
	 */
	,fileStoppedText: 'Uploading file <b>{0}</b> has 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'
	
	,upload_callback: null
	
	,delete_callback: null
	
	// }}}

	// 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: 'date', type: 'text', system: true}
			,{name: 'status', type: 'text', system: true}
			,{name: 'name', type: 'text', system: true}
			,{name: 'size', type: 'text', system: true}
			,{name: 'input', system: true}
		];

		// add custom fields if passed
		if (Ext.isArray(this.customFields)) {
			fields.push(this.customFields);
		}
		
		if (this.store_url == null) {
			this.store = new Ext.data.Store({
				autoLoad: false
				,reader: new Ext.data.JsonReader({
					fields: fields
				})
			});
		} else {
			var rec = Ext.data.Record.create([
				{name: 'name'},
				{name: 'size'},
				{name: 'date'},
				{name: 'status'},
				{name: 'input'}
			]);
			this.store = new Ext.data.Store({
					proxy: new Ext.data.HttpProxy({
					url: this.store_url
				})
				,autoLoad: false
				,reader: new Ext.data.JsonReader({
					root: 'data.data'
				}, rec)
			});
			
			var upanel = this;
			this.store.on("load", function(store, records, options) {
				var tb = 'tbar' === upanel.buttonsAt ? upanel.getTopToolbar() : upanel.getBottomToolbar();
				var addBtn = Ext.getCmp(tb.items.first().id);
				var uploadBtn = Ext.getCmp(tb.items.itemAt(1).id);
				var removeAllBtn = Ext.getCmp(tb.items.last().id);
				
				if (store.getCount() > 0) {
					removeAllBtn.enable();
				}
				
				if (store.getCount() >= upanel.file_num) {
					addBtn.disable();
				}
			});
			
			this.store.load();
		}
		
		// 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="{name}">{name}</div>'
					+ '<div id="remove-{[values.input.id]}" class="ux-up-icon-state ux-up-icon-{status}"'
					+ '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
			,file_num: this.file_num
			,store_url: this.store_url
			,delete_url: this.delete_url
			,upload_callback: this.upload_callback
			,delete_callback: this.delete_callback
			,addText: this.addText
			,clickRemoveText: this.clickRemoveText
			,clickStopText: this.clickStopText
			,emptyText: this.emptyText
			,errorText: this.errorText
			,fileQueuedText: this.fileQueuedText
			,fileDoneText: this.fileDoneText
			,fileFailedText: this.fileFailedText
			,fileStoppedText: this.fileStoppedText
			,fileUploadingText: this.fileUploadingText
			,removeAllText: this.removeAllText
			,removeText: this.removeText
			,stopAllText: this.stopAllText
			,uploadText: this.uploadText
		};
		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.status) {
			case 0: //queued
				qtip = String.format(this.fileQueuedText, values.name);
				qtip += '<br>' + this.clickRemoveText;
			break;

			case 1: //uploading
				qtip = String.format(this.fileUploadingText, values.name);
				qtip += '<br>' + values.pctComplete + '% done';
				qtip += '<br>' + this.clickStopText;
			break;

			case 2: //done
				qtip = String.format(this.fileDoneText, values.name);
				qtip += '<br>' + this.clickRemoveText;
			break;

			case 3: //failed
				qtip = String.format(this.fileFailedText, values.name);
				qtip += '<br>' + this.errorText + ':' + values.error;
				qtip += '<br>' + this.clickRemoveText;
			break;

			case 4: //stopped
				qtip = String.format(this.fileStoppedText, values.name);
				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
			,name: fileName
			,size: 0
			,date: ''
			,status: 0
		}, 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);
		}
		
		if (this.store.getCount() >= this.file_num) {
			//Ext.MessageBox.alert(i18n_fileupload.tip, i18n_fileupload.limit1 + this.file_num + i18n_fileupload.limit2);
			this.addBtn.disable();
		}
		

	} // 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);
		
		if (record.get('status') == 2) {
			Ext.Ajax.request({
				url: this.delete_url
				,success: function() {
					//Ext.MessageBox.alert('OK','附件删除成功!');
					if (this.delete_callback != undefined) {
						this.delete_callback(record);
					}
				}
				,failure: function() {
					//Ext.MessageBox.alert('Error','附件删除失败!');
				}
				,params: {Filename: record.get('name')}
			});
		}
		
		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();
		}
		
		if (this.store.getCount() > 0) {
			this.removeAllBtn.enable();
		} else {
			this.removeAllBtn.disable();
		}
		if (this.store.getCount() >= this.file_num) {
			this.addBtn.disable();
		} else {
			this.addBtn.enable();
		}
	} // 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-0|.ux-up-icon-2|.ux-up-icon-3|.ux-up-icon-4)');
		if (t) {
			this.onRemoveFile(this.store.getAt(index));
		}
		t = e.getTarget('div.ux-up-icon-1');
		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;
		}
		
		if (this.store.getCount() > 0) {
			this.removeAllBtn.enable();
		} else {
			this.removeAllBtn.disable();
		}
		if (this.store.getCount() >= this.file_num) {
			this.addBtn.disable();
		} else {
			this.addBtn.enable();
		}
	} // 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

