/*
 * $Header: /CSC/itemtable.js   2   2005-11-16 11:52:49+01:00   eruis $
 * Created on:      29-Januari-2004
 * Original author: Eric Ruis
 *
 * Description:
 *	Implements a dynamic table with a fixed maximum columns. The table has the following mandatory layout:
 *
 *  <table id="name">
 *		<tr id="name.headerRow">
 *			<td> .. </td>
 *			<td> .. </td>
 *			<td> .. </td>
 *			...
 *		</tr>
 *		<tr id="name.firstRow">
 *			<td id="removeBtnCol"><input type="button" style="visibility: hidden" value="Remove" name="removeButton"></td>
 *			<td id="name.col1> .. </td>
 *			<td id="name.col2> .. </td>
 *			...
 *		</tr>
 *		<tr id="name.lastRow">
 *			<td> .. </td>
 *			<td> .. </td>
 *			<td> .. </td>
 *			...
 *		</tr>
 *  </table>
 */
function itemTable(tableName) {
	// the table
	this.name = tableName;
	this.table = document.getElementById(tableName);
	if (this.table == null) {
		alert("itemTable: table '" + tableName + "' does not exist");
		return this;
	}
	if (this.table.nodeName != "TABLE") {
		alert("itemTable: The element " + tableName + " is not a table but a " + this.table.nodeName);
		return this;
	}
	// the row nodes of the table are part of child element of the table node called TBODY
	this.tbody = this.table.firstChild;
	this.rows = this.tbody.childNodes;
	this.maxCols = 12;

	// table properties
	var rowList = this.rows;
	for (var k = 0; k < rowList.length; k++) {
		var id = rowList.item(k).getAttribute("id");
		if (id == (tableName + ".headerRow")) {
			this.headerRow = rowList.item(k); 
		}
		if (id == (tableName + ".firstRow")) { 
			this.firstRow = rowList.item(k); 
			this.firstRowIndex = k;
		}
		if (id == (tableName + ".lastRow")) { 
			this.lastRow = rowList.item(k); 
		}
	}
	if (this.headerRow == null) {
		alert("itemTable: table " + tableName + " does not contain a row with the id :'" + tableName + ".headerRow'");
		return this;
	}
	if (this.firstRow == null) {
		alert("itemTable: table " + tableName + " does not contain a row with the id :'" + tableName + ".firstRow'");
		return this;
	}
	if (this.lastRow == null) {
		alert("itemTable: table " + tableName + " does not contain a row with the id '" + tableName + ".lastRow'");
		return this;
	}

	// Get the indexes of the table columns
	var columns = this.firstRow.childNodes;
	this.removeBtnCol = null; // in case the table does not contain a remove button column
	for (var i = 0; i < columns.length; i++) {
		var id = columns.item(i).getAttribute("id");
		if (id == (tableName + ".col1")) { this.col1 = i; }
		if (id == (tableName + ".col2")) { this.col2 = i; }
		if (id == (tableName + ".col3")) { this.col3 = i; }
		if (id == (tableName + ".col4")) { this.col4 = i; }
		if (id == (tableName + ".col5")) { this.col5 = i; } 
		if (id == (tableName + ".col6")) { this.col6 = i; }
		if (id == (tableName + ".col7")) { this.col7 = i; }
		if (id == (tableName + ".col8")) { this.col8 = i; }
		if (id == (tableName + ".col9")) { this.col9 = i; }
		if (id == (tableName + ".col10")) { this.col10 = i; }
		if (id == (tableName + ".col11")) { this.col11 = i; }
		if (id == (tableName + ".col12")) { this.col12 = i; }
		if (id == (tableName + ".removeBtnCol")) {
			var removeBtn = columns.item(i).firstChild; 
			if (removeBtn == null || removeBtn.nodeName != "INPUT") {
				alert("itemTable: table " + tableName + " does not contain a remove button with the id '" + tableName +
				".removeBtnCol' or the remove button is not an input type");
			}
			this.removeBtnCol = i;
		}
	}

	// methods
	this.cloneFirstRow = it_cloneFirstRow;
	this.getRow = it_getRow;
	this.getRowIndex = it_getRowIndex;
	this.getRowNr = it_getRowNr;
	this.getColumn = it_getColumn;
	this.getColumnIndex = it_getColumnIndex; // internal use only. Use getColumn for getEntry and setEntry
	this.addRow = it_addRow;
	this.removeRow = it_removeRow;
	this.setEntry = it_setEntry;
	this.getEntry = it_getEntry;
	this.showRemoveBtn = it_showRemoveBtn;
	this.hideRemoveBtn = it_hideRemoveBtn;
	this.clearFirstRow = it_clearFirstRow;
	this.moveItemsOneUp = it_moveItemsOneUp;
	this.getRemoveBtn = it_getRemoveBtn;
	this.getNrOfItems = it_getNrOfItems;
	this.addToSessionState = it_addToSessionState;
	this.removeFromSessionState = it_removeFromSessionState;
	this.isFirstRowEmpty = it_isFirstRowEmpty;
	this.resetTable = new Function("it_resetTable('" + this.name + "')");
	this.blankColumn = it_blankColumn;
	this.showColumn = it_showColumn;
	this.hideColumn = it_hideColumn;
	
	// The real number of columns in the table. For example, if the table has a row with two td's, each with a colspan of 2, the function will return 4.
	this.realColumnCount = it_getRealColumnCount(this.firstRow); 
	
	return this;
}

