Example Start

RSS Feed Controls

:

:


:

:

:

Timer Controls

:

:

:


:
 

Example End

Example Description

Type: Best Practice

Simple example of an RSS Feed Reader using a live region widget.

Keyboard Support

No keyboard information

Example Markup

Browser Compatibility

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" role="button">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()