Radio Navigation Menu
Radio Example 3: Background Images
Lunch Options
-
Thai
-
Subway
-
Jimmy Johns
-
Radio Maria
-
Rainbow Gardens
Drink Options
-
Water
-
Tea
-
Coffee
-
Cola
-
Ginger Ale
Application Notes
This version of the radio group widget uses background images to show the button state. Background images are typically not displayed in high contrast mode. For this reason, background images should not be relied upon to display essential visual information.
Keyboard Shortcuts
The following keyboard shortcuts are implemented for this example (based on recommended shortcuts specified by the DHTML Style Guide Working Group.):
- Tab: Move between radio button groups.
- Up/Left and Down/Right Arrow: Move selection between radio items in a group
- Ctrl + Arrow: Move between radio items in a group without changing checked state
- Space Bar: Check the radio button that currently has focus
ARIA Roles and Properties
-
Roles:
role="application"
role="radiogroup"
role="radio"
- States and properties:
aria-checked
aria-describedby
aria-labelledby
HTML Source Code
Show HTML Source Code: radio3.inc
<div role="application">
<h3 id="rg1_label">Lunch Options</h3>
<ul class="radiogroup"
id="rg1"
role="radiogroup"
aria-labelledby="rg1_label"
>
<li id="r1"
class="unchecked"
tabindex="-1"
role="radio"
aria-checked="false">
Thai
</li>
<li id="r2"
class="unchecked"
tabindex="-1"
role="radio"
aria-checked="false">
Subway
</li>
<li id="r3"
class="unchecked"
tabindex="-1"
role="radio"
aria-checked="false">
Jimmy Johns
</li>
<li id="r4"
class="checked"
tabindex="0"
role="radio"
aria-checked="true">
Radio Maria
</li>
<li id="r5"
class="unchecked"
tabindex="-1"
role="radio"
aria-checked="false">
Rainbow Gardens
</li>
</ul>
<!-- Start of second Radio Group -->
<h3 id="rg2_label">Drink Options</h3>
<ul id="rg2"
class="radiogroup"
role="radiogroup"
aria-labelledby="rg2_label"
>
<li id="r6"
class="unchecked"
tabindex="0"
role="radio"
aria-checked="false">
Water
</li>
<li id="r7"
class="unchecked"
tabindex="-1"
role="radio"
aria-checked="false">
Tea
</li>
<li id="r8"
class="unchecked"
tabindex="-1"
role="radio"
aria-checked="false">
Coffee
</li>
<li id="r9"
class="unchecked"
tabindex="-1"
role="radio"
aria-checked="false">
Cola
</li>
<li id="r10"
class="unchecked"
class="last"
tabindex="0"
role="radio"
aria-checked="false">
Ginger Ale
</li>
</ul>
</div>
Javascript Source Code
Show Javascript Source Code: radio3.js
<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.removeClass('checked').addClass('unchecked');
// set the previous button's aria-checked attribute to false
this.$checked.attr('aria-checked', 'false');
// update the button image
$id.removeClass('unchecked').addClass('checked');
// set the new button's aria-checked attribute to false
$id.attr('aria-checked', 'true');
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
this.$checked.attr('tabindex', '-1');
}
// Place this button in the tab order
$id.attr('tabindex', '0');
// update the stored $checked object
this.$checked = $id;
}
// reset the checkButton flag - in case it was false
this.checkButton = true;
// update the stored $active object
this.$active = $id;
// give this button the selected class
$id.addClass('selected');
} // end selectButton()
//
// 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.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;
}
// give the previous button focus
$prev[0].focus();
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;
}
// give the next button focus
$next[0].focus();
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) {
// Do button selection processing
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>
CSS Source Code
Show CSS Source Code: radio3.css
<style type="text/css">
/* Example 3 CSS
// This example uses CSS background-image to visual display checkbox status
*/
ul.radiogroup {
margin: 0;
padding: 0;
}
ul.radiogroup li {
margin: 0;
margin-left: 1em;
padding: 4px;
padding-left: 20px;
list-style: none;
width: 6em;
}
ul.radiogroup li:hover {
padding: 2px 18px;
background-position: 2px 4px;
border: gray 2px solid;
}
ul.radiogroup li.selected {
padding: 2px 18px;
background-position: 2px 4px;
border: black 2px solid;
}
.offscreen {
position: absolute;
left: -200em;
top: -20em;
}
.checked {
background: url('./images/radio-checked.gif') no-repeat 4px 6px;
}
.unchecked {
background: url('./images/radio-unchecked.gif') no-repeat 4px 6px;
}
</style>
W3C Validation of HTML5