$(document).ready(function () {

	// Create an instance of the travel Calculator. Parameters are the table to use,
	// the per-mile reimbursement, the maximum number of data rows, and the initial
	// number of rows to create.
	var app = new travelCalc('table#expenses', 0.15, 10, 5);

}); // end ready function

function keyCodes () {
	// Define values for keycodes
	this.backspace  = 8;
	this.tab        = 9;
	this.enter      = 13;
	this.esc        = 27;

	this.space      = 32;
	this.pageup     = 33;
	this.pagedown   = 34;
	this.end        = 35;
	this.home       = 36;

	this.left       = 37;
	this.up         = 38;
	this.right      = 39;
	this.down       = 40; 

	this.insert     = 45; 
	this.del        = 46; 

	this.f2         = 113; 
}

//
// travelCalc() is a class to implement a simple travel reimbusement calculator
//
// @param (table string) table is the id of the table to operate on
//
// @param (maxRows integer) maxRows is the maximum number of rows an instance of
// travelCalc may have
//
// @param (initNum integer) initNum is the number of rows to add during object instantiation
//
// @return N/A
//
function travelCalc(table, reimbursement, maxRows, initNum) {

	var thisObj = this; // Store the this pointer

	// Define class properties
	this.reimbursement = reimbursement;
	this.maxRows = maxRows; // maximum number of rows allowed this instance
	this.numRows = 0;	// The current number of rows belonging to instance
	this.curRow = 0;	// The currently selected row
	this.curCol = 0;	// The currently selected column
	this.$tbody = $(table).find('tbody#data'); // Store the tbody object
	this.editMode = false;  // True if in edit mode

	this.keys = new keyCodes();

	// Bind handlers
	this.bindHandlers();

	// Add rows
	for (var ndx = 0; ndx < initNum; ndx += 1) {
		this.addRow();
	}

	// Store the collection of editable table cells in an object property
	// this property must be updated when adding new rows.
	this.$editableCells = this.$tbody.find('td.editable');

	// Make first row navigable
	$('#row1-date').attr('tabindex', '0');

} // end travelCalc constructor

//
// addRow() is a member function to add a row to the data grid. Function builds a string containing
// the elements to add, and appends the string to the table. addRow() will not add a new row if
// maxRows has been reached.
//
// @return N/A
//
travelCalc.prototype.addRow = function() {
	var thisObj = this;	// store a pointer to this object

	// Do not add a new row if maxRows has been reached
	if (this.numRows < this.maxRows) { 
		var label;

		// Increment the number of rows
		this.numRows += 1;

		var row = '<tr role=\"row\" id=\"row' + (this.numRows) + '\">';

		// date cell
		label = 'aria-labelledby=\"date r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-date\" role=\"gridcell\" ' +
			'class=\"date editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Departure location cell
		label = 'aria-labelledby=\"location1 r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-location1\" role=\"gridcell\" ' +
			'class=\"location editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<div role=\"combobox\">' +
			'<input class=\"edt\" type=\"text\" size=\"12\" ' + label + '/>' +
			'</div></td>';

		// Departure time cell
		label = 'aria-labelledby=\"time1 r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-time1\" role=\"gridcell\" ' +
			'class=\"time editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Arrived at cell
		label = 'aria-labelledby=\"location2 r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-location2\" role=\"gridcell\" ' +
			'class=\"location editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<div role=\"combobox\">' +
			'<input class=\"edt\" type=\"text\" size=\"12\" ' + label + '/>' +
			'</div></td>';

		// Arrival time cell
		label = 'aria-labelledby=\"time2 r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-time2\" role=\"gridcell\" ' +
			'class=\"time editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Automobile miles cell
		label = 'aria-labelledby=\"miles r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-miles\" role=\"gridcell\" ' +
			'class=\"miles editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Mileage reimbursement cell
		label = 'aria-labelledby=\"mileage r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-mileage\" role=\"gridcell\" ' +
			'class=\"calc\" ' + label + 'tabindex=\"-1\">-</td>';

		// Air et al. transportation cost cell
		label = 'aria-labelledby=\"trans-other r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-trans-other\" role=\"gridcell\" ' +
			'class=\"expense editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Car rental transportation cost cell
		label = 'aria-labelledby=\"trans-rental r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-trans-rental\" role=\"gridcell\" ' +
			'class=\"expense editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Misc. transportation cost cell
		label = 'aria-labelledby=\"trans-misc r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-trans-misc\" role=\"gridcell\" ' +
			'class=\"expense editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Lodging cost cell
		label = 'aria-labelledby=\"lodging r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-lodging\" role=\"gridcell\" ' +
			'class=\"expense editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Meals cost cell
		label = 'aria-labelledby=\"meals r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-meals\" role=\"gridcell\" ' +
			'class=\"expense editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Misc. cost cell
		label = 'aria-labelledby=\"misc r' + this.numRows + '\"';
		row += '<td id=\"row' + this.numRows + '-misc\" role=\"gridcell\" ' +
			'class=\"expense editable\" ' + label + 'tabindex=\"-1\">' +
			'<span class=\"data\"></span>' +
			'<input class=\"edt\" type=\"text\" size=\"6\" ' + label + '/>' +
			'</td>';

		// Row total cell and closing tr
		row += '<td id = \"row' + this.numRows + '-total\" class=\"calc\">-</td></tr>'

		// Append the new row to the grid
		$('tbody#data').append(row);

		// Add an entry for this row in the list of rows
		$('#row_numbers').append('<div id=\"r' + this.numRows + '\">Row ' + this.numRows + '</div>');
	}

} //end addRow()

