Grid Navigation Menu
Grid Example 1: Travel Reimbursement Form
ARIA Enabled Travel Reimbursement Form Example
| Date |
Departed From |
Arrived At |
Automobile Miles |
Mileage Reimbursement |
Other Transportation Cost |
Lodging |
Meals |
Miscellaneous |
Daily Total |
| Location |
Time |
Location |
Time |
Air, Rails, etc.. |
Car Rental |
Taxi, Parking, Tolls, etc.. |
| Total Travel Expenses |
- |
- |
- |
- |
- |
- |
- |
- |
| Messages: | |
Keyboard Shortcuts
Based on the keyboard shortcuts defined in the AOL DHTML Style guide for grid
- Tab (standard mode): Move focus between grid, links and form controls.
- Tab (actionable mode): Move focus.
- ENTER or F2 (standard mode): Edit contents of gridcell.
- ENTER or F2 (Actionable mode): Update the contents of the gridcell.
- ESC (Actionable mode): Move to standard mode, do not update the contents of the gridcell.
- Up Arrow: Move focus to gridcell in previous row
- Down Arrow: Move focus to gridcell in next row
- Left Arrow: Move focus one gridcell to the left.
- Right Arrow: Move focus one gridcell to the right.
- Home: Move focus to first gridcell in the row.
- End: Move focus to the last gridcell to the row.
- Page Up: Move focus to the first gridcell in the columm.
- Page Down: Move focus to the last gridcell in the column.
ARIA Roles and Properties used
role="application"
role="grid"
role="gridcell"
role="alert"
- States and properties:
aria-labeledby
aria-describedby
aria-live
Other ARIA Features
- Date is checked for format and reformatted to standard date format, date errors are reported using
role="alert"
- Cells that only take numbers are filtered and an error is reported when non-numbers are entered using
role="alert".
- The total cost cell uses the
aria-live="polite" property.
HTML Source Code
Show HTML Source Code: grid1.inc
<div role="application">
<table id="expenses" role="grid" summary="ARIA Enabled Travel Reimbursement Form Example">
<caption>ARIA Enabled Travel Reimbursement Form Example</caption>
<thead>
<tr>
<th id="date" rowspan="2">Date</th>
<th id="from" colspan="2">Departed From</th>
<th id="to" colspan="2">Arrived At</th>
<th id="miles" rowspan="2">Automobile<br/> Miles</th>
<th id="mileage" rowspan="2">Mileage <br/>Reimbursement</th>
<th id="trans" colspan="3">Other Transportation Cost</th>
<th id="lodging" rowspan="2">Lodging</th>
<th id="meals" rowspan="2">Meals</th>
<th id="misc" rowspan="2">Miscellaneous</th>
<th id="total" rowspan="2">Daily<br/>Total</th>
</tr>
<tr>
<th id="location1">Location</th>
<th id="time1">Time</th>
<th id="location2">Location</th>
<th id="time2">Time</th>
<th id="trans-other">Air, Rails, etc..</th>
<th id="trans-rental">Car Rental</th>
<th id="trans-misc">Taxi, Parking, Tolls, etc..</th>
</tr>
</thead>
<tbody id="data">
</tbody>
<tr role="row">
<td id="calc-total-expenses-lbl" class="calc" role="gridcell" colspan="6">Total Travel Expenses</td>
<td id="calc-total-mileage" class="calc" role="gridcell" aria-label="Total Mileage">-</td>
<td id="calc-total-trans-other" class="calc" role="gridcell" aria-label="Total air or rail transportation">-</td>
<td id="calc-total-trans-rental" class="calc" role="gridcell" aria-label="Total car rental">-</td>
<td id="calc-total-trans-misc" class="calc" role="gridcell" aria-label="Total taxi, parking and tolls">-</td>
<td id="calc-total-lodging" class="calc" role="gridcell" aria-label="Total lodging">-</td>
<td id="calc-total-meals" class="calc" role="gridcell" aria-label="Total meals">-</td>
<td id="calc-total-misc" class="calc" role="gridcell" aria-label="Total miscellaneous">-</td>
<td id="calc-total-reimbursement" class="reimbursement" role="gridcell" aria-label="Total reimbursement" aria-live="polite">-</td>
</tr>
<tr>
<th id="msg">Messages: </th><td headers="msg" colspan="13" id="alert" role="alert"> </td>
</tr>
</table>
<div id="debug"></div>
<div id="row_numbers" class="offscreen"></div>
</div>
Javascript Source Code
Show Javascript Source Code: grid1.js
<script type="text/javascript">
$(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;
}
</script>
CSS Source Code
Show CSS Source Code: grid1.css
<style type="text/css">
table#expenses {
margin: 0;
padding: 0;
border: 1px solid black;
border-spacing: 0;
}
caption {
margin: 0;
padding: 5px;
border: 2px solid black;
font-weight: bold;
font-size: 1.25em;
color: #027;
background-color: #eee;
}
table#expenses th {
margin: 0;
padding: 5px;
border: 1px solid black;
background-color: #eee;
color: #027;
}
table#expenses td {
margin: 0;
padding: 2px;
border: 1px solid black;
border: 1px solid black;
}
.expense, .miles {
text-align: right;
}
.calc, .reimbursement {
background-color: #ffe;
font-weight: bold;
text-align: right;
color: #027;
}
#msg, #alert {
border-top: 3px solid black !important;
}
.offscreen {
position: absolute;
top: -30em;
left: -300em;
}
.edt {
margin: 1px;
padding: 0;
width: 100%;
border: none;
background-color: white;
display: none;
}
.data {
margin: 0;
padding: 0;
width: 100%;
}
.focus {
background-color: #79e;
}
</style>
W3C Validation of HTML5