Test Start
| 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: | |||||||||||||
Test End
Rule References
| Rule | Pass IDs | Fail IDs |
|---|---|---|
| Rule 84 |
|
none |
| Rule 85 |
|
none |
| Rule 86 |
|
none |
| Rule 87 |
|
none |
| Rule 88 |
|
none |
| Rule 89 |
|
none |
| Rule 92 |
|
none |
| Rule 93 |
|
none |
| Rule 94 |
|
none |
| Rule 95 |
|
none |
| Rule 97 |
|
none |
| Rule 98 |
|
none |
Calculations
No calculations for test 122
Test Description
- This test implements a simple editable grid widget for a travel reimbursement form.
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.
Test Markup
- ARIA 1.0: [aria-disabled]
- ARIA 1.0: [aria-hidden]
- ARIA 1.0: [aria-label]
- ARIA 1.0: [aria-labelledby]
- ARIA 1.0: [aria-live]
- ARIA 1.0: [aria-pressed]
- ARIA 1.0: [role="alert"]
- ARIA 1.0: [role="application"]
- ARIA 1.0: [role="button"]
- ARIA 1.0: [role="grid"]
- ARIA 1.0: [role="gridcell"]
- ARIA 1.0: [role="row"]
User Agent Implementation
No user agent implementation information.
HTML Source Code
<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="dep">Location</th>
<th id="time1">Time</th>
<th id="dest">Location</th>
<th id="time2">Time</th>
<th id="trans-other">Air, Rails, etc..</th>
<th id="trans-rent">Car Rental</th>
<th id="trans-misc">Taxi, Parking, Tolls, etc..</th>
</tr>
</thead>
<tbody id="data">
<tr id="totals" role="row">
<th id="tot-expense-lbl" class="calc" role="gridcell" colspan="6">
<div id="addRow" role="button" class="addButton" tabindex="0" title="Add new row" aria-pressed="false" aria-disabled="false">+</div>
Total Travel Expenses
</th>
<td id="calc-tot-mileage" class="calc" role="gridcell" headers="tot-expense-lbl mileage" aria-label="Total Mileage">-</td>
<td id="calc-tot-trans-other" class="calc" role="gridcell" headers="tot-expense-lbl trans-other" aria-label="Total air or rail transportation">-</td>
<td id="calc-tot-trans-rent" class="calc" role="gridcell" headers="tot-expense-lbl trans-rent" aria-label="Total car rental">-</td>
<td id="calc-tot-trans-misc" class="calc" role="gridcell" headers="tot-expense-lbl trans-misc" aria-label="Total taxi, parking and tolls">-</td>
<td id="calc-tot-lodging" class="calc" role="gridcell" headers="tot-expense-lbl lodging" aria-label="Total lodging">-</td>
<td id="calc-tot-meals" class="calc" role="gridcell" headers="tot-expense-lbl meals" aria-label="Total meals">-</td>
<td id="calc-tot-misc" class="calc" role="gridcell" headers="tot-expense-lbl misc" aria-label="Total misc">-</td>
<td id="calc-tot-reimbursement" class="reimbursement" role="gridcell" headers="tot-expense-lbl total" aria-label="Total reimbursement" aria-live="polite">-</td>
</tr>
<tr>
<th id="msg">Messages: </th>
<td id="alert" colspan="13" headers="msg" role="alert"> </td>
</tr>
</tbody>
</table>
<div id="row_numbers" class="offscreen"></div>
</div>
CSS Code
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 th.date {
background-color: #fff;
font-weight: normal;
}
table#expenses td {
margin: 0;
padding: 2px;
background-color: #fff;
border: 1px solid black;
border: 1px solid black;
}
.expense, .miles {
text-align: right;
}
.calc, .reimbursement {
background-color: #ffe !important;
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%;
}
div.addButton {
float: left;
display: inline;
width: 1.5em;
text-align: center;
font-weight: bold;
color: #800;
background-color: #eee;
border-top: 1px solid #777;
border-left: 1px solid #777;
border-right: 2px solid #000;
border-bottom: 2px solid #000;
}
div.pressed {
background-color: #fff;
border-top: 2px solid #000;
border-left: 2px solid #000;
border-bottom: 1px solid #777;
border-right: 1px solid #777;
}
div.disabled {
background-color: #eee;
color: #444;
border-top: 1px solid #777;
border-left: 1px solid #777;
border-bottom: 2px solid #000;
border-right: 2px solid #000;
}
.focus {
background-color: #79e !important;
}
Javascript Source Code
$(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, 5, 1);
}); // 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.$addButton = $(table).find('.addButton'); // Store the add row button 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,th.editable');
// Make first row navigable
$('#r1-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="r' + (this.numRows) + '">';
// date cell
label = 'aria-labelledby="date row' + this.numRows + '"';
row += '<th id="r' + this.numRows + '-date" role="gridcell" ' +
'class="date editable" ' + label + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-date-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-date-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter travel date of trip ' + this.numRows + '" />' +
'</th>';
// Departure location cell
headers = 'headers="dep r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-dep" role="gridcell" ' +
'class="location editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-dep-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-dep-edt" class="edt" type="text" aria-hidden="true" size="12" title="Enter departure location for trip ' + this.numRows + '" />' +
'</td>';
// Departure time cell
headers = 'headers="time1 r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-time1" role="gridcell" ' +
'class="time editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-time1-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-time1-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter departure time for trip ' + this.numRows + '" />' +
'</td>';
// Arrived at cell
headers = 'headers="dest r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-dest" role="gridcell" ' +
'class="location editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-dest-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-dest-edt" class="edt" type="text" aria-hidden="true" size="12" title="Enter destination for trip ' + this.numRows + '" />' +
'</td>';
// Arrival time cell
headers = 'headers="time2 r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-time2" role="gridcell" ' +
'class="time editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-time2-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-time2-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter arrival time for trip ' + this.numRows + '" />' +
'</td>';
// Automobile miles cell
headers = 'headers="miles r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-miles" role="gridcell" ' +
'class="miles editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-miles-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-miles-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter miles driven for trip ' + this.numRows + '" />' +
'</td>';
// Mileage reimbursement cell
headers = 'headers="mileage r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-mileage" role="gridcell" ' +
'class="calc" ' + headers + 'tabindex="-1">-</td>';
// Air et al. transportation cost cell
headers = 'headers="trans-other r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-trans-other" role="gridcell" ' +
'class="expense editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-trans-other-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-trans-other-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter air, rail, or other major travel expense for trip ' +
this.numRows + '" />' +
'</td>';
// Car rental transportation cost cell
headers = 'headers="trans-rent r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-trans-rent" role="gridcell" ' +
'class="expense editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-trans-rent-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-trans-rent-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter car rental expense for trip ' + this.numRows + '" />' +
'</td>';
// Misc. transportation cost cell
headers = 'headers="trans-misc r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-trans-misc" role="gridcell" ' +
'class="expense editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-trans-misc-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-trans-misc-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter miscellaneous expenses, such as parking, tolls, or taxi, for trip ' +
this.numRows + '" />' +
'</td>';
// Lodging cost cell
headers = 'headers="lodging r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-lodging" role="gridcell" ' +
'class="expense editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-lodging-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-lodging-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter lodging expense for trip ' + this.numRows + '" />' +
'</td>';
// Meals cost cell
headers = 'headers="meals r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-meals" role="gridcell" ' +
'class="expense editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-meals-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-meals-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter meal expense for trip ' + this.numRows + '" />' +
'</td>';
// Misc. cost cell
headers = 'headers="misc r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-misc" role="gridcell" ' +
'class="expense editable" ' + headers + 'tabindex="-1">' +
'<span id="r' + this.numRows + '-misc-dta" class="data" aria-hidden="false"></span>' +
'<input id="r' + this.numRows + '-misc-edt" class="edt" type="text" aria-hidden="true" size="6" title="Enter other miscellaneous expenses for trip ' + this.numRows + '" />' +
'</td>';
// Row total cell and closing tr
headers = 'headers="total r' + this.numRows + '-date"';
row += '<td id="r' + this.numRows + '-total" class="calc" ' + headers + '>-</td></tr>'
// Append the new row to the grid
$('tbody').find('tr#totals').before(row);
// Add an entry for this row in the list of rows
$('#row_numbers').append('<div id="row' + this.numRows + '">Row ' + this.numRows + '</div>');
}
else {
$('td#alert').text('Maximum number of rows (' + this.maxRows + ') reached.');
this.$addButton.unbind('mousedown mouseup keydown keyup');
this.$addButton.addClass('disabled');
this.$addButton.attr('aria-disabled', 'true');
this.$addButton.removeClass('pressed');
this.$addButton.attr('aria-pressed', 'false');
}
} //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)
var $button = this.$addButton; // store a pointer to the add row button property (saves a dereference)
/************ Bind the handlers for the editable grid cells in the data table **********/
//
// bind a click handler
$tbody.delegate('.editable', 'click', function (e) {
return thisObj.handleCellClick(this, e);
}); // end click handler
// bind a double click handler
$tbody.delegate('.editable', 'dblclick', function (e) {
return thisObj.handleCellDblclick(this, e);
}); // end double click handler
// bind a keydown handler
$tbody.delegate('.editable', 'keydown', function (e) {
return thisObj.handleCellKeyDown(this, e);
}); // end keydown handler
// bind a keypress handler - consume events for Opera
$tbody.delegate('.editable', 'keypress', function (e) {
return thisObj.handleCellKeyPress(this, e);
}); // end keyup handler
// bind a focus handler
$tbody.delegate('.editable', 'focus', function (e) {
return thisObj.handleCellFocus(this, e);
}); // end focus handler
// bind a blur handler
$tbody.delegate('.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
/************ Bind the handlers for the add row button **********/
// bind a mousedown handler
$button.mousedown(function (e) {
return thisObj.handleAddMouseDown(this, e);
}); // end add button mousedown handler
// bind a mouseup handler
$button.mouseup(function (e) {
return thisObj.handleAddMouseUp(this, e);
}); // end add button mouseup handler
// bind a keydown handler
$button.keydown(function (e) {
return thisObj.handleAddKeyDown(this, e);
}); // end add button keydown handler
// bind a keyup handler
$button.keyup(function (e) {
return thisObj.handleAddKeyUp(this, e);
}); // end add button keyup handler
// bind a focus handler
$button.focus(function (e) {
return thisObj.handleAddFocus(this, e);
}); // end add button focus handler
// bind a blur handler
$button.blur(function (e) {
return thisObj.handleAddBlur(this, e);
}); // end add button blur handler
} // 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();
$data.attr('aria-hidden', 'true');
$edt.show();
$edt.attr('aria-hidden', 'false');
// 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();
$edt.attr('aria-hidden', 'true');
$data.show();
$data.attr('aria-hidden', 'false');
// 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 = $('#r' + 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-tot-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 = $('#r' + 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-tot' + 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.
$('th#tot-expense-lbl').siblings().not('td#calc-tot-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-tot-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(1, len - 1) - 1;
if (rowNum > 0)
{
// build the id string of the new cell
var newCell = '#r' + 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(1, len - 1)) + 1;
if (rowNum <= this.numRows)
{
// build the id string of the new cell
var newCell = '#r' + 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(1, len - 1)) - 1;
if (rowNum > 0)
{
// build the id string of the new cell
var newCell = '#r1-' + 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(1, len - 1)) + 1;
if (rowNum <= this.numRows)
{
// build the id string of the new cell
var newCell = '#r' + 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;
}
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();
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();
// leave edit mode
this.leaveEditMode($parentNode);
e.stopPropagation();
return false;
}
//
// handleAddMouseDown() is a callback for the mousedown event. It is bound to the add row button
//
// @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.handleAddMouseDown = function(id, e) {
$(id).addClass('pressed');
$(id).attr('aria-pressed', 'true')
this.addRow();
$(id).removeClass('pressed');
$(id).attr('aria-pressed', 'false')
$(id).focus();
e.stopPropagation();
return false;
} // end handleAddMouseDown()
//
// handleAddMouseUp() is a callback for the mouseup event. It is bound to the add row button
//
// @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.handleAddMouseUp = function(id, e) {
$(id).removeClass('pressed');
$(id).attr('aria-pressed', 'false')
e.stopPropagation();
return false;
} // end handleAddMouseUp()
//
// handleAddKeyDown() is a callback for the keydown event. It is bound to the add row button
//
// @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.handleAddKeyDown = 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.enter:
case this.keys.space: {
$(id).addClass('pressed');
$(id).attr('aria-pressed', 'true')
this.addRow();
e.stopPropagation();
return false;
break;
}
}
return true;
} // end handleAddKeyDown()
//
// handleAddKeyUp() is a callback for the keyup event. It is bound to the add row button
//
// @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.handleAddKeyUp = 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.enter:
case this.keys.space: {
$(id).removeClass('pressed');
$(id).attr('aria-pressed', 'false')
e.stopPropagation();
return false;
break;
}
}
return true;
} // end handleAddKeyDown()
//
// handleAddFocus() is a callback for the focus event. It is bound to the add row button
//
// @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.handleAddFocus = function(id, e) {
$(id).addClass('buttonFocus');
return true;
}
//
// handleAddBlur() is a callback for the blur event. It is bound to the add row button
//
// @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.handleAddBlur = function(id, e) {
$(id).removeClass('buttonFocus pressed');
$(id).attr('aria-pressed', 'false')
return false;
}