cthulhu/test/html/aria-tabpanel2.html

770 lines
20 KiB
HTML

<html>
<head>
<style type="text/css">
.tabpanel {
margin: 20px;
padding: 0;
height: 1%; /* IE fix for float bug */
}
.tablist {
margin: 0 0px;
padding: 0;
list-style: none;
}
.tab {
margin: .2em 1px 0 0;
padding: 10px;
height: 1em;
font-weight: bold;
background-color: #ec9;
border: 1px solid black;
-webkit-border-radius-topright: 5px;
-webkit-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
-moz-border-radius-topleft: 5px;
border-radius-topright: 5px;
border-radius-topleft: 5px;
float: left;
display: inline; /* IE float bug fix */
}
.panel {
clear: both;
display: block;
margin: 0 0 0 0;
padding: 10px;
width: 600px;
border: 1px solid black;
-webkit-border-radius-topright: 10px;
-webkit-border-radius-bottomleft: 10px;
-webkit-border-radius-bottomright: 10px;
-moz-border-radius-topright: 10px;
-moz-border-radius-bottomleft: 10px;
-moz-border-radius-bottomright: 10px;
border-radius-topright: 10px;
border-radius-bottomleft: 10px;
border-radius-bottomright: 10px;
}
ul.controlList {
list-style-type: none;
}
li.selected {
color: black;
background-color: #fff;
border-bottom: 1px solid white;
}
.focus {
margin-top: 0;
height: 1.2em;
}
.accordian {
margin: 0;
float: none;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
width: 600px;
}
.hidden {
position: absolute;
left: -300em;
top: -30em;
}
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var panel1 = new tabpanel("tabpanel1", false);
//var panel2 = new tabpanel("accordian1", true);
});
//
// keyCodes() is an object to contain keycodes needed for the application
//
function keyCodes() {
// Define values for keycodes
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;
} // end keyCodes
//
// tabpanel() is a class constructor to create a ARIA-enabled tab panel widget.
//
// @param (id string) id is the id of the div containing the tab panel.
//
// @param (accordian boolean) accordian is true if the tab panel should operate
// as an accordian; false if a tab panel
//
// @return N/A
//
// Usage: Requires a div container and children as follows:
//
// 1. tabs/accordian headers have class 'tab'
//
// 2. panels are divs with class 'panel'
//
function tabpanel(id, accordian) {
// define the class properties
this.panel_id = id; // store the id of the containing div
this.accordian = accordian; // true if this is an accordian control
this.$panel = $('#' + id); // store the jQuery object for the panel
this.keys = new keyCodes(); // keycodes needed for event handlers
this.$tabs = this.$panel.find('.tab'); // Array of panel tabs.
this.$panels = this.$panel.children('.panel'); // Array of panel.
// Bind event handlers
this.bindHandlers();
// Initialize the tab panel
this.init();
} // end tabpanel() constructor
//
// Function init() is a member function to initialize the tab/accordian panel. Hides all panels. If a tab
// has the class 'selected', makes that panel visible; otherwise, makes first panel visible.
//
// @return N/A
//
tabpanel.prototype.init = function() {
var $tab; // the selected tab - if one is selected
// add aria attributes to the panel container
this.$panel.attr('aria-multiselectable', this.accordian);
// add aria attributes to the panels
this.$panels.attr('aria-hidden', 'true');
// hide all the panels
this.$panels.hide();
// get the selected tab
$tab = this.$tabs.filter('.selected');
if ($tab == undefined) {
$tab = this.$tabs.first();
$tab.addClass('selected');
}
// show the panel that the selected tab controls and set aria-hidden to false
this.$panel.find('#' + $tab.attr('aria-controls')).show().attr('aria-hidden', 'false');
} // end init()
//
// Function switchTabs() is a member function to give focus to a new tab or accordian header.
// If it's a tab panel, the currently displayed panel is hidden and the panel associated with the new tab
// is displayed.
//
// @param ($curTab obj) $curTab is the jQuery object of the currently selected tab
//
// @param ($newTab obj) $newTab is the jQuery object of new tab to switch to
//
// @param (activate boolean) activate is true if focus should be set on an element in the panel; false if on tab
//
// @return N/A
//
tabpanel.prototype.switchTabs = function($curTab, $newTab) {
// Remove the highlighting from the current tab
$curTab.removeClass('selected');
$curTab.removeClass('focus');
// remove tab from the tab order
$curTab.attr('tabindex', '-1');
// update the aria attributes
// Highlight the new tab
$newTab.addClass('selected');
// If this is a tab panel, swap displayed tabs
if (this.accordian == false) {
// hide the current tab panel and set aria-hidden to true
this.$panel.find('#' + $curTab.attr('aria-controls')).hide().attr('aria-hidden', 'true');
// show the new tab panel and set aria-hidden to false
this.$panel.find('#' + $newTab.attr('aria-controls')).show().attr('aria-hidden', 'false');
}
// Make new tab navigable
$newTab.attr('tabindex', '0');
// give the new tab focus
$newTab.focus();
} // end switchTabs()
//
// Function togglePanel() is a member function to display or hide the panel associated with an accordian header
//
// @param ($tab obj) $tab is the jQuery object of the currently selected tab
//
// @return N/A
//
tabpanel.prototype.togglePanel = function($tab) {
$panel = this.$panel.find('#' + $tab.attr('aria-controls'));
if ($panel.attr('aria-hidden') == 'true') {
$panel.slideDown(100);
$panel.attr('aria-hidden', 'false');
}
else {
$panel.slideUp(100);
$panel.attr('aria-hidden', 'true');
}
} // end togglePanel()
//
// Function bindHandlers() is a member function to bind event handlers for the tabs
//
// @return N/A
//
tabpanel.prototype.bindHandlers = function() {
var thisObj = this; // Store the this pointer for reference
//////////////////////////////
// Bind handlers for the tabs / accordian headers
// bind a tab keydown handler
this.$tabs.keydown(function(e) {
return thisObj.handleTabKeyDown($(this), e);
});
// bind a tab keypress handler
this.$tabs.keypress(function(e) {
return thisObj.handleTabKeyPress($(this), e);
});
// bind a tab click handler
this.$tabs.click(function(e) {
return thisObj.handleTabClick($(this), e);
});
// bind a tab focus handler
this.$tabs.focus(function(e) {
return thisObj.handleTabFocus($(this), e);
});
// bind a tab blur handler
this.$tabs.blur(function(e) {
return thisObj.handleTabBlur($(this), e);
});
/////////////////////////////
// Bind handlers for the panels
// bind a keydown handlers for the panel focusable elements
this.$panels.keydown(function(e) {
return thisObj.handlePanelKeyDown($(this), e);
});
// bind a keypress handler for the panel
this.$panels.keypress(function(e) {
return thisObj.handlePanelKeyPress($(this), e);
});
} // end bindHandlers()
//
// Function handleTabKeyDown() is a member function to process keydown events for a tab
//
// @param ($tab obj) $tab is the jquery object of the tab being processed
//
// @paran (e obj) e is the associated event object
//
// @return (boolean) Returns true if propagating; false if consuming event
//
tabpanel.prototype.handleTabKeyDown = function($tab, e) {
if (e.altKey) {
// do nothing
return true;
}
switch (e.keyCode) {
case this.keys.enter:
case this.keys.space: {
// Only process if this is an accordian widget
if (this.accordian == true) {
// display or collapse the panel
this.togglePanel($tab);
e.stopPropagation();
return false;
}
return true;
}
case this.keys.left:
case this.keys.up: {
var thisObj = this;
var $prevTab; // holds jQuery object of tab from previous pass
var $newTab; // the new tab to switch to
if (e.ctrlKey) {
// Ctrl+arrow moves focus from panel content to the open
// tab/accordian header.
}
else {
var curNdx = this.$tabs.index($tab);
if (curNdx == 0) {
// tab is the first one:
// set newTab to last tab
$newTab = this.$tabs.last();
}
else {
// set newTab to previous
$newTab = this.$tabs.eq(curNdx - 1);
}
// switch to the new tab
this.switchTabs($tab, $newTab);
}
e.stopPropagation();
return false;
}
case this.keys.right:
case this.keys.down: {
var thisObj = this;
var foundTab = false; // set to true when current tab found in array
var $newTab; // the new tab to switch to
var curNdx = this.$tabs.index($tab);
if (curNdx == this.$tabs.last().index()) {
// tab is the last one:
// set newTab to first tab
$newTab = this.$tabs.first();
}
else {
// set newTab to next tab
$newTab = this.$tabs.eq(curNdx + 1);
}
// switch to the new tab
this.switchTabs($tab, $newTab);
e.stopPropagation();
return false;
}
case this.keys.home: {
// switch to the first tab
this.switchTabs($tab, this.$tabs.first());
e.stopPropagation();
return false;
}
case this.keys.end: {
// switch to the last tab
this.switchTabs($tab, this.$tabs.last());
e.stopPropagation();
return false;
}
}
} // end handleTabKeyDown()
//
// Function handleTabKeyPress() is a member function to process keypress events for a tab.
//
//
// @param ($tab obj) $tab is the jquery object of the tab being processed
//
// @paran (e obj) e is the associated event object
//
// @return (boolean) Returns true if propagating; false if consuming event
//
tabpanel.prototype.handleTabKeyPress = function($tab, e) {
if (e.altKey) {
// do nothing
return true;
}
switch (e.keyCode) {
case this.keys.enter:
case this.keys.space:
case this.keys.left:
case this.keys.up:
case this.keys.right:
case this.keys.down:
case this.keys.home:
case this.keys.end: {
e.stopPropagation();
return false;
}
case this.keys.pageup:
case this.keys.pagedown: {
// The tab keypress handler must consume pageup and pagedown
// keypresses to prevent Firefox from switching tabs
// on ctrl+pageup and ctrl+pagedown
if (!e.ctrlKey) {
return true;
}
e.stopPropagation();
return false;
}
}
return true;
} // end handleTabKeyPress()
//
// Function handleTabClick() is a member function to process click events for tabs
//
// @param ($tab object) $tab is the jQuery object of the tab being processed
//
// @paran (e object) e is the associated event object
//
// @return (boolean) returns true
//
tabpanel.prototype.handleTabClick = function($tab, e) {
// Remove the highlighting from all tabs
this.$tabs.removeClass('selected');
// remove all tabs from the tab order
this.$tabs.attr('tabindex', '-1');
// hide all tab panels
this.$panels.hide();
// Highlight the clicked tab
$tab.addClass('selected');
// show the clicked tab panel
this.$panel.find('#' + $tab.attr('aria-controls')).show();
// make clicked tab navigable
$tab.attr('tabindex', '0');
// give the tab focus
$tab.focus();
return true;
} // end handleTabClick()
//
// Function handleTabFocus() is a member function to process focus events for tabs
//
// @param ($tab object) $tab is the jQuery object of the tab being processed
//
// @paran (e object) e is the associated event object
//
// @return (boolean) returns true
//
tabpanel.prototype.handleTabFocus = function($tab, e) {
// Add the focus class to the tab
$tab.addClass('focus');
return true;
} // end handleTabFocus()
//
// Function handleTabBlur() is a member function to process blur events for tabs
//
// @param ($tab object) $tab is the jQuery object of the tab being processed
//
// @paran (e object) e is the associated event object
//
// @return (boolean) returns true
//
tabpanel.prototype.handleTabBlur = function($tab, e) {
// Remove the focus class to the tab
$tab.removeClass('focus');
return true;
} // end handleTabBlur()
/////////////////////////////////////////////////////////
// Panel Event handlers
//
//
// Function handlePanelKeyDown() is a member function to process keydown events for a panel
//
// @param ($elem obj) $elem is the jquery object of the element being processed
//
// @paran (e obj) e is the associated event object
//
// @return (boolean) Returns true if propagating; false if consuming event
//
tabpanel.prototype.handlePanelKeyDown = function($elem, e) {
if (e.altKey) {
// do nothing
return true;
}
switch (e.keyCode) {
case this.keys.esc: {
e.stopPropagation();
return false;
}
case this.keys.left:
case this.keys.up: {
if (!e.ctrlKey) {
// do not process
return true;
}
// get the jQuery object of the tab
var $tab = $('#' + $elem.attr('aria-labeledby'));
// Move focus to the tab
$tab.focus();
e.stopPropagation();
return false;
}
case this.keys.pageup: {
var $newTab;
if (!e.ctrlKey) {
// do not process
return true;
}
// get the jQuery object of the tab
var $tab = this.$tabs.filter('.selected');
// get the index of the tab in the tab list
var curNdx = this.$tabs.index($tab);
if (curNdx == 0) {
// this is the first tab, set focus on the last one
$newTab = this.$tabs.last();
}
else {
// set focus on the previous tab
$newTab = this.$tabs.eq(curNdx - 1);
}
// switch to the new tab
this.switchTabs($tab, $newTab);
e.stopPropagation();
e.preventDefault();
return false;
}
case this.keys.pagedown: {
var $newTab;
if (!e.ctrlKey) {
// do not process
return true;
}
// get the jQuery object of the tab
var $tab = $('#' + $elem.attr('aria-labeledby'));
// get the index of the tab in the tab list
var curNdx = this.$tabs.index($tab);
if (curNdx == this.$tabs.last().index()) {
// this is the last tab, set focus on the first one
$newTab = this.$tabs.first();
}
else {
// set focus on the next tab
$newTab = this.$tabs.eq(curNdx + 1);
}
// switch to the new tab
this.switchTabs($tab, $newTab);
e.stopPropagation();
e.preventDefault();
return false;
}
}
return true;
} // end handlePanelKeyDown()
//
// Function handlePanelKeyPress() is a member function to process keypress events for a panel
//
// @param ($elem obj) $elem is the jquery object of the element being processed
//
// @paran (e obj) e is the associated event object
//
// @return (boolean) Returns true if propagating; false if consuming event
//
tabpanel.prototype.handlePanelKeyPress = function($elem, e) {
if (e.altKey) {
// do nothing
return true;
}
if (e.ctrlKey && (e.keyCode == this.keys.pageup || e.keyCode == this.keys.pagedown)) {
e.stopPropagation();
e.preventDefault();
return false;
}
switch (e.keyCode) {
case this.keys.esc: {
e.stopPropagation();
e.preventDefault();
return false;
}
}
return true;
} // end handlePanelKeyPress()
// focusable is a small jQuery extension to add a :focusable selector. It is used to
// get a list of all focusable elements in a panel. Credit to ajpiano on the jQuery forums.
//
$.extend($.expr[':'], {
focusable: function(element) {
var nodeName = element.nodeName.toLowerCase();
var tabIndex = $(element).attr('tabindex');
// the element and all of its ancestors must be visible
if (($(element)[nodeName == 'area' ? 'parents' : 'closest'](':hidden').length) == true) {
return false;
}
// If tabindex is defined, its value must be greater than 0
if (!isNaN(tabIndex) && tabIndex < 0) {
return false;
}
// if the element is a standard form control, it must not be disabled
if (/input|select|textarea|button|object/.test(nodeName) == true) {
return !element.disabled;
}
// if the element is a link, href must be defined
if ((nodeName == 'a' || nodeName == 'area') == true) {
return (element.href.length > 0);
}
// this is some other page element that is not normally focusable.
return false;
}
});
</script>
</head>
<body>
<div role="application">
<h2>Happy Time Pizza On-line Ordering System</h2>
<form>
<div id="tabpanel1" class="tabpanel">
<ul class="tablist" role="tablist">
<li id="tab1" class="tab selected" aria-controls="panel1" role="tab">Crust</li>
<li id="tab2" class="tab" aria-controls="panel2" role="tab">Veggies</li>
<li id="tab3" class="tab" aria-controls="panel3" role="tab">Carnivore</li>
<li id="tab4" class="tab" aria-controls="panel4" role="tab">Delivery</li>
</ul>
<div id="panel1" class="panel" aria-labeledby="tab1" role="tabpanel">
<h3>Select Crust</h3>
<ul class="controlList">
<li><label><input type="radio" name="crust" value="crust1" />Deep Dish</label></li>
<li><label><input type="radio" name="crust" value="crust2" checked="checked" />Thick and cheesy</label></li>
<li><label><input type="radio" name="crust" value="crust3" />Thick and spicy</label></li>
<li><label><input type="radio" name="crust" value="crust4" />Thin</label></li>
</ul>
</div>
<div id="panel2" class="panel" aria-labeledby="tab2" role="tabpanel">
<h3>Select Vegetables</h3>
<ul class="controlList">
<li><label><input type="checkbox" name="veg" value="black olives" />Black Olives</label></li>
<li><label><input type="checkbox" name="veg" value="green olives" />Green Olives</label></li>
<li><label><input type="checkbox" name="veg" value="green peppers" />Green Peppers</label></li>
<li><label><input type="checkbox" name="veg" value="mushrooms" />Mushrooms</label></li>
<li><label><input type="checkbox" name="veg" value="onions" />Onions</label></li>
<li><label><input type="checkbox" name="veg" value="pineapple" />Pineapple</label></li>
</ul>
</div>
<div id="panel3" class="panel" aria-labeledby="tab3" role="tabpanel">
<h3>Select Carnivore Options</h3>
<ul class="controlList">
<li><label><input type="checkbox" name="meat" value="pepperoni" />Pepperoni</label></li>
<li><label><input type="checkbox" name="meat" value="sausage" />Italian Sausage</label></li>
<li><label><input type="checkbox" name="meat" value="ham" />Ham</label></li>
<li><label><input type="checkbox" name="meat" value="hamburger" />Hamburger</label></li>
</ul>
</div>
<div id="panel4" class="panel" aria-labeledby="tab4" role="tabpanel">
<h3>Select Delivery Method</h3>
<ul class="controlList">
<li><label><input type="radio" name="delivery" value="delivery1" checked="checked" />Delivery</label></li>
<li><label><input type="radio" name="delivery" value="delivery2" />Eat in</label></li>
<li><label><input type="radio" name="delivery" value="delivery3" />Carry out</label></li>
<li><label><input type="radio" name="delivery" value="delivery4" />Overnight mail</label></li>
</ul>
</div>
</div>
</form>
</div>
</body>
</html>