// Returns a copy of the first row including layout specifications
function it_cloneFirstRow() {
	var row = this.firstRow.cloneNode(true);
	row.id = "item"; // change the id
	return row;
}

// Gets the row with the specified absolute index or null if the row does not exist
function it_getRow(itemNr) {
	if (itemNr < 0 || itemNr >= this.rows.length) return null;
	return this.rows.item(itemNr);
}

// Returns the absolute index of a row in the table
function it_getRowIndex(row) {
	for (var i = 0; i < this.rows.length; i++) {
		if (row == this.rows.item(i))
			return i;
	}
	return -1;
}

// Returns the row number to be used in setEntry
function it_getRowNr(itemNr) {
	return this.firstRowIndex + itemNr;
}

// Returns the 1-based column index for the specified column number. This method should be used to determine
// the column index from the column number (e.g. 2 when the column name is 'col2') for use in either getEntry
// or setEntry.
function it_getColumn(colNr) {
	var index = this.getColumnIndex(colNr);
	if (index != -1 && this.removeBtnCol == null) index += 1;
	return index;
}

// Returns the zero-based index of the column with the specified number or -1 if the column index
// is not defined.
function it_getColumnIndex(colNr) {
	if (colNr < 1 || colNr > this.maxCols) 
		return -1;
	var f = new Function("table", "return table.col" + colNr);
	var ix = f(this);
	if (ix == null) ix = -1;
	return ix
}

// Gets the number of items in the table (Between the first and the last row, not counting the last row 
// itself but including the first row)
function it_getNrOfItems() {
	var lastRowIndex = this.getRowIndex(this.lastRow);
	var nritems = lastRowIndex - this.firstRowIndex;
	if (nritems == 1 && this.isFirstRowEmpty())
		nritems = 0;
	return nritems;
}

// returns the remove button for the item row with the specified index (relative to the first row)
function it_getRemoveBtn(itemNr) {
	if (this.removeBtnCol == null) return null;
	var row = this.getRow(itemNr);
	if (row == null) return null;
	return row.childNodes.item(this.removeBtnCol).firstChild;
}