//
// bindHandlers() is a member function to bind event handlers to the tbody of the data table. This uses event
// delegation to manage events for the children cells. Event delegation is much faster than binding to each cell,
// and it also allows new table rows to be handled.
//
// @return N/A
//
travelCalc.prototype.bindHandlers = function() {
	var thisObj = this;	// store a pointer to this object
	var $tbody = this.$tbody; // store a pointer to the table body property (saves a dereference)

	/************ Bind the handlers for the editable grid cells  in the data table **********/
	//

	// bind a click handler
	$tbody.delegate('td.editable', 'click', function (e) {
			return thisObj.handleCellClick(this, e);
	}); // end click handler

	// bind a double click handler
	$tbody.delegate('td.editable', 'dblclick', function (e) {
			return thisObj.handleCellDblclick(this, e);
	}); // end double click handler

	// bind a keydown handler
	$tbody.delegate('td.editable', 'keydown', function (e) {
			return thisObj.handleCellKeyDown(this, e);
	}); // end keydown handler

	// bind a keypress handler - consume events for Opera
	$tbody.delegate('td.editable', 'keypress', function (e) {
			return thisObj.handleCellKeyPress(this, e);
	}); // end keyup handler

	// bind a focus handler
	$tbody.delegate('td.editable', 'focus', function (e) {
			return thisObj.handleCellFocus(this, e);
	}); // end focus handler

	// bind a blur handler
	$tbody.delegate('td.editable', 'blur', function (e) {
			return thisObj.handleCellBlur(this, e);
	}); // end blur handler


	/************ Bind the handlers for the edit boxes in the editable cells **********/
	
	// bind a keydown handler
	$tbody.delegate('input.edt', 'keydown', function (e) {
			return thisObj.handleEditKeyDown(this, e);
	}); // end edit box keydown handler

	// bind a keypress handler - consume events for Opera
	$tbody.delegate('input.edt', 'keypress', function (e) {
			return thisObj.handleEditKeyPress(this, e);
	}); // end edit box keyup handler

	// bind a focus handler
	$tbody.delegate('input.edt', 'focus', function (e) {
			return thisObj.handleEditFocus(this, e);
	}); // end edit box focus handler

	// bind a blur handler
	$tbody.delegate('input.edt', 'blur', function (e) {
			return thisObj.handleEditBlur(this, e);
	}); // end edit box blurhandler

} // end bindHandlers()


//
// enterEditMode() is a member function to enter the edit mode of an editable cell
//
// @param (id object) id is the jQuery object of the cell to operate on
//
// @return N/A
//
travelCalc.prototype.enterEditMode = function($id) {

	var $edt = $id.find('.edt');
	var $data = $id.find('.data');
	
	// Clear any old alert messages
	$('td#alert').text('');

	// Set the editMode flag to true -- make edit mode modal
	// This is faster than unbinding edit cell event handlers
	this.editMode = true;

	// Copy the data from the cell's data span into the edit box
	$edt.val($data.text());

	// hide the data and show the edit box
	$data.hide();
	$edt.show();

	// give the edit box focus
	$edt.focus();

} // end enterEditMode()

