<html> <head> <style type="text/css"> ul.radiogroup { margin: 0; padding: 0; } ul.radiogroup li { padding: 0; margin: 0; margin-left: 1em; padding: 4px; list-style: none; width: 6em; } ul.radiogroup li:hover { padding: 2px; border: gray 2px solid; } ul.radiogroup li.selected { padding: 2px; border: black 2px solid; } .offscreen { position: absolute; left: -200em; top: -20em; } </style> </head> <body> <div role="application"> <h3 id="rg1_label">Lunch Options</h3> <ul class="radiogroup" id="rg1" role="radiogroup" aria-labelledby="rg1_label"> <li id="r1" tabindex="-1" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Thai </li> <li id="r2" tabindex="-1" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Subway </li> <li id="r3" tabindex="-1" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Jimmy Johns </li> <li id="r4" tabindex="0" role="radio" aria-checked="true"> <img role="presentation" src="radio-checked.gif" alt=""> Radio Maria </li> <li id="r5" tabindex="-1" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Rainbow Gardens </li> </ul> <h3 id="rg2_label">Drink Options</h3> <ul id="rg2" class="radiogroup" role="radiogroup" aria-labelledby="rg2_label"> <li id="r6" tabindex="0" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Water </li> <li id="r7" tabindex="-1" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Tea </li> <li id="r8" tabindex="-1" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Coffee </li> <li id="r9" tabindex="-1" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Cola </li> <li id="r10" tabindex="0" role="radio" aria-checked="false"> <img role="presentation" src="radio-unchecked.gif" alt=""> Ginger Ale </li> </ul> </div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function() { var group1 = new radioGroup('rg1'); var group2 = new radioGroup('rg2'); }); // end ready function keyCodes () { // Define values for keycodes this.enter = 13; this.space = 32; this.left = 37; this.up = 38; this.right = 39; this.down = 40; } // // Function radioGroup() is a class to define an ARIA-enabled radiogroup widget. // // This widget attaches to an unordered list and makes each list entry a group // of radio buttons. // // @param (id object) id is the html id of the <ul> to attach to // // @return N/A // function radioGroup(id) { var thisObj = this; ///////// define widget properties /////////////// this.$id = $('#' + id); // find all list items with a role of radio this.$buttons = this.$id.find('li').filter('[role=radio]'); // Store the currently checked item this.$checked = this.$buttons.filter('[aria-checked=true]'); this.checkButton = true; // set to false during ctrl+arrow operations; this.$active = null; // the selected button (may not be checked) this.keys = new keyCodes(); ///////////// Bind Event handlers //////////////// this.$buttons.click(function(e) { return thisObj.handleClick(e, $(this)); }); this.$buttons.keydown(function(e) { return thisObj.handleKeyDown(e, $(this)); }); this.$buttons.keypress(function(e) { return thisObj.handleKeyPress(e, $(this)); }); this.$buttons.focus(function(e) { return thisObj.handleFocus(e, $(this)); }); this.$buttons.blur(function(e) { return thisObj.handleBlur(e, $(this)); }); } // // Function selectButton() is a member function to select and possibly check a button in the // radioGroup. // // @param ($id object) $id is the jQuery object of the button to select // // @return N/A // radioGroup.prototype.selectButton = function($id) { if (this.checkButton == true) { // checking the button // remove the checked image from the previously checked button this.$checked.find('img').attr('src', 'radio-unchecked.gif'); // update the button image $id.find('img').attr('src', 'radio-checked.gif'); if (this.$checked.length == 0) { // no previously checked group buttons // the first and last items in the group will have // tabindex=0. Remove them both from the tab order. this.$buttons.first().attr('tabindex', '-1'); this.$buttons.last().attr('tabindex', '-1'); } else { // remove the previously checked item from // the tab order and modify it's aria attributes accordingly this.$checked.attr('tabindex', '-1').attr('aria-checked', 'false'); } // Place this button in the tab order and modify it's aria attributes $id.attr('tabindex', '0').attr('aria-checked', 'true'); // update the stored $checked object this.$checked = $id; } // update the stored $active object this.$active = $id; // give this button the selected class $id.addClass('selected'); // Reset checkButton flag - in case it was false this.checkButton = true; } // end selectButton() // // Function handleClick() is a member function to process keydown events for the radioGroup. // // @param (e object) e is the event object // // @param ($id object) $is is the jquery object of the triggering element // // @return (boolean) Returns false if consuming event; true if propagating // radioGroup.prototype.handleClick = function(e, $id) { if (e.altKey || e.ctrlKey || e.shiftKey) { // do nothing return true; } // simply consume the event - browser calls focus() e.stopPropagation(); return false; } // end handleClick() // // Function handleKeyDown() is a member function to process keydown events for the radioGroup. // // @param (e object) e is the event object // // @param ($id object) $is is the jquery object of the triggering element // // @return (boolean) Returns false if consuming event; true if propagating // radioGroup.prototype.handleKeyDown = function(e, $id) { if (e.altKey) { // do nothing return true; } switch (e.keyCode) { case this.keys.space: case this.keys.enter: { if (e.ctrlkey || e.shiftKey) { // do nothing return true; } // select and check the button this.selectButton($id); e.stopPropagation(); return false; } case this.keys.left: case this.keys.up: { var $prev = $id.prev(); // the previous button if (e.shiftKey) { // do nothing return true; } // if this was the first item // select the last one in the group. if ($id.index() == 0) { $prev = this.$buttons.last(); } if (e.ctrlKey) { // set checkButton to false so // focus does not check button this.checkButton = false; } // select the previous button $prev[0].focus(); e.preventDefault(); e.stopPropagation(); return false; } case this.keys.right: case this.keys.down: { var $next = $id.next(); // the next button if (e.shiftKey) { // do nothing return true; } // if this was the last item, // select the first one in the group. if ($id.index() == this.$buttons.length - 1) { $next = this.$buttons.first(); } if (e.ctrlKey) { // set checkButton to false so // focus does not check button this.checkButton = false; } // select the next button $next[0].focus(); e.preventDefault(); e.stopPropagation(); return false; } } // end switch return true; } // end handleKeyDown() // // Function handleKeyPress() is a member function to process keydown events for the radioGroup. // This is needed to prevent browsers that process window events on keypress (such as Opera) from // performing unwanted scrolling of the window, etc. // // @param (e object) e is the event object // // @param ($id object) $is is the jquery object of the triggering element // // @return (boolean) Returns false if consuming event; true if propagating // radioGroup.prototype.handleKeyPress = function(e, $id) { if (e.altKey) { // do nothing return true; } switch (e.keyCode) { case this.keys.space: case this.keys.enter: { if (e.ctrlKey || e.shiftKey) { // do nothing return true; } } case this.keys.left: case this.keys.up: case this.keys.right: case this.keys.down: { if (e.shiftKey) { // do nothing return true; } e.stopPropagation(); return false; } } // end switch return true; } // end handleKeyPress() // // Function handleFocus() is a member function to process focus events for the radioGroup. // // @param (e object) e is the event object // // @param ($id object) $is is the jquery object of the triggering element // // @return (boolean) Returns false if consuming event; true if propagating // radioGroup.prototype.handleFocus = function(e, $id) { // select the button this.selectButton($id); return true; } // end handleFocus() // // Function handleBlur() is a member function to process blur events for the radioGroup. // // @param (e object) e is the event object // // @param ($id object) $is is the jquery object of the triggering element // // @return (boolean) Returns false if consuming event; true if propagating // radioGroup.prototype.handleBlur = function(e, $id) { // remove the focus styling from this buttons $id.removeClass('selected'); return true; } // end handleBlur() </script> </body> </html>