/////////////////////////////////////////////////////////////////////////////
//
// Gladiator Components
//
// http://www.gladiatorweb.com
//
// (c) 2006 by Edward H. Trager .  All Rights Reserved
//
// NAME: gladiatorTableEditor.js
//
// DESCRIPTION: Implements an object that makes an ordinary HTML table editable.
//
// AUTHOR: Edward H. Trager
//
// LAST UPDATE: 2006.07.18
//
// NOTE: SEE "LICENSE" FILE FOR LICENSING TERMS
//
//////////////////////////////////////////////////////////////////////////////

//
// DEPENDENCY CHECKING:
//
if(typeof GLADIATOR_CORE_INCLUDED == 'undefined') alert('NOTE BENE: "gladiatorTableEditor.js" REQUIRES "gladiatorCore.js"!');

var GLADIATOR_TABLE_EDITOR_INCLUDED=true;


var TABLE_EDITOR_MODE = new Object();
TABLE_EDITOR_MODE.includeAll       = 1; // Edit all cells except for core columns that are not allowed anyway.
TABLE_EDITOR_MODE.includeTaggedYes = 2; // Edit only cells that are tagged with    ' editable="yes" '.
TABLE_EDITOR_MODE.excludeTaggedNo  = 3; // Exclude only cells that are tagged with ' editable="no"  '.