//
// leaveEditMode() is a member function to exit the edit mode of an editable cell
//
// @param (id object) id is the jQuery object of the cell to operate on
//
// @return N/A
//
travelCalc.prototype.leaveEditMode = function(id) {

	var $cell = $(id);
	var $edt = $cell.find('.edt');
	var $data = $cell.find('.data');
	var thisObj = this;
	var validEntry = true;


	// Make sure we are actually in edit mode
	if (this.editMode == false) {
		return;
	}

	// Validate the input
	if ($cell.hasClass('date')) {

		if (this.validateDate($edt) == true) {

			// Store the changes
			$data.text($edt.val());
		}
	}
	else if ($cell.hasClass('time')) {

		if (this.validateTime($edt) == true) {

			// Store the changes
			$data.text($edt.val());
		}
	}
	else if ($cell.hasClass('miles')) {

		if (this.validateMiles($edt) == true) {

			var $reimbursementCell = $cell.next(); 

			// Store the changes
			$data.text($edt.val());

			// Calculate the mileage reimbursement
			$reimbursementCell.text( this.calcMileAmount($edt.val()) );

			// recalculate the daily total
			this.calcDaily($cell.attr('id').split('-')[0])

			// recalculate the mileage reimbursement total
			this.calcMileageTotal();

			// recalculate the total reimbursement
			this.calcTotalReimbursement();
		}
	}
	else if ($cell.hasClass('expense')) {

		if (this.validateExpense($edt) == true) {

			// Store the changes
			$data.text($edt.val());

			// recalculate the daily total
			this.calcDaily($cell.attr('id').split('-')[0])

			// recalculate the column total
			this.calcExpenseTotal($cell.attr('id'))

			// recalculate the total reimbursement
			this.calcTotalReimbursement();
		}
	}
	else {
		// Don't validate; just store the changes
		$data.text($edt.val());
	}

	// Set the editMode flag to false
	this.editMode = false;

	// Hide the edit box and show the data
	$edt.hide();
	$data.show();

	// Give the parent focus
	$cell.focus();


} // end leaveEditMode()

//
// validateDate() is a member function to validate data entered in the date column
//
// @param (id object) id is the jQuery object of the edit box the user modified
//
// @return true if valid entry; false if invalid
//
travelCalc.prototype.validateDate = function($edt) {

	var curDate = new Date();

	if ($edt.val() != "") {
		// try parsing as date using JavaScript Date constructor
		var dateValue = new Date($edt.val().replace(/-/g, '/'));


		if (isFinite(dateValue)) {

			// If user entered 2-digit year, the date will be approximately 100 years off. Check for this and correct
			if (curDate.getFullYear() - dateValue.getFullYear() >= 99) {
				dateValue.setFullYear(dateValue.getFullYear() + 100);
			}

			// Check if date entered is in the future
			if (dateValue > curDate) {
				$('td#alert').text('Date must be before the current date');
				return false;
			}

			// Set date to 60 days in the past
			curDate.setDate(curDate.getDate() - 60);

			// Check if date entered is older than 60 days ago
			if (dateValue < curDate) {
				$('td#alert').text('Date must be within the last 60 days');
				return false;
			}

			// format as mm/dd/yyyy
			$edt.val( (dateValue.getMonth() + 1) + '/' + dateValue.getDate() + '/' + dateValue.getFullYear() );
			return true;
		}
		else {
			$('td#alert').text('Date needs to be in date format, such as 1/31/2001.');
			return false;
		}
	}

	return false;

} // end validateDate()

//
// validateTime() is a member function to validate data entered in the time columns
//
// @param (id object) id is the jQuery object of the edit box the user modified
//
// @return true if valid entry; false if invalid
//
travelCalc.prototype.validateTime = function($edt) {

	if ($edt.val() != "") {
		var str = $.trim($edt.val());

		if (/^(0?[1-9]|1[0-2]):[0-5]\\d ?([ap]m?)?$/.test(str) == false) {

			$('td#alert').text('Time must be in valid time format, such as h:mm [am|pm]');
			return false;
		}
	}

	return true;

} // end validateTime()