// Add a new row just before the last row to the table and returns the index of the new row
function it_addRow() {
	var lastRowIndex = 0;
	
	// in case the first row is empty we do not need to add a new row. Instead we need to update the column
	// values of the first row.
	if (!this.isFirstRowEmpty()) {
		var newRow = this.cloneFirstRow();
		this.tbody.insertBefore(newRow, this.lastRow);
		lastRowIndex = this.getRowIndex(this.lastRow);
	} else {
		lastRowIndex = this.getRowIndex(this.lastRow);

		// If the table does not contain a remove button column do nothing
		if (this.removeBtnCol != null) {
			// copy the onclick method of the remove button of the first row to the hidden input onclick method of the last row.
			// This method will be called when a row is deleted from the table
			var currentHTML = this.getEntry(lastRowIndex, this.removeBtnCol, true);
			
			debugWindow.writeln("it_addRow: table has a remove button column");
				
			// do nothing if the hidden input HTML has been created already.
			if (currentHTML == "") {
				this.setEntry(lastRowIndex, this.removeBtnCol, "<input id='lastremoveclick' type='hidden' onclick=''>", true);
				var inputObj = this.getRemoveBtn(lastRowIndex);
				var firstRowRemoveBtn = this.getRemoveBtn(this.firstRowIndex);
				inputObj.onclick = firstRowRemoveBtn.onclick;
			}
			
			this.showRemoveBtn();
		}
	}
	
	// calculate the index of the new row. Since it is added just before the last row, its index is the index of the
	// last row minus 1.
	var newRowIndex = lastRowIndex - 1;
	
	// Do nothing if the table does not contain a remove button column
	if (this.removeBtnCol != null) {
		var removeBtn = this.getRemoveBtn(newRowIndex);
		removeBtn.onclick = new Function("it_removeRow(" + newRowIndex + ", '" + this.name + "')");
	}
	
	return newRowIndex;
}

// Remove the row with the specified index. If the row cannot be found the function does nothing and returns false.
// If the row is removed succesfully the function returns true. Note that the it_removeRow method is called by
// pressing the remove button for a certain item without the context of the item table object. In that case the
// the item table object is reconstructed from its table name.
function it_removeRow(itemNr, tableName) {
	var table = this;
	if (tableName != null)
		table = new itemTable(tableName);

	var nritems = table.getNrOfItems();
	if (nritems == 1) {
		table.clearFirstRow();
		table.removeFromSessionState(itemNr);
	}
	else {
		table.moveItemsOneUp(itemNr);
		var lastRowIndex = table.getRowIndex(table.lastRow);
		table.removeFromSessionState(lastRowIndex);
	}
		
	// Call the onclick method that was originally set in the first row's remove button
	// This is only necessary in case the table contains a remove button column
	if (table.removeBtnCol != null) {
		var lastRowIndex = table.getRowIndex(table.lastRow);
		var inputObj = table.getRemoveBtn(lastRowIndex);
		if (inputObj != null && inputObj.onclick != null) {
			try { inputObj.onclick(); }
			catch (E) { alert("Unable to call the onclick method of the remove button"); }
		}
	}
	
	return true;
}

// Remove all items from the table
function it_resetTable(tableName) {
	if (tableName == null) return;
	var table = new itemTable(tableName);
	
	var i = table.getNrOfItems() - 1;
	for (var i = table.getNrOfItems() - 1; i >= 0; i--) {
		table.removeRow(i);
	}
}

// Changes the content of the entry with the specified row and column index.
// when the setHTML variable is set to true the method will set the innerHTML of the table entry instead of the innerText
// when the updateSessionState is set to true or left out (null) the session state will automatically be updated
function it_setEntry(rowNr, colNr, val, setHTML, updateSessionState) {
	if (val == null) 
		return false; 
	var row = this.getRow(rowNr);
	
	if (row == null || !row.hasChildNodes()) 
		return false;
	if (colNr < 0 || colNr >= row.childNodes.length) 
		return false;
	
	var col = row.childNodes.item(colNr);
	if (col == null) 
		return false;
	if (setHTML == null || setHTML == false)
		col.innerText = val;
	else
		col.innerHTML = val;
		
	// update the session state
	if (updateSessionState == null || updateSessionState) {
		var colIndex = this.getColumnIndex(colNr);
		if (colIndex != -1) {
			debugWindow.writeln("updating: " + this.name + "[" + rowNr + "," + colIndex + "]=" + val);
			calcSession.updateVariable(this.name + "[" + rowNr + "," + colIndex + "]", val);
		}
	}
	
	return true;
}

