Accessibility Examples
Test Start
RSS Feed Controls
:
:
:
:
:
Timer Controls
:
:
:
:
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 92 |
|
none |
| Rule 93 |
|
none |
| Rule 94 |
|
none |
| Rule 95 |
|
none |
Calculations
No calculations for test 127
Test Description
- This test implements a simple RSS feed reader.
Keyboard shortcuts:
* Tab: Move between region controls
* Enter or space: Toggle display of region. If focus is on region close button, collapse region and move focus to the region’s controlling button.
Test Markup
- ARIA 1.0: [aria-activedescendant]
- ARIA 1.0: [aria-atomic]
- ARIA 1.0: [aria-controls]
- ARIA 1.0: [aria-labelledby]
- ARIA 1.0: [aria-live]
- ARIA 1.0: [aria-pressed]
- ARIA 1.0: [aria-relevant]
- ARIA 1.0: [aria-selected]
- ARIA 1.0: [role="application"]
- ARIA 1.0: [role="log"]
- ARIA 1.0: [role="option"]
- ARIA 1.0: [role="timer"]
User Agent Implementation
No user agent implementation information.
HTML Source Code
<div role="application">
<div class="controls">
<h2>RSS Feed Controls</h2>
<p><label id="rssFeedSelectLabel" for="rssFeedSelect">Select an RSS Feed</label>:
<select id="rssFeedSelect" aria-labelledby="rssFeedSelectLabel" aria-activedescendant="rssFeed1" tabindex="0">
<option
id="rssFeed1"
role="option"
aria-selected="true"
value="http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml" selected>Mozilla Latest Headlines
</option>
<option
id="rssFeed2"
role="option"
aria-selected="false"
value="http://news.google.com/?output=rss">Google News
</option>
<option
id="rssFeed3"
role="option"
aria-selected="false"
value="http://feeds.bbci.co.uk/news/rss.xml">BBC News
</option>
<option
id="rssFeed4"
role="option"
aria-selected="false"
value="http://hosted.ap.org/lineups/TOPHEADS.rss?SITE=AP&SECTION=HOME">Associated Press News
</option>
<option
id="rssFeed5"
role="option"
aria-selected="false"
value="http://rss.slashdot.org/Slashdot/slashdot">Slashdot
</option>
</select></p>
<p><label id="intervalSelectLabel" for="intervalSelect">Select an Update Interval</label>:
<select id="intervalSelect" aria-labelledby="intervalSelectLabel" aria-activedescendant="interval2" tabindex="0">
<option
id="interval1"
role="option"
aria-selected="false"
value="1">1 minute
</option>
<option
id="interval2"
role="option"
aria-selected="true"
value="5" selected>5 minutes
</option>
<option
id="interval3"
role="option"
aria-selected="false"
value="10">10 minutes
</option>
</select></p>
<hr />
<p><label id="rssPolitenessSelectLabel" for="rssPolitenessSelect">Politeness Level (aria-live value)</label>:
<select id="rssPolitenessSelect" aria-labelledby="rssPolitenessSelectLabel" aria-activedescendant="rssPolite3" tabindex="0">
<option
id="rssPolite1"
role="option"
aria-selected="false"
value="off">off
</option>
<option
id="rssPolite2"
role="option"
aria-selected="false"
value="polite">polite
</option>
<option
id="rssPolite3"
role="option"
aria-selected="true"
value="assertive" selected>assertive
</option>
</select></p>
<p><label id="rssAtomicSelectLabel" for="rssAtomicSelect">Atomic Updates</label>:
<select id="rssAtomicSelect" aria-labelledby="rssAtomicSelectLabel" aria-activedescendant="rssAtomic1" tabindex="0">
<option
id="rssAtomic1"
role="option"
aria-selected="true"
value="false" selected>false
</option>
<option
id="rssAtomic2"
role="option"
aria-selected="false"
value="true">true
</option>
</select></p>
<p><label id="rssRelevantSelectLabel" for="rssRelevantSelect">Change Relevance</label>:
<select id="rssRelevantSelect" aria-labelledby="rssRelevantSelectLabel" aria-activedescendant="rssRelevant1" tabindex="0">
<option
id="rssRelevant1"
role="option"
aria-selected="true"
value="additions" selected>additions
</option>
<option
id="rssRelevant2"
role="option"
aria-selected="false"
value="removals">removals
</option>
<option
id="rssRelevant3"
role="option"
aria-selected="false"
value="text">text
</option>
<option
id="rssRelevant4"
role="option"
aria-selected="false"
value="all">all
</option>
</select></p>
</div>
<div class="controls">
<h2>Timer Controls</h2>
<p><label id="timerPolitenessSelectLabel" for="timerPolitenessSelect">Politeness Level (aria-live value)</label>:
<select id="timerPolitenessSelect" aria-labelledby="timerPolitenessSelectLabel" aria-activedescendant="timerPolite2" tabindex="0">
<option
id="timerPolite1"
role="option"
aria-selected="false"
value="off">off
</option>
<option
id="timerPolite2"
role="option"
aria-selected="true"
value="polite" selected>polite
</option>
<option
id="timerPolite3"
role="option"
aria-selected="false"
value="assertive">assertive
</option>
</select></p>
<p><label id="timerAtomicSelectLabel" for="timerAtomicSelect">Atomic Updates</label>:
<select id="timerAtomicSelect" aria-labelledby="timerAtomicSelectLabel" aria-activedescendant="timerAtomic2" tabindex="0">
<option
id="timerAtomic1"
role="option"
aria-selected="false"
value="false">false
</option>
<option
id="timerAtomic2"
role="option"
aria-selected="true"
value="true" selected>true
</option>
</select></p>
<p><label id="timerRelevantSelectLabel" for="timerRelevantSelect">Change Relevance</label>:
<select id="timerRelevantSelect" aria-labelledby="timerRelevantSelectLabel" aria-activedescendant="timerRelevant4" tabindex="0">
<option
id="timerRelevant1"
role="option"
aria-selected="false"
value="additions">additions
</option>
<option
id="timerRelevant2"
role="option"
aria-selected="false"
value="removals">removals
</option>
<option
id="timerRelevant3"
role="option"
aria-selected="false"
value="text">text
</option>
<option
id="timerRelevant4"
role="option"
aria-selected="true"
value="all" selected>all
</option>
</select></p>
<hr />
<button id="startStop" aria-controls="timer rssFeedContent" aria-pressed="false">Start Timer</button>
<div id="countdown">
<label id="timerlabel" for="timer">Time until next update</label>:
<div id="timer" role="timer" aria-labelledby="timerLabel" aria-live="polite" aria-atomic="true" aria-relevant="all"></div>
</div>
</div>
<div id="rssFeedContent" role="log" aria-live="assertive" aria-atomic="false" aria-relevant="additions">
</div>
</div>
CSS Code
div.controls {
margin-left: 10px;
padding: 5px 10px;
width: 21em;
float: left;
display: inline;
overflow: visible;
}
button,
select {
float: right;
}
div#countdown {
margin-top: 3em;
margin-bottom: 1em;
}
#timerlabel {
margin-left: 4em;
font-weight: bold;
}
#timer {
margin: 0;
padding: 2px 5px;
float: right;
width: 3.5em;
height: 1.2em;
font-weight: bold;
text-align: right;
background-color: #eee;
border: 1px solid black;
}
hr {
height: 1px;
width: 80%;
color: #000088;
background-color: #000088;
}
#rssFeedContent {
margin: 10px;
padding: 10px;
width: 43em;
border: 1px solid black;
clear: both;
}
.entry {
margin-bottom: 20px;
width: 40em;
}
.postTitle {
margin: 0;
padding: 5px;
}
.entry a.storyLink {
padding-bottom: 1px;
color: #000088;
font-size: 1.2em;
text-decoration: none;
border-bottom: 1px solid #000088;
}
.entry a.storyLink:hover {
color: #880000;
border-bottom: 2px solid #880000;
}
.entry a.storyLink:visited {
color: #777;
}
.entry .date {
margin: 0;
padding: 0;
padding-right: 10px;
font-size: 80%;
letter-spacing: 0.1em;
}
.entry .summary {
margin: 0;
padding: 0 10px;
}
Javascript Source Code
var g_seconds = 0;
$(document).ready(function() {
// call init() to reset the select elements - in case of page reload
init();
// set the globabal second countdown variable before we create the associated
// live region
g_seconds = $('#intervalSelect').val() * 60 - 1;
var timerRegion = new liveRegion('timer', 'countdown()', 1000, true);
var rssRegion = new liveRegion('rssFeedContent', 'getRssFeed()', $('#intervalSelect').val() * 60000);
var paused = true;
// do initial rss grab
getRssFeed();
//////////////////// bind event handlers for rssRegion controls /////////////////////////////
// bind a change event handler for the RSS feed select
$('#rssFeedSelect').change(function(e) {
var $newItem = null;
// select the new item
$newItem = selectItem($(this), $(this).find('option'), $(this).val());
if ($newItem != null) {
// get the new feed
getRssFeed();
}
e.stopPropagation();
return false;
});
// bind a change event handler for the interval select
$('#intervalSelect').change(function(e) {
var $newItem = null;
// select the new item
$newItem = selectItem($(this), $(this).find('option'), $(this).val());
if ($newItem != null) {
// set the new timer interval
rssRegion.setInterval($newItem.val() * 60000);
// update the countdown interval
g_seconds = $newItem.val() * 60 - 1;
}
e.stopPropagation();
return false;
});
// bind a change event handler for the politeness select
$('#rssPolitenessSelect').change(function(e) {
var $newItem = null;
// select the new item
$newItem = selectItem($(this), $(this).find('option'), $(this).val());
if ($newItem != null) {
// set the politeness level
rssRegion.setPoliteness($newItem.val());
}
e.stopPropagation();
return false;
});
// bind a change event handler for the atomic updates select
$('#rssAtomicSelect').change(function(e) {
var $newItem = null;
// select the new item
$newItem = selectItem($(this), $(this).find('option'), $(this).val());
if ($newItem != null) {
// set the atomic update setting
rssRegion.setAtomic($newItem.val());
}
e.stopPropagation();
return false;
});
// bind a change event handler for the update relevance select
$('#rssRelevantSelect').change(function(e) {
var $newItem = null;
// select the new item
$newItem = selectItem($(this), $(this).find('option'), $(this).val());
if ($newItem != null) {
// set the update relevance setting
rssRegion.setRelevant($newItem.val());
}
e.stopPropagation();
return false;
});
//////////////////// bind event handlers for timer controls /////////////////////////////
// bind a change event handler for the politeness select
$('#timerPolitenessSelect').change(function(e) {
var $newItem = null;
// select the new item
$newItem = selectItem($(this), $(this).find('option'), $(this).val());
if ($newItem != null) {
// set the politeness level
timerRegion.setPoliteness($newItem.val());
}
e.stopPropagation();
return false;
});
// bind a change event handler for the atomic updates select
$('#timerAtomicSelect').change(function(e) {
var $newItem = null;
// select the new item
$newItem = selectItem($(this), $(this).find('option'), $(this).val());
if ($newItem != null) {
// set the atomic update setting
timerRegion.setAtomic($newItem.val());
}
e.stopPropagation();
return false;
});
// bind a change event handler for the update relevance select
$('#timerRelevantSelect').change(function(e) {
var $newItem = null;
// select the new item
$newItem = selectItem($(this), $(this).find('option'), $(this).val());
if ($newItem != null) {
// set the update relevance setting
timerRegion.setRelevant($newItem.val());
}
e.stopPropagation();
return false;
});
// bind a click event handler for the start/stop button
$('#startStop').click(function(e) {
timerRegion.toggleUpdates();
if (paused == false) {
$('#timer').html('Paused');
$('#startStop').html('Start Timer');
paused = true;
}
else
{
$('#startStop').html('Stop Timer');
paused = false;
}
e.stopImmediatePropagation();
return false;
});
}); // end ready
//
// Function init() is a function to initialize the select elements to match their activedescendant values. This
// ensures that any page reloads do not introduce erroneous aria attribute values
function init() {
var elems = new Array('rssFeedSelect', 'intervalSelect', 'rssPolitenessSelect', 'rssAtomicSelect',
'rssRelevantSelect', 'timerPolitenessSelect', 'timerAtomicSelect', 'timerRelevantSelect');
var $id = null;
var val = null;
for (ndx in elems) {
// get the jQuery object of select to set
$id = $('#' + elems[ndx]);
// get the value of the active descendant
val = $('#' + $id.attr('aria-activedescendant')).val();
// set the value of the select
$id.val(val);
}
} // end init()
//
// Function selectItem() is a function to iterate through an option list to find a new option and select it.
// The function also updates the aria-seleced attribute of the items.
//
// @param ($select object) $select is the jquery object of the select that the option list is part of.
//
// @param ($list object) $list is the list of options to iterate through.
//
// @param (val string) val is the value to find in the list.
//
// @return (object) returns the jquery object of the new item selected. Returns NULL if not found.
//
function selectItem($select, $list, val) {
var $prevItem = $('#' + $select.attr('aria-activedescendant'));
var $newItem = null;
// find the list item associated with the new value
$list.each(function() {
if ($(this).val() == val) {
$newItem = $(this);
}
});
if ($newItem == null) {
// no new item found
return null;
}
// set the aria-selected attribute to false for the previously selected item
$prevItem.attr('aria-selected', 'false')
// set the aria-selected attribute to true for the new item
$newItem.attr('aria-selected', 'true')
// update the active descendent of the select
$select.attr('aria-activedescendant', $newItem.attr('id'));
// return the new item selected
return $newItem;
} // end selectItem()
//
// Function countdown() is a callback to update the countdown live region. This callback is passed to
// rssRegion.
//
// @return N/A
//
function countdown () {
var minutes = Math.floor(g_seconds / 60);
var seconds = g_seconds % 60;
if (seconds < 10) {
seconds = '0' + seconds;
}
if (g_seconds == 0) {
// do nothing - countdown will be reset by the
// rssRegion interval timer
$('#timer').text('updating');
}
else {
$('#timer').text(minutes + ':' + seconds);
g_seconds--;
}
}
//
// Function getRssFeed() is a callback to obtain the top 5 items from an RSS feed. the function checks
// the entries obtained against those that may have been obtained previously (present in the live region).
// It will remove old entries and prepend newer ones it has received. This callback is passed to rssRegion.
//
// Note: This function uses the jquery.jGFeed plugin in order to get around the same-domain security
// barrier for AJAX.
//
// @return N/A
//
function getRssFeed() {
var $id = $('#rssFeedContent');
var $topEntry = $id.find('.entry').first();
$.jGFeed($('#rssFeedSelect').val(), function(feed) {
if(!feed) {
alert('there was an error');
}
// Check to see if this is a new article
if ($topEntry) {
var topTitle = $topEntry.find('.storyLink').text();
var topNdx;
// iterate through the feed entries and find the index
// of the matching story.
for(topNdx = 0; topNdx < feed.entries.length; topNdx++) {
if (topTitle == feed.entries[topNdx].title) {
break;
}
}
if (topNdx == feed.entries.length) {
// there was no match. Clear the region and
// append all articles
$id.empty();
}
else {
// there was a match. Remove as many articles
// as there are new ones from the feed.
for (var ndx = 0; ndx < topNdx; ndx++) {
// remove the last entry
$id.find('.entry').last().remove();
}
}
}
for (var i = topNdx - 1; i >= 0; i--) {
var entry = feed.entries[i];
var title = entry.title;
var link = entry.link;
var description = entry.contentSnippet;
var pubDate = entry.publishedDate;
var html = '<div class="entry"><h4 class="postTitle">'
+ '<a class="storyLink" href="' + link + '" target="_blank" tabindex="0">'
+ title + '</a>';
html += '<br><em class="date">' + pubDate + '</em></h4><div class="summary">';
html += '<p class="description">' + description + '</p></div></div>';
$id.prepend(html);
}
}, 5);
// reset cowntdown
g_seconds = $('#intervalSelect').val() * 60 - 1;
} // end getRssFeed()