Ext.namespace('Ext.ux.form');
/**
 * @class Ext.ux.form.SuperField 
 * @constructor
 * @author <a href="mailto:danh2000@gmail.com">Dan Humphrey</a>
 * @version 0.3
 */
Ext.ux.form.SuperField = {
	items : [],
	hideLabel : true,
	minItems : 0,
	minItemsText : 'This field expects a minimum of {0} items',
	maxItems : 0,
	maxItemsText : 'This field accepts a maximum of {0} items',
	arrayAppendChar : '[]', //needs to think about this
	stripeRows: true, 
	msgTarget : 'qtip',
	disableMaskedElements: true,
	addLabel : 'Add',
	defaultItemText: 'Item',
	renderSummaryHeader: true,
	centerSummaryData: false,
	values : [], 
	windowConfig : {
		resizable : false,
		autoHeight : true
	},
	initComponent : function() {
		this.validationEvent = 'blur';
		this.focusClass = '';
		this.emptyText = '';
		this.ti = this.tabIndex || '';
		delete this.tabIndex;
		this.defaultAutoCreate = {
			tag: 'div',
			cls: 'x-superfield'	
		};
		this.stripeClass = 'odd';
		this.allowAdd = true;
		this.getRawValue = this.getValue;
		this.arrayAppend = (this.maxItems === 0) ? this.arrayAppendChar : (this.maxItems > 1) ? this.arrayAppendChar : '';
		//saved items collection
		this.savedItems = new Ext.util.MixedCollection(false);
		this.saveId = 0;
		//format minItemsText and maxItemsText messages
		this.minItemsText = String.format(this.minItemsText,this.minItems);
		this.maxItemsText = String.format(this.maxItemsText,this.maxItems);
		
		this.summaryHeaderFields = [];
		this.itemClassArray = [];
	
		for(var i = 0, j = this.items.length, head, item; i < j; i++){
			item = this.items[i];
			if (item.summaryCls) {
				this.itemClassArray[item.name] = item.summaryCls;
			}
			if(item.summaryDisplay){
				head = item.summaryHeader ? item.summaryHeader : item.fieldLabel;
				this.summaryHeaderFields.push({
					name : item.name,
					header: head
				}); 
			}		
		}
		//save button tab index
		var ti = this.items[this.items.length-1].tabIndex + 1 || -1;
		
		//ITEMS FORM AND ITEMS WINDOW
		this.itemsForm = new  Ext.FormPanel({
			bodyStyle : 'padding:10px;',
			items : this.items,
			autoScroll : true,
			monitorValid : true,
			autoHeight:true,
			labelWidth: 180,
			buttons : [{
				text : 'Save',
				tabIndex: ti,
				handler : function(){
					var f = this.itemsForm.form;
					this.itemsWindow.hide();
					this.saveItem.call(this,f.getValues());
					f.reset();
					this.addButtonEl.focus();
				},
				scope : this,
				formBind : true
			}]
		});
		// create items window
		// required config that cannot be overridden
		var winConfig = {
			title : this.addLabel,
			closeAction : 'hide',
			items : [this.itemsForm],
			modal:true,
			listeners : {
				'beforeshow' : {
					fn : function() {
						try{
							var first = Ext.getCmp(this.items[0].id);
							this.itemsForm.form.clearInvalid();
							first.focus(true,150);
						}catch(e){}
					},
					delay : 50,
					scope : this
				}
			}
		};
		//apply user window config
		Ext.applyIf(winConfig, this.windowConfig);
		// window config defaults that will be applied if not provided
		var windowConfigDefaults = {
			autoScroll : true,
			title : this.addLabel,
			layout : 'fit',
			modal : true,
			width : 450,
			height : 300
		};
		Ext.applyIf(winConfig, windowConfigDefaults);
		this.itemsWindow = new Ext.Window(winConfig);
		
		//prevent tabbing behind 
		this.itemsWindow.on('beforeshow',function(){
			if (this.disableMaskedElements === true) {
				this.disabledMaskedControls = [];
				var db = Ext.fly(document.body);
				var els = db.select("input, select, a, button");
				els.each(function(el){
					if (!el.dom.disabled && el.isVisible() && el.findParent('div[id='+this.itemsWindow.getId()+']') === null) {
						el.dom.disabled = true;
						this.disabledMaskedControls.push(el.dom);
					}
				}, this);
			}
		},this);
		this.itemsWindow.on('beforehide',function(){
			Ext.each(this.disabledMaskedControls, function(el) {
				el.disabled = false;
			});
		},this);
		//end prevent tabbing behind 

		//CUSTOM EVENTS
		this.addEvents(
			'beforeadditem',
			'additem',
			'beforesaveitem',
			'beforeremoveitem',
			'removeitem'
		);
		//super	
		Ext.ux.form.SuperField.superclass.initComponent.call(this);
	},
	onRender : function(ct, position){
		//super
		Ext.ux.form.SuperField.superclass.onRender.call(this, ct, position);
		//create wrap
		this.wrapEl = this.el.wrap({
			tag : 'div',
			cls : 'x-superfield-wrap'
		});
		//create header
		this.header = this.el.createChild({
			tag : 'div',
			cls : 'x-superfield-header' 
		});
		this.createAddButton();
		//create error container for msgTarget of 'side'
		this.errCt = this.header.createChild({
			tag:'div',
			style: 'width:20px;float:right;height:20px;',
			html: '&nbsp;'	
		}); 
		this.header.createChild({
			tag:'div',
			style: 'line-height:1px;clear:both;',
			html: '&nbsp;'
		});
		//create items container
		this.createItemsContainer();
	
		this.el.on('focus',function(){
			this.addButtonEl.focus();
		},this);
		// set values passed into config
		this.setValue.call(this, this.values);
		delete this.values;
	},
	afterRender : function() {
		Ext.ux.form.SuperField.superclass.afterRender.call(this);
		this.originalValue = Ext.encode(this.getValue());
	},
	onDestroy : function() {
		this.clearValues();
		this.addButtonEl.removeAllListeners();
		this.addButtonEl.remove();
		this.addButton.removeAllListeners();
		this.addButton.remove();
		this.errCt.remove();
		this.header.remove();
		this.itemsContainer.remove();
		this.wrapEl.remove();
		this.itemsForm.destroy();
		this.itemsWindow.destroy();
		Ext.ux.form.SuperField.superclass.onDestroy.call(this);
	},
	addButtonOver : function() {
		if (this.disabled || this.allowAdd === false) {
			return;
		}
		this.addButton.addClass('over');
	},
	addButtonOut : function() {
		this.addButton.removeClass('over');
	},
	addButtonBlur : function() {
		this.addButtonOut.call(this);
		this.validateValue();	
	},
	createAddButton : function() {
		this.addButton = this.header.createChild({
			tag : 'table',
			cellpadding : '0',
			cellspacing : '0',
			border : '0',
			style:'width:auto;float:left;',
			cls : 'x-superfield-button',
			html :  '<tbody><tr>'+
					'<td><button type="button" class="x-superfield-button-icon add" tabIndex="'+this.ti+'"></button></td>'+
					'<td class="x-superfield-button-text"><em unselectable="on">' + this.addLabel + '</em></td>' +
					'</tr></tbody>'
		});
		
		this.addButton.on({
			'mouseover' : this.addButtonOver,
			'mouseout' : this.addButtonOut,
			'focus' : this.addButtonOver,
			'blur' : this.addButtonBlur,
			'click' : function() {
				if (this.disabled || this.allowAdd === false || this.addButtonEl.dom.disabled === true) {
					return;
				}
				if (this.fireEvent('beforeadditem') !== false) {
					this.itemsForm.form.reset();
					this.itemsWindow.show();
				}
			},
			scope : this
		});
		
		this.addButtonEl = this.addButton.down('tbody tr td button');
		//add button events to the add button element
		this.addButtonEl.on({
			'focus': this.addButtonOver,
			'blur': this.addButtonBlur,
			scope: this
		});
	},
	getStripeClass : function() {
		if (this.stripeRows === true) {
			this.stripeClass = (this.stripeClass == 'even') ? 'odd' : 'even';
		}else{
			this.stripeClass = '';
		}
		return this.stripeClass;
	},
	createItemsContainer : function(){
		//item summary header
		var summaryHeader = '';
		if (this.renderSummaryHeader) {
			if (this.summaryHeaderFields.length) {
				summaryHeader = '<thead><tr>';
				for (var i = 0, j = this.summaryHeaderFields.length; i < j; i++) {
					summaryHeader += '<th class="x-superfield-summary-header">' + this.summaryHeaderFields[i].header + '</th>';
				}
				summaryHeader += '<th width="16" class="x-superfield-summary-header"></th>';//extra column for item button 
				summaryHeader += '</tr></thead>';
			}
		}
		var itemsCls = 'x-superfield-items';
		if(this.centerSummaryData === true){
			itemsCls += ' center';	
		}
		//items table and container
		var itemsTable = this.el.createChild({
			tag : 'table',
			width : '100%',
			cls : itemsCls ,
			html : summaryHeader + '<tbody><tr></tr></tbody>'
		});
		this.itemsContainer = itemsTable.down('tbody');
	
	},
	manageAddButton : function() {
		if (!this.rendered) { // not rendered
			return;
		}
		// disable add button
		if (this.maxItems > 0 && (this.savedItems.getCount() == this.maxItems)) {
			this.allowAdd = false;
			this.header.setOpacity(0.5,true);
			this.header.addClass('disabled');
			Ext.QuickTips.register({ 
				target: this.header.id,
    			text: this.maxItemsText
			});
		// enable add button
		} else {
			Ext.QuickTips.unregister(this.header.id);
			this.allowAdd = true;
			this.header.clearOpacity();
			this.header.removeClass('disabled');
		}
	},
	fixItemNumbers : function(){
		if (this.summaryHeaderFields.length && this.renderSummaryHeader){
			return;
		}
		var ic = 1;
		this.itemsContainer.select('tr.x-superfield-item').each(function(i){
			i.down('td').update(this.defaultItemText+' '+ ic++);
		},this);
	},
	fixStripes : function(){
		if (this.stripeRows !== true){
			return;
		}
		this.itemsContainer.select('tr.x-superfield-item').each(function(i){
			if (i.hasClass('odd')) {
				i.removeClass('odd');
			}
			if (i.hasClass('even')) {
				i.removeClass('even');
			}
			i.addClass(this.getStripeClass());
		},this);
	},
	saveItem : function(itemVals) {
		// prevent saving item if subscriber returns false to beforestoreitem
		if (this.fireEvent('beforesaveitem', itemVals) === false) {
			return;
		}
		// create hidden elements and add to the owner container
		// and hidden elements array
		var hiddenElements = [];
		for (var p in itemVals) {
			var h = new Ext.form.Hidden({
				id : p + this.saveId,
				name : p + this.arrayAppend,
				value : itemVals[p]
			});
			hiddenElements.push(h);
			this.ownerCt.add(h);
		}
		// store new item
		var newItem = {
			hiddenElements : hiddenElements,
			values : itemVals
		};
		var startVal = this.getValue();
		this.savedItems.add(this.saveId, newItem);
		++this.saveId; //increment save id
		this.fireEvent('change', this, startVal, this.getValue());
		
		var rowData = '';
		var cls;
		for(var i = 0,j = this.summaryHeaderFields.length;i <j;i++){
			//item class
			cls = this.itemClassArray[this.summaryHeaderFields[i].name] || '';
		
			rowData += '<td nowrap="nowrap" class="'+cls+'">' + itemVals[this.summaryHeaderFields[i].name] + '</td>';
		}
		if(rowData === ''){ //no summary data
			rowData = '<td>'+this.defaultItemText+' ' + this.savedItems.getCount() +'</td>';
		}
		//render the item row
		var iRow = this.itemsContainer.createChild({
			tag : 'tr',
			cls : 'x-superfield-item ' + this.getStripeClass(),
			html : rowData + '<td class="x-superfield-button" width="16"><em unselectable="on"><button type="button" class="x-superfield-button-icon minus" id=""></button></em></td>'
		});
		
		iRow.hide();
		
		// create the item remove button and apply listeners
		var itemButton = iRow.down('td.x-superfield-button');
		var btnEl = itemButton.down('em button');
		
		var itemButtonOver = function() {
			if (this.disabled) {
				return;
			}
			itemButton.addClass('over');
		};
		var itemButtonOut = function() {
			if (this.disabled) {
				return;
			}
			itemButton.removeClass('over');
		};
		
		btnEl.on({
			'focus': itemButtonOver,
			'blur': itemButtonOut,
			scope: this
		});
		itemButton.on({
			'focus' : itemButtonOver,
			'blur' : itemButtonOut,
			'mouseover' : itemButtonOver,
			'mouseout' : itemButtonOut,
			'click' : function() {
				if (this.disabled) {
					return;
				}
				// prevent remove if event returns false
				if (this.fireEvent('beforeremoveitem', itemVals) === false) {
					return;
				}

				// remove and destroy hidden elements
				Ext.each(hiddenElements, function(h) {
					this.ownerCt.remove(h, true);
					h = null;
				}, this);
				this.fireEvent('removeitem', itemVals);
				this.savedItems.remove(newItem);
				var startVal = this.getValue();
				this.fireEvent('change', this, startVal, this.getValue());
				btnEl.removeAllListeners();
				itemButton.removeAllListeners();
				itemButton = null;
				iRow.remove();
				iRow = null;
				hiddenElements = null;

				this.manageAddButton.call(this);
				this.validate();
				this.ownerCt.doLayout();
				this.fixStripes();
				this.fixItemNumbers();
			},
			scope : this
		});
		
		iRow.show(true);
		this.manageAddButton.call(this);
		this.ownerCt.doLayout();
		
		this.validate();
		this.fireEvent('additem', itemVals);
		return itemVals;
	},
	initValue : Ext.emptyFn,
	clearInvalid : function() {
		if (!this.rendered || this.preventMark) { // not rendered
			return;
		}
		this.el.removeClass(this.invalidClass);
		switch (this.msgTarget) {
			case 'qtip' :
				this.header.dom.qtip = '';
				this.header.dom.qclass = '';
				break;
			case 'title' :
				this.el.dom.title = '';
				break;
			case 'under' :
				if (this.errorEl) {
					Ext.form.Field.msgFx[this.msgFx].hide(this.errorEl, this);
				}
				break;
			case 'side' :
				if (this.errorIcon) {
					this.errorIcon.dom.qtip = '';
					this.errorIcon.hide();
					this.un('resize', this.alignErrorIcon, this);
				}
				break;
			default :
				if (this.msgTarget) {
					var t = Ext.getDom(this.msgTarget);
					t.innerHTML = '';
					t.style.display = 'none';
				}
				break;
		}
		this.fireEvent('valid', this);
	},
	markInvalid : function(msg) {
		if (!this.rendered || this.preventMark) { // not rendered
			return;
		}
		this.el.addClass(this.invalidClass);
		msg = msg || this.invalidText;
		var elp;
		switch (this.msgTarget) {
			case 'qtip' :
				this.header.dom.qtip = msg;
				this.header.dom.qclass = 'x-form-invalid-tip';
				if (Ext.QuickTips) { // fix for floating editors interacting with DND
					Ext.QuickTips.enable();
				}
				break;
			case 'title' :
				this.el.dom.title = msg;
				break;
			case 'under' :
				if (!this.errorEl) {
					elp = this.getErrorCt();
					this.errorEl = elp.createChild({
						cls : 'x-form-invalid-msg'
					});
					this.errorEl.setWidth(elp.getWidth(true) - 20);
				}
				this.errorEl.update(msg);
				Ext.form.Field.msgFx[this.msgFx].show(this.errorEl, this);
				break;
			case 'side' :
				if (!this.errorIcon) {
					elp = this.getErrorCt();
					this.errorIcon = elp.createChild({
						cls : 'x-form-invalid-icon'
					});
				}
				this.alignErrorIcon();
				this.errorIcon.dom.qtip = msg;
				this.errorIcon.dom.qclass = 'x-form-invalid-tip';
				this.errorIcon.show();
				this.on('resize', this.alignErrorIcon, this);
				break;
			default :
				if (this.msgTarget) {
					var t = Ext.getDom(this.msgTarget);
					t.innerHTML = msg;
					t.style.display = this.msgDisplay;
					break;
				}
		}
		this.fireEvent('invalid', this, msg);
	},
	alignErrorIcon : function(){
    	this.errorIcon.alignTo(this.errCt, 'tr?', [-18, 0]);
    },
	disable : function() {
		this.clearInvalid();
		Ext.ux.form.SuperField.superclass.disable.call(this);
	},
	validateValue : function(value) {
		if (this.disabled) {
			return;
		}
		if ((this.minItems > 0) && this.savedItems.getCount() < this.minItems) {
			this.markInvalid(this.minItemsText);
			return false;
		}
		return true;
	},
	reset : function() {
		if (this.disabled) {
			return;
		}
		this.setValue(Ext.decode(this.originalValue));
		this.manageAddButton.call(this);
	},
	isDirty : function() {
		if (this.disabled) {
			return false;
		}
		return Ext.encode(this.getValue()) !== this.originalValue;
	},
	getValue : function() {
		var ret = [];
		this.savedItems.each(function(item, idx, len) {
			ret.push(item.values);
		});
		return ret;
	},
	setValue : function(values) {
		if (this.disabled || !values) {
			return;
		}
		// kill existing
		this.clearValues.call(this);
		if (Ext.isArray(values)) {
			var vc = 0;
			Ext.each(values, function(v) {
				this.saveItem.call(this, v);
				++vc;
				if (vc == this.maxItems) { // prevent adding too many
					return false;
				}
			}, this);

		} else {
			this.saveItem.call(this, values);
		}
	},
	clearValues : function() {
		// kill items
		var el = this.itemsContainer.down('tr.x-superfield-item');
		while (el) {
			var itemButton = el.down('td.x-superfield-button');
			var btnEl = itemButton.down('em button');
			btnEl.removeAllListeners();
			btnEl.remove();
			itemButton.removeAllListeners();
			itemButton.remove();
			el.remove(true);
			el = this.itemsContainer.down('tr.x-superfield-item');
		}
		el = null;
		this.savedItems.each(function(item, idx, len) {
			for (var i = 0, j = item.hiddenElements.length; i < j; i++) {
				this.ownerCt.remove(item.hiddenElements[i], true);
			}
		}, this);
		this.stripeClass = 'odd';
		this.savedItems.clear();
		this.ownerCt.doLayout();
	},
	getErrorCt : function() {
		if(this.msgTarget == 'under'){
			return this.wrapEl;
		}
		return this.errCt;
	}
};
Ext.ux.form.SuperField = Ext.extend(Ext.form.Field,Ext.ux.form.SuperField);