// Gets the content of the entry with the specified row and column index.
// when the returnHTML variable is set to true the method will return the inner HTML instead of the inner text
//
// 0 <= rowNr < getNrOfItems
// 0 <= colNr < NrOfColumns
// returnHTML: [true|false]
//
function it_getEntry(rowNr, colNr, returnHTML) {
	var row = this.getRow(rowNr);
	if (row == null || !row.hasChildNodes()) 
		return null;
	if (colNr < 0 || colNr >= row.childNodes.length) 
		return null;
	var col = row.childNodes.item(colNr);
	if (col == null)
		return null;
	if (returnHTML == null || returnHTML == false)
		return col.innerText;
	return col.innerHTML;
}

// shows the remove button in the first row
function it_showRemoveBtn() {
	if (this.removeBtnCol != null) {
		var removeBtn = this.getRemoveBtn(this.firstRowIndex);
		removeBtn.style['visibility'] = 'visible';
	}
}

// hides the remove button in the first row
function it_hideRemoveBtn() {
	if (this.removeBtnCol != null) {
		var removeBtn = this.getRemoveBtn(this.firstRowIndex);
		removeBtn.style['visibility'] = 'hidden';
	}
}

// clears the entries in the first row
function it_clearFirstRow() {
	var columns = this.firstRow.childNodes;
	for (var col = 1; col <= this.maxCols; col++) {
		var colIndex = this.getColumnIndex(col);
		if (colIndex != -1)
			columns.item(colIndex).innerText = "";
	}
	this.hideRemoveBtn();
}

// Blanks the values in the column with the specified Nr. The method
// does not remove the header text. Use getColumn to find the column
// number for a column index (the column index is the index of the 'td'
// element within the table row starting at zero).
function it_blankColumn(colNr) {
	for (var q = 0; q < this.getNrOfItems(); q++) {
		var qc = this.getRowNr(q); // get the real row nr for setEntry
		this.setEntry(qc, colNr, "", true); // clear the innerHTML
	}
}

// Counts the number of columns in the table taking into account the colspan attribute
// For example, if the table has a row with two td's, each with a colspan of 2, the function
// will return 4.
function it_getRealColumnCount(row) {
	if (row==null||row.childNodes==null) return 0;
	var tdlist=row.childNodes;
	var count=0;
	for (var k=0;k<tdlist.length;k++) {
		var span=tdlist.item(k).getAttribute("colspan");
		count+=parseInt(span);
	}
	return count;
}

// Gets the real column index of the specified column number
function it_getRealColumnIndex(table,colNr) {
	if (colNr<1||colNr>this.maxCols) return -1;
	var tdlist=table.firstRow.childNodes;
	var count=0;
	for (var k=0;k<tdlist.length;k++) {
		var td=tdlist.item(k);
		var id=td.getAttribute("id");
		var span=td.getAttribute("colspan");
		count+=parseInt(span);
		if (id==(table.name+".col"+colNr)) return count;
	}
	return count;
}

// Shows a column in the table
function it_showColumn(colNr) {
	if (colNr < 1 || colNr > this.maxCols) return;
	var realColIndex = it_getRealColumnIndex(this,colNr);
	
	if (realColIndex!=-1) {
		var rowList=this.rows; // list of tr's
		for (var k=0;k<rowList.length; k++) {
			var tdlist=rowList.item(k).childNodes; // list of td's for this row (=columns)
			var ix=0;
			for (var l=0;l<tdlist.length; l++) {
				var td=tdlist.item(l);
				var span=td.getAttribute("colspan");
				ix+=parseInt(span); // the real col index of the td
				if (ix==realColIndex) {
					td.style['visibility']='visible';
					break;
				}
			}
		}
	}
}