//
// validateMiles() is a member function to validate data entered in the Automobile Miles column
//
// @param (id object) id is the jQuery object of the edit box the user modified
//
// @return true if valid entry; false if invalid
//
travelCalc.prototype.validateMiles = function($edt) {

	if ($edt.val() != "") {
		var str = $.trim($edt.val());

		if (/^\\d*$/.test(str) == false) {

			$('td#alert').text('Automobile Miles must be a number');
			return "Invalid";
		}
	}

	return true;

} // end validateMiles()

//
// validateExpense() is a member function to validate data entered in the expense columns
//
// @param (id object) id is the jQuery object of the edit box the user modified
//
// @return true if valid entry; false if invalid
//
travelCalc.prototype.validateExpense = function($edt) {

	if ($edt.val() != "") {
		var str = $.trim($edt.val());

		if (/^\\$?[1-9]\\d{0,2}(,\\d{3})*(\\.\\d{0,2})?$/.test(str) ) {

			if (str.charAt(0) != '$') {
				str = '$' + str;
			}
			
			if (/\\.\\d$/.test(str)) {
				str += "0";
			}
			else if (/\\.$/.test(str) ) {
				str += "00";
			}
			else if (!/\\.\\d{2}$/.test(str) ) {
				str += ".00";
			}
			$edt.val(str);
			return true;
		}
		else {
			$('td#alert').text('Expense must be in valid US money format, such as $1,000.00');
			return "Invalid";
		}
	}

	return true;

} // end validateExpense()

//
// calcMileAmount() is a member function to calculate the automobile mileage reimbursement for a trip
//
// @param (miles int) miles is the total miles entered in the Automobile Miles column
//
// @return The calculated reimbursement, in U.S. currency format
//
travelCalc.prototype.calcMileAmount = function(miles) {

	var amount = '$' + (miles * this.reimbursement); 
	var tmp = amount.split('.');

	// If cents is defined, round to the nearest cent
	if (tmp[1] != undefined) {
		var rounded = Math.round(tmp[1].substr(0,2) + '.' + tmp[1].substr(2));
		amount = tmp[0] + '.' + rounded;
	}

	// Add cents
	if (/\\.\\d$/.test(amount)) {
		amount += "0";
	}
	else if (/\\.$/.test(amount) ) {
		amount += "00";
	}
	else if (!/\\.\\d{2}$/.test(amount) ) {
		amount += ".00";
	}

	return amount;
}

//
// calcDaily() is a member function to calculate the daily total cost of a trip
//
// @param (row string) row is the id of the current row the user modified
//
// @return N/A
//
travelCalc.prototype.calcDaily = function(row) {
	var total = $('#' + row + '-mileage').text().substr(1); // Strip the '$'

	if (total.length) {
		// remove any ',' from the value and convert to a float
		total = parseFloat(total.replace(/,/g, ''));
	}
	else {
		total = 0;
	}

	// Add the total for each expense
	$('#' + row).find('td.expense').each(function() {
		var expense = $(this).find('.data').text().substr(1);

		if (expense.length) {
			// remove any ',' from the value
			expense = expense.replace(/,/g, '');
				
			total += parseFloat(expense);
		}
	});

	// Add cents
	if (/\\.\\d$/.test(total)) {
		total += "0";
	}
	else if (/\\.$/.test(total) ) {
		total += "00";
	}
	else if (!/\\.\\d{2}$/.test(total) ) {
		total += ".00";
	}
	
	$('#' + row + '-total').text('$' + total);
}

//
// calcMileageTotal() is a member function to calculate the total mileage Reimbursement amount
//
// @return N/A
//
travelCalc.prototype.calcMileageTotal = function() {

	var total = 0;

	// Iterate through the column, adding the expense to the total.
	for (var row = 1; row <= this.numRows ; row++) {
		var expense = $('#row' + row + '-mileage').text().substr(1); // strip the'$' from the expense

		if (expense.length) {
			// remove any ',' from the value
			expense = expense.replace(/,/g, '');
				
			total += parseFloat(expense);
		}
	}

	// Add cents
	if (/\\.\\d$/.test(total)) {
		total += "0";
	}
	else if (/\\.$/.test(total) ) {
		total += "00";
	}
	else if (!/\\.\\d{2}$/.test(total) ) {
		total += ".00";
	}

	$('#calc-total-mileage').text('$' + total);
}

