<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>