Test Start

RSS Feed Controls

:

:


:

:

:

Timer Controls

:

:

:


:
 

Test End

Rule References

Rule Pass IDs Fail IDs
Rule 84
  • application
  • rssFeedSelect
  • rssFeed1
  • startStop
  • timer
  • rssFeedContent
none
Rule 85
  • rssFeedSelect
  • rssFeed1
  • startStop
  • timer
  • rssFeedContent
none
Rule 86
  • rssFeed1
  • intervalSelect
  • startStop
  • timer
  • rssFeedContent
none
Rule 87
  • rssFeed1
  • interval1
  • startStop
none
Rule 88
  • startStop
none
Rule 92
  • application
  • rssFeed1
  • startStop
  • timer
  • rssFeedContent
none
Rule 93
  • rssFeedSelect
  • rssFeed1
  • startStop
  • timer
  • rssFeedContent
none
Rule 94
  • rssFeedSelect
  • intervalSelect
  • startStop
none
Rule 95
  • rssFeedSelect
  • intervalSelect
  • startStop
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

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">
  &nbsp;
</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()