//
// calcExpenseTotal() is a member function to calculate the total expense for a column
//
// @param (id string) id is the id of the column to update
//
// @return N/A
//
travelCalc.prototype.calcExpenseTotal = function(id) {

	var total = 0;
	var col = id.substr(id.indexOf('-'));

	// Iterate through the column, adding the expense to the total.
	for (var row = 1; row <= this.numRows ; row++) {
		var expense = $('#row' + row + col).find('span').text().substr(1); // strip the'$' from the expense

		if (expense.length) {
			// remove any ',' from the value
			expense = expense.replace(/,/g, '');
				
			total += parseFloat(expense);
		}
	}

	// Add cents
	if (/\\.\\d$/.test(total)) {
		total += "0";
	}
	else if (/\\.$/.test(total) ) {
		total += "00";
	}
	else if (!/\\.\\d{2}$/.test(total) ) {
		total += ".00";
	}

	$('#calc-total' + col).text('$' + total);
}

//
// calcTotalReimbursement() is a member function to calculate the total expense reimbursement
//
// @return N/A
//
travelCalc.prototype.calcTotalReimbursement = function() {

	var total = 0;

	// Iterate through the column, adding the expense to the total.
	$('td#calc-total-expenses-lbl').siblings().not('td#calc-total-reimbursement').each(function() {
		var expense = $(this).text().substr(1); // strip the'$' from the expense

		if (expense.length) {
			// remove any ',' from the value
			expense = expense.replace(/,/g, '');
				
			total += parseFloat(expense);
		}
	});

	// Add cents
	if (/\\.\\d$/.test(total)) {
		total += "0";
	}
	else if (/\\.$/.test(total) ) {
		total += "00";
	}
	else if (!/\\.\\d{2}$/.test(total) ) {
		total += ".00";
	}

	$('#calc-total-reimbursement').text('$' + total);
}

//
// handleCellClick() is a callback for the click event. It is bound to the editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
//
// @return N/A
//
travelCalc.prototype.handleCellClick = function(id, e) {

	$(id).focus();

	e.stopPropagation;
	return false;

} // end handleCellClick()

//
// handleCellDblclick() is a callback for the dblclick event. It is bound to the editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
// @return N/A
//
travelCalc.prototype.handleCellDblclick = function(id, e) {

	// do nothing if we are in editMode
	if (this.editMode == false) {

		// enter the edit mode for the cell
		this.enterEditMode($(id));
	}

	e.stopPropagation;
	return false;

} //end handleCellDblclick()

//
// handleCellKeyDown() is a callback for the keydown event. It is bound to the editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
// @return False if specified key is pressed, true if other keypress
//
travelCalc.prototype.handleCellKeyDown = function(id, e) {

	var $curCell = $(id); // Store the current cell object to prevent repeated DOM traversals

	// do nothing if the shift, alt, or ctrl key is pressed or we are in editMode
	if (e.ctrlKey == true || e.altKey == true || e.shiftKey == true || this.editMode == true) {
		return true;
	}

	switch (e.keyCode) {
		case this.keys.enter:
		case this.keys.f2: {
			// enter the edit mode for the cell
			this.enterEditMode($curCell);

			e.stopPropagation;
			return false;
			break;
		}
		case this.keys.left: {
			var $newCell = $curCell.prev();

			// If there is another editable cell to the right, select it
			if ($newCell.length) {

				if ($newCell.attr('id').search('mileage') > 0) {
					// skip this column
					$newCell = $newCell.prev();
				}

				if ($newCell.hasClass('editable')) {

					// Make the new cell navigable and give it focus
					// Use jQuery chaining for optimization
					$newCell.attr('tabindex', '0').focus();
				}
			}

			e.stopPropagation;
			return false;
			break;
		}
		case this.keys.right: {
			var $newCell = $curCell.next();

			// If there is another editable cell to the right, select it
			if ($newCell.length) {

				if ($newCell.attr('id').search('mileage') > 0) {
					// skip this column
					$newCell = $newCell.next();
				}

				if ($newCell.hasClass('editable')) {

					// Make the new cell navigable and give it focus
					// Use jQuery chaining for optimization
					$newCell.attr('tabindex', '0').focus();
				}
			}

			e.stopPropagation;
			return false;
			break;
		}
		case this.keys.up: {
			// Cell id's are of the form "row#-colName". We need to isolate the row number and
			// column name
			var curRow = $curCell.attr('id');
			var len = curRow.indexOf('-');
			var rowNum = curRow.substr(3, len - 3) - 1;

			if (rowNum > 0)
			{
				// build the id string of the new cell
				var newCell = '#row' + rowNum + '-' + curRow.substr(len + 1);

				// Make the new cell navigable and give it focus
				// Use jQuery chaining for optimization
				$(newCell).attr('tabindex', '0').focus();
			}

			e.stopPropagation;
			return false;
		}
		case this.keys.down: {
			// Cell id's are of the form "row#-colName". We need to isolate the row number and
			// column name
			var curRow = $curCell.attr('id');
			var len = curRow.indexOf('-');
			var rowNum = parseInt(curRow.substr(3, len - 3)) + 1;

			if (rowNum <= this.numRows)
			{
				// build the id string of the new cell
				var newCell = '#row' + rowNum + '-' + curRow.substr(len + 1);

				// Make the new cell navigable and give it focus
				// Use jQuery chaining for optimization
				$(newCell).attr('tabindex', '0').focus();
			}
			e.stopPropagation;
			return false;
		}
		case this.keys.pageup: {
			// Cell id's are of the form "row#-colName". We need to isolate the row number and
			// column name
			var curRow = $curCell.attr('id');
			var len = curRow.indexOf('-');
			var rowNum = parseInt(curRow.substr(3, len - 3)) - 1;

			if (rowNum > 0)
			{
				// build the id string of the new cell
				var newCell = '#row1-' + curRow.substr(len + 1);

				// Make the new cell navigable and give it focus
				// Use jQuery chaining for optimization
				$(newCell).attr('tabindex', '0').focus();
			}

			e.stopPropagation;
			return false;
		}
		case this.keys.pagedown: {
			// Cell id's are of the form "row#-colName". We need to isolate the row number and
			// column name
			var curRow = $curCell.attr('id');
			var len = curRow.indexOf('-');
			var rowNum = parseInt(curRow.substr(3, len - 3)) + 1;

			if (rowNum <= this.numRows)
			{
				// build the id string of the new cell
				var newCell = '#row' + this.numRows + '-' + curRow.substr(len + 1);

				// Make the new cell navigable and give it focus
				// Use jQuery chaining for optimization
				$(newCell).attr('tabindex', '0').focus();
			}
			e.stopPropagation;
			return false;
		}
		case this.keys.home: {
			var row = $curCell.attr('id').split('-')[0];

			// Make the new cell navigable and give it focus
			// Use jQuery chaining for optimization
			$('#' + row + '-date').attr('tabindex', '0').focus();

			e.stopPropagation;
			return false;
			break;
		}
		case this.keys.end: {
			var row = $curCell.attr('id').split('-')[0];

			// Make the new cell navigable and give it focus
			// Use jQuery chaining for optimization
			$('#' + row + '-misc').attr('tabindex', '0').focus();

			e.stopPropagation;
			return false;
			break;
		}

	}

	return true;

} // end handleCellKeyDown

//
// handleCellKeyPress() is a callback for the keypress event. It is bound to the editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
// @return False if specified key is released, true if other key released
//
travelCalc.prototype.handleCellKeyPress = function(id, e) {

	// do nothing if the shift, alt, or ctrl key is pressed or we are in editMode
	if (e.ctrlKey == true || e.altKey == true || e.shiftKey == true || this.editMode == true) {
		return true;
	}
	
	switch (e.keyCode) {
		case this.keys.enter:
		case this.keys.f2:
		case this.keys.left:
		case this.keys.right:
		case this.keys.up:
		case this.keys.down:
		case this.keys.pageup:
		case this.keys.pagedown:
		case this.keys.home:
		case this.keys.end: {

			e.stopPropagation;
			return false;
			break;
		}
	}

	return true;

} // end handleCellKeyPress

//
// handleCellFocus() is a callback for the focus event. It is bound to the editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
// @return N/A
//
travelCalc.prototype.handleCellFocus = function(id, e) {

	// Remove the highlighting from the table cells and remove them from the tab order.
	// Use jQuery chaining for optimization
	this.$editableCells.attr('tabindex', '-1').removeClass('focus');

	// Add the highlighting for the focused cell and make it navigable
	// Use jQuery Chaining for optimization
	$(id).addClass('focus').attr('tabindex', '0');

	return true;

} // end handleCellFocus()

//
// handleCellBlur() is a callback for the blur event. It is bound to the editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
// @return N/A
//
travelCalc.prototype.handleCellBlur = function(id, e) {

	var $cell = $(id);
	// do nothing if we are in editMode
	if (this.editMode == false) {
		$cell.removeClass('focus');
	}
	return true;

} // end handleCellBlur()

//
// handleEditKeyDown() is a callback for the keydown event. It is bound to the edit boxes in the
// editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
//
// @return N/A
//
travelCalc.prototype.handleEditKeyDown = function(id, e) {

	var $parentNode = $(id).parent();
	var $newNode;
	
	// do nothing if the ctrl or alt key is pressed
	if (e.ctrlKey == true || e.altKey == true) {
		return true;
	}

	// handle edit boxes nested in combobox divs
	if ($parentNode.attr('tagName') == 'DIV') 
	{
		$parentNode = $parentNode.parent();
	}

	switch (e.keyCode) {
		case this.keys.tab: {
			var haveNewCell = false;

			if (e.shiftKey) {
				// user pressed shift-tab
				$newNode = $parentNode.prev();
				if ($newNode.length) {
					if ($newNode.attr('id').search('mileage') > 0) {
						$newNode = $newNode.prev();
					}

					haveNewCell = true;
				}
			}
			else {
				$newNode = $parentNode.next();
				if ($newNode.length) {
					if ($newNode.attr('id').search('mileage') > 0) {
						$newNode = $newNode.next();
					}

					haveNewCell = true;
				}
			}
			// leave edit mode
			this.leaveEditMode($parentNode);

			// Select the next editable cell (if possible)
			if (haveNewCell == true && $newNode.is('.editable')) {
				$newNode.focus();
			}

			e.stopPropagation;
			return false;
			break;
		}
		case this.keys.enter:
		case this.keys.f2:
		case this.keys.esc: {
			// leave edit mode
			this.leaveEditMode($parentNode);
			e.stopPropagation;
			return false;
			break;
		}
	}

	return true;

} // end handleEditKeyDown()

//
// handleEditKeyPress() is a callback for the keypress event. It is bound to the edit boxes in the
// editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
// @return N/A
//
travelCalc.prototype.handleEditKeyPress = function(id, e) {

	// do nothing if the ctrl or alt key is pressed
	if (e.ctrlKey == true || e.altKey == true) {
		return true;
	}
	switch (e.keyCode) {
		case this.keys.tab:
		case this.keys.enter:
		case this.keys.f2:
		case this.keys.esc: {
			e.stopPropagation;
			return false;
			break;
		}
	}
	return true;
} // end handleEditKeyPress()

//
// handleEditFocus() is a callback for the focus event. It is bound to the edit boxes in the
// editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
// @return N/A
//
travelCalc.prototype.handleEditFocus = function(id, e) {
	
	var $parentNode = $(id).parent();
	
	// Get the parent node of edit boxes nested in combobox divs
	if ($parentNode.attr('tagName') == 'DIV') 
	{
		$parentNode = $parentNode.parent();
	}

	return true;
}

//
// handleEditBlur() is a callback for the blur event. It is bound to the edit boxes in the
// editable grid cells
//
// @param (id object) id is the element generating the event
//
// @param (e object) e is the event object associated with the event
//
// @return N/A
//
travelCalc.prototype.handleEditBlur = function(id, e) {

	var $parentNode = $(id).parent();
	
	// Get the parent node of edit boxes nested in combobox divs
	if ($parentNode.attr('tagName') == 'DIV') 
	{
		$parentNode = $parentNode.parent();
	}

	// leave edit mode
	this.leaveEditMode($parentNode);

	e.stopPropagation;
	return false;
}