// Hides a column in the table. The function hides the entire column including header and footer.
function it_hideColumn(colNr) {
	if (colNr < 1 || colNr > this.maxCols) return;
	var realColIndex = it_getRealColumnIndex(this,colNr);
	
	if (realColIndex!=-1) {
		var rowList=this.rows; // list of tr's
		for (var k=0;k<rowList.length; k++) {
			var tdlist=rowList.item(k).childNodes; // list of td's for this row (=columns)
			var ix=0;
			for (var l=0;l<tdlist.length; l++) {
				var td=tdlist.item(l);
				var span=td.getAttribute("colspan");
				ix+=parseInt(span); // the real col index of the td
				if (ix==realColIndex) {
					td.style['visibility']='hidden';
					break;
				}
			}
		}
	}
}

// Returns true if the first row is empty
function it_isFirstRowEmpty() {
	var v = null;

	// the string trim function is defined in the file session.js
	var columns = this.firstRow.childNodes;
	for (var col = 1; col <= this.maxCols; col++) {
		var colIndex = this.getColumnIndex(col);
		if (colIndex != -1) {
			var v = this.getEntry(this.firstRowIndex, colIndex); 
			if (v != null && v.trim() != "") {
				debugWindow.writeln("first row is not empty");
				return false;
			}
		}
	}
	debugWindow.writeln("first row is empty");
	
	return true;
}

// move all items one up starting at the itemStartNr index and delete the last row. The entries in the row with the 'itemStartNr'
// index will overwritten with those in the row with the index 'itemStartNr + 1', etc.
function it_moveItemsOneUp(itemStartNr) {
	var lastRowIndex = this.getRowIndex(this.lastRow);
	for (var i = itemStartNr; i < lastRowIndex - 1; i++) {
		var row = this.getRow(i);
		var nextrow = this.getRow(i + 1);

		for (var col = 1; col <= this.maxCols; col++) {
			var colIndex = this.getColumnIndex(col);
			if (colIndex != -1)
				this.setEntry(i, colIndex, this.getEntry(i + 1, colIndex));
		}
	}
	var lastItemRow = this.getRow(lastRowIndex - 1);
	this.tbody.removeChild(lastItemRow);
}

// Stores the values of the row with 'itemNr' in the session cookie
function it_addToSessionState(itemNr) {
	// if the session manager is not initialized return without doing anything
	if (calcSession == null) return;

	// update the number of items
	var nritems = this.getNrOfItems();
	calcSession.updateVariable(this.name + ".nritems", nritems);
	debugWindow.writeln("updating the number of items to: " + nritems);
	
	// update the column values
	for (var col = 1; col <= this.maxCols; col++) {
		var colIndex = this.getColumnIndex(col);
		if (colIndex != -1) {
			var v = this.getEntry(itemNr, colIndex); 
			debugWindow.writeln("updating: " + this.name + "[" + itemNr + "," + colIndex + "]=" + v);
			calcSession.updateVariable(this.name + "[" + itemNr + "," + colIndex + "]", v);
		}
	}
}

// Removes the values of the row with 'itemNr' from the session cookie
function it_removeFromSessionState(itemNr) {
	// if the session manager is not initialized return without doing anything
	if (calcSession == null) return;

	// update the number of items
	var nritems = this.getNrOfItems();
	if (nritems == 0)
		calcSession.removeVariable(this.name + ".nritems");
	else
		calcSession.updateVariable(this.name + ".nritems", nritems);
		
	// remove the column values
	for (var col = 1; col <= this.maxCols; col++) {
		var colIndex = this.getColumnIndex(col);
		if (colIndex != -1) {
			debugWindow.writeln("removing: " + this.name + "[" + itemNr + "," + colIndex + "]");
			calcSession.removeVariable(this.name + "[" + itemNr + "," + colIndex + "]");
		}
	}
}

// restore a table using the information stored in the session cookie
//
// Note that it is possible to map a table in HTML to a different table name stored in the session
// This is useful when a table has been split into two pages in HTML when navigating from one
// page to another. When the session table differs from the actual table name, a column map array
// should be specified. The array has the following layout:
//		colMap[0] = "3"; // col1 maps to col3 of the session table
//		colMap[1] = "4"; // col2 maps to col3 of the session table
//		...
function it_restoreFromSessionState(tableName, sessionTableName, colMap) {
	// if the session manager is not initialized return without doing anything
	if (calcSession == null) return;

	var table = new itemTable(tableName);
	if (sessionTableName == null) {
		sessionTableName = table.name;
		debugWindow.writeln("it_restoreFromSessionState: restoring table " + tableName);
	}
	else
		debugWindow.writeln("it_restoreFromSessionState: restoring HTML table " + tableName + " from the session table " + sessionTableName);
		
	if (colMap == null)	colMap = new Array();
	
	var nritems = calcSession.getVariable(sessionTableName + ".nritems");
	if (nritems != "") {
		nritems = parseInt(nritems);
		debugWindow.writeln("it_restoreFromSessionState: nritems in " + sessionTableName + " is " + nritems);
		
		for (var i = table.firstRowIndex; i < (nritems + table.firstRowIndex); i++) {
			var newRowIndex = table.addRow();
			
			for (var colNr = 1; colNr <= table.maxCols; colNr++) {
				var colIndex = table.getColumnIndex(colNr);
				if (colIndex != -1) {
					var sessionColNr = colNr
					// find the session column index
					if (colMap[colNr - 1] != null) {
						sessionColNr = colMap[colNr - 1];
						debugWindow.writeln("it_restoreFromSessionState: HTML col" + colNr + " maps to session col" + sessionColNr);
					}
					
					var colVal = calcSession.getVariable(sessionTableName + "[" + i + "," + sessionColNr + "]");
					debugWindow.writeln("it_restoreFromSessionState: " + sessionTableName + "[" + newRowIndex + "," + colIndex + "]=" + colVal);
					table.setEntry(newRowIndex, colIndex, colVal, false, false);
				}
			}
		}	
	}
}

// Adds the item to the table without updating the session state
// See also it_AddItem
function it_addItemNoUpdate(tableName, col1val, col2val, col3val, col4val, col5val, col6val, col7val, col8val, col9val, col10val, col11val, col12val) {
	var table = new itemTable(tableName);
	var rowIndex = table.addRow();
	
	// write the column values into the new row
	table.setEntry(rowIndex, table.col1, col1val, false, false);
	table.setEntry(rowIndex, table.col2, col2val, false, false);
	table.setEntry(rowIndex, table.col3, col3val, false, false);
	table.setEntry(rowIndex, table.col4, col4val, false, false);
	table.setEntry(rowIndex, table.col5, col5val, false, false);
	table.setEntry(rowIndex, table.col6, col6val, false, false);
	table.setEntry(rowIndex, table.col7, col7val, false, false);
	table.setEntry(rowIndex, table.col8, col8val, false, false);
	table.setEntry(rowIndex, table.col9, col9val, false, false);
	table.setEntry(rowIndex, table.col10, col10val, false, false);
	table.setEntry(rowIndex, table.col11, col11val, false, false);
	table.setEntry(rowIndex, table.col12, col12val, false, false);
	
	return rowIndex;
}

// Use this method to add the item info to the specified table
// You can only remove an item using the remove button on the item itself
// The table automatically maintains the session state. Restore the table from the session state using
// the method it_restoreFromSessionState.
function it_AddItem(tableName, col1val, col2val, col3val, col4val, col5val, col6val, col7val, col8val, col9val, col10val, col11val, col12val) {
	var rowIndex = it_addItemNoUpdate(tableName, col1val, col2val, col3val, col4val, col5val, col6val, col7val, col8val,
		col9val, col10val, col11val, col12val);
	
	// Update the session state
	var table = new itemTable(tableName);
	table.addToSessionState(rowIndex);
	
	// Return the item number
	return table.getNrOfItems() - 1;
}