//
// class gTableEditor
//
// Note Bene: Currently only works on scrollable tables.
//
// Parameters:
// ===========
//
//   tableNode: the DOM <table> node of the scrollable table.
//   
//   nonEditableColumnCount: How many left-side columns are
//                           non-editable.  This is related to the
//                           number of fixed columns in the scrollable
//                           table.
//
function gTableEditor(tableNode,nonEditableColumnCount,scrollableTableObjectInstance){
	
	
	var _targetTextNode=false; // This should be a text node child of a a <td> cell node.
	var _parentCellNode=false; // This should be a "<td>" cell node in a table.
	
	var _columnCount=0;
	var _rowCount=0;
	
	var _nonEditableColumnCount=nonEditableColumnCount;
	var _scrollableTableObjectInstance= ( scrollableTableObjectInstance ? scrollableTableObjectInstance : false );
	
	var _cellNodes; // array of TD tags
	
	var _grid = new Object; // 2D Grid of references to all cells in the table
	
	var _confirmEventHandler="";
	
	//
	// Create the active cell editor:
	//
	var _cellEditor = document.createElementNS(NS.xhtml,"div");
	    _cellEditor.setAttribute("class","gCE");
	var _wrappingDiv = document.createElementNS(NS.xhtml,"div");
	    _wrappingDiv.setAttribute("style","overflow:auto;");
	var _activeCell = document.createElementNS(NS.xhtml,"input");
	    _activeCell.setAttribute("class","gAC");
	var _confirm    = document.createElementNS(NS.xhtml,"div");
	    _confirm.setAttribute("class","gCF");
	var _dismiss    = document.createElementNS(NS.xhtml,"div");
	    _dismiss.setAttribute("class","gDS");
	_wrappingDiv.appendChild(_activeCell);
	_activeCell.focus();
	_cellEditor.appendChild( _wrappingDiv );
	_cellEditor.appendChild( _confirm    );
	_cellEditor.appendChild( _dismiss    );
	
	//
	// Store the positional offsets that will be used
	// to adjust the _activeCell's position due to the border
	// width attributes for the gAC class from CSS:
	//
	// = - parseInt(getElementStyle(_activeCell,"border-left-width"));
	// = - parseInt(getElementStyle(_activeCell,"border-top-width" ));
	//
	var _leftAdjustment; 
	var _topAdjustment;  
	var _self = this;
	
	/////////////////////////////////////////////////
	//
	// Mode: There are three modes, described above.
	//       By default, include all allowable cells
	//       for editing.
	//
	//////////////////////////////////////////////////
	
	var _mode=TABLE_EDITOR_MODE.includeAll;
	
	////////////////////////////////////////////////
	//
	// Add the cellEditor to the table node
	// of the scrollable table:
	//
	////////////////////////////////////////////////
	if(tableNode){
		
		tableNode.appendChild( _cellEditor );
		
	}else{
		alert("Error: Pass tableNode to the constructor");
		return;
	}
	
	///////////////////
	//
	// PUBLIC METHODS:
	//
	///////////////////
	
	//
	// setMode: Determines which cells in a table are editable.
	//
	this.setMode = function( mode ){
		
		if(mode < TABLE_EDITOR_MODE.includeAll || mode > TABLE_EDITOR_MODE.excludeTaggedNo) return;
		// Set mode:
		_mode = mode;
		
	}
	
	
	//
	// activateTable() : Makes a table editable by
	//                   adding event listeners on the cells
	//                  
	this.activateTable = function( tableNode ){
		
		if(!tableNode) tableNode=document;
		//
		// Cells in the table: 
		//
		_cellNodes = tableNode.getElementsByTagName("td");
		
		//
		// Determine the font size and style already in use in
		// the table, and set the _activeCell's CSS to match
		// it exactly :
		//
		_activeCell.style.fontSize   = getElementStyle(_cellNodes[0],"font-size");
		_activeCell.style.fontFamily = getElementStyle(_cellNodes[0],"font-family");
		_activeCell.style.fontStyle  = getElementStyle(_cellNodes[0],"font-style");
		_activeCell.style.fontWeight = getElementStyle(_cellNodes[0],"font-weight");
		//
		// Calculate left and top adjustments based on difference in
		// border widths between target cells in the table and the activeCell's
		// border width:
		//
		_leftAdjustment = 0.5*parseInt(getElementStyle(_cellNodes[0],"border-left-width")) -
		                      parseInt(getElementStyle(_activeCell,"border-left-width")) -1;
		                      
		_topAdjustment  = 0.5*parseInt(getElementStyle(_cellNodes[0],"border-top-width")) -
		                      parseInt(getElementStyle(_activeCell,"border-top-width")) - 1;
		                      
		if(_cellNodes.length){
			//
			// Loop through table cells:
			//
			var col=0;_rowCount=0;
			var lastParent = false;
			
			for(var i=0;i<_cellNodes.length;i++){
				
				var isEditable=_cellNodes[i].getAttribute("editable");
				//
				// Set event listeners on cells based on mode:
				//
				if(_mode==TABLE_EDITOR_MODE.includeTaggedYes && isEditable=="y"){
					
					_cellNodes[i].addEventListener("click",_self.editCell,false);
					
				}else if(_mode==TABLE_EDITOR_MODE.excludeTaggedNo && isEditable!="n"){
					
					_cellNodes[i].addEventListener("click",_self.editCell,false);
					
				}else if(_mode==TABLE_EDITOR_MODE.includeAll){
					
					_cellNodes[i].addEventListener("click",_self.editCell,false);
					
				}
				
				//
				// Detect whenever we change to a new row in the table
				// and track current row and column:
				//
				if(_cellNodes[i].parentNode!=lastParent){
					_rowCount++;
					col=0;
					lastParent=_cellNodes[i].parentNode;
					_grid[ _rowCount-1 ]= new Object;
				}
				//
				// The class maintains a 2-dimensional "array"
				// (actually, we use JS Object) of the table cells.
				// We also assign "row" and "column" attributes so that
				// we can programatically and instantly navigate to any
				// cell that we want:
				//
				_grid[ _rowCount-1 ][ col ]=_cellNodes[i];
				_cellNodes[i].setAttribute("row",_rowCount-1);
				_cellNodes[i].setAttribute("column",col);
				col++;
			}
			// Finally, store _columnCount:
			_columnCount = col;
			
		}
	
	}
	
	//
	// editCell :
	//
	this.editCell = function(e){
		
		e=gEvent(e);
		var targetNode = e.currentTarget;
		
		// Get column number ...
		var col = targetNode.getAttribute("column");
		//
		// ... and prevent editing of the leftmost
		//     non-Editable columns:
		//
		if(col <= _nonEditableColumnCount-1){
			return;
		}
		
		//
		// If the cell to be edited is part of the
		// scrollable table's fixed column set, then:
		//
		if( _scrollableTableObjectInstance){
			
			if( col < _scrollableTableObjectInstance.getFixedColumnCount() ){
				_scrollableTableObjectInstance.showRowIndex(false);
			}else{
				_scrollableTableObjectInstance.showRowIndex(true);
			}
			
		}
		
		//
		// Using new findPosition() utility function
		// in gladiatorCore : returns an array with left,top:
		//
		var position = findPosition(targetNode);
		_cellEditor.style.left   = _leftAdjustment + position[0] + "px";
		_cellEditor.style.top    = _topAdjustment  + position[1] + "px";
		
		var targetDimensions = getComputedWidthAndHeight(targetNode);
		// 
		// The extra "50" on the width comes from the fact that we need room for the
		// confirm and dismiss icons:
		//
		// The "-2" is a required fudge factor needed to achieve perfect
		// visual alignment in firefox.  I don't have a good explanation
		// of why this is required:
		_cellEditor.style.width     = -2 + targetDimensions[0] + 50 + "px";
		_cellEditor.style.height    = -2 + targetDimensions[1] + "px";
		_activeCell.style.width = -2 + targetDimensions[0] + "px";
		_activeCell.style.height= -2 + targetDimensions[1] + "px";
		
		_confirm.style.height       = _cellEditor.style.height;
		_dismiss.style.height       = _cellEditor.style.height;
		
		if(targetNode.firstChild && targetNode.firstChild.nodeValue){
			_activeCell.value        = targetNode.firstChild.nodeValue;
		}else{
			_activeCell.value        = "";
		}
		if(_activeCell.value == ".") _activeCell.value="";
		// Show the editor:
		_cellEditor.style.display="block";
		//
		// Save targets so they can be referenced 
		// by other methods in the class:
		//
		_parentCellNode = targetNode;
		_targetTextNode = targetNode.firstChild;
		
		//
		// Put the focus on the _activeCell:
		//
		
		_activeCell.focus();
		
		
		
	}
	
	//
	// myConfirm(): Confirms an entry
	//
	this.myConfirm = function(e){
		
		//
		// Create the child text node 
		// if none is present (i.e., empty cell):
		//
		if(!_targetTextNode){
			
			_targetTextNode = document.createTextNode(_activeCell.value);
			_parentCellNode.appendChild(_targetTextNode);
			
		}
		var oldValue = "";
		var newValue = "";
		//
		// Update cell contents as necessary:
		//
		if(_activeCell.value == "") _targetTextNode.nodeValue=".";
		else if( _targetTextNode.nodeValue != _activeCell.value ){
			oldValue = _targetTextNode.nodeValue;
			newValue = _activeCell.value;
			_targetTextNode.nodeValue = _activeCell.value;
			
			if(_scrollableTableObjectInstance){
				//
				// Check whether header column width needs to be updated:
				//
				var col = _parentCellNode.getAttribute("column");
				var row = _parentCellNode.getAttribute("row");
				_scrollableTableObjectInstance.setRowIndexTableValue(row,col,_activeCell.value);
				_scrollableTableObjectInstance.setHeaderWidth(_parentCellNode,col);
				
			}
		}
		
		// Hide the editor:
		_cellEditor.style.display="none";
		//
		// No need to call the scrollableTable's showRowIndex() because setHeaderWidth()
		// above does it already ...
		//
		if(oldValue != newValue && _confirmEventHandler){
			_confirmEventHandler(oldValue,newValue,_parentCellNode.className);
		}
	
	}
	
	//
	// myDismiss(): Dismisses an edit
	//
	this.myDismiss = function(e){
		
		if(_activeCell.value == "") _targetTextNode.nodeValue =".";
		// Hide the editor:
		_cellEditor.style.display="none";
		//
		// Call scrollableTable's showRowIndex just in case it is needed:
		//
		if(_scrollableTableObjectInstance){
			_scrollableTableObjectInstance.showRowIndex(true);
		}
	}
	
	//
	// handleKeyEvents
	//
	this.handleKeyEvents = function(e){
		
		
		switch(e.keyCode){
		case KB.enter:
		case KB.tab:
			_self.myConfirm(e);
			break;
			
		case KB.escape:
			_self.myDismiss(e);
			break;
			
		case KB.left:
			//
			// Allow cursor to move within cell normally
			// unless we hit the left edge:
			//
			
			if(_activeCell.selectionStart>0) return;
			
			// Left arrow key:
			//_self.myConfirm(e);
			_self.myDismiss(e);
			// Get row and column number:
			var row = _parentCellNode.getAttribute("row");
			var col = _parentCellNode.getAttribute("column");
			//
			// Decrement column ... :
			//
			col--;
			//
			// ... and wrap to the highest column if
			// the next column to the left is non-editable:
			//
			if(col <= _nonEditableColumnCount-1){
				col=_columnCount-1;
			}
			
			var evt=document.createEvent("MouseEvents");
			evt.initUIEvent("click",true,true,window,1);
			_grid[row][col].scrollIntoView(false);
			_grid[row][col].dispatchEvent(evt);
			break;
			
		case KB.right:
			//
			// Allow cursor to move within cell normally
			// unless we hit the right edge:
			//
			if(_activeCell.selectionStart<_activeCell.value.length) return;
			
			// Right arrow key:
			//_self.myConfirm(e);
			_self.myDismiss(e);
			// Get row and column number:
			var row = _parentCellNode.getAttribute("row");
			var col = _parentCellNode.getAttribute("column");
			//
			// Increment column ... :
			//
			col++;
			//
			// ... and wrap over to leftmost editable
			// column if col is greater than columnCount:
			//
			if(col>_columnCount-1){
				col=_nonEditableColumnCount;
			}
			
			var evt=document.createEvent("MouseEvents");
			evt.initUIEvent("click",true,true,window,1);
			_grid[row][col].scrollIntoView(false);
			_grid[row][col].dispatchEvent(evt);
			
			break;
			
		case KB.down:
			
			// Down arrow key:
			_self.myConfirm(e);
			//_self.myDismiss(e);
			
			// Get row and column number:
			var row = _parentCellNode.getAttribute("row");
			var col = _parentCellNode.getAttribute("column");
			
			row++;
			if(row>_rowCount-1) row=0;
			
			var evt=document.createEvent("MouseEvents");
			evt.initUIEvent("click",true,true,window,1);
			_grid[row][col].scrollIntoView(false);
			_grid[row][col].dispatchEvent(evt);
			
			break;
			
		case KB.up:
			
			// Up arrow key:
			//_self.myConfirm(e);
			_self.myDismiss(e);
			// Get row and column number:
			var row = _parentCellNode.getAttribute("row");
			var col = _parentCellNode.getAttribute("column");
			
			row--;
			if(row<0) row=_rowCount-1;
			
			var evt=document.createEvent("MouseEvents");
			evt.initUIEvent("click",true,true,window,1);
			_grid[row][col].scrollIntoView(false);
			_grid[row][col].dispatchEvent(evt);
			
			break;

			
		default:
			//alert(e.keyCode);
			break;
		}
		
	}
	
	this.addConfirmEventHandler = function(handler){
		
		_confirmEventHandler = handler;
		
	}
	
	//
	// Add Event Handlers:
	//
	_confirm.addEventListener("click",_self.myConfirm,false);
	_dismiss.addEventListener("click",_self.myDismiss,false);
	_activeCell.addEventListener("keypress",_self.handleKeyEvents,false);
	
	// Be sure to dismiss when window is resized -- Otherwise
	// the _activeCell would get misaligned with the table:
	window.onresize=_self.myDismiss;
	
}

