Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Build an RSS Feed Reader with jQuery and jGFeed

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

This tutorial shows how to use jQuery and the jGFeed plugin to fetch news feeds remotely and write the results into the existing markup. jGFeed is a jQuery plugin that allows you to get 'any' RSS feed from 'any' host and returns a JSON object for easy usage.

This tutorial includes a screencast available to Tuts+ Premium members.

The example code in this tutorial might not work in Internet Explorer 7 or earlier but it has been successfully tested on Firefox, Safari, Chrome and IE8.


Step 1: Organising the Files for the Tutorial

Before we start writing any code let's create a directory where to place the feed reader files. I have created a directory in my Desktop called 'feedreader'. Inside this directory create another one called assets.

We will also need to download a copy of the jGFeed source. You can do so here. For this example I have downloaded the compressed version of the file and placed it in the 'assets' directory.

Finally, let's create some empty text files to hold the markup, javascript and styles. I have created the following file structure:

  
|-- assets |  
|-- feedreader.js |  
|-- jquery.jgfeed-min.js |  
`-- styles.css  
`-- index.html

The file structure should look something like:


Note that index.html, feedreader.js and styles.css should be empty at this stage.


Step 2: The Markup

Our markup for this tutorial is going to be very simple, and we are assuming that you already know some HTML, so we'll introduce it all at once.

In our index.html file we will link to the stylesheet that we will be using later to style the feed reader, then add a list of links to the feeds we want to get and finally add the javascript files needed for the example.

Note that we add the Javascript files at the end of the body. This is to improve the perceived loading speed of your pages, as the browser can start displaying output without having to wait for the scripts to be loaded.

Add the following markup to index.html:

 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 
 
 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>jQuery + jGFeed AJAX Feed Reader Example</title> <link rel="stylesheet" href="assets/styles.css" 
type="text/css" /> </head> <body> 
 
 <h1>jQuery + jGFeed AJAX Feed Reader Example</h1> 
 
 <ul class="menu"> <li> <a class="ajax-feed-trigger" href="http://www.lupomontero.com/feed"> .lupomontero </a> </li> <li> <a class="ajax-feed-trigger" 
href="http://feeds.feedburner.com/nettuts"> Nettuts+ </a> </li> <li> <a class="ajax-feed-trigger" href="http://feeds.feedburner.com/jquery"> jQuery </a> </li> <li> <a class="ajax-feed-trigger" 
href="http://feeds2.feedburner.com/LearningJquery"> Learning jQuery </a> </li> </ul> 
 
 <div id="ajax-feed-wrapper"> <div id="ajax-feed-container"> Click links above to fetch feeds </div> </div> 
 
 <!-- Load jQuery, jGFeed and feedreader scripts --> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"> </script> <script type="text/javascript" src="assets/jquery.jgfeed-min.js"></script> <script type="text/javascript" src="assets/feedreader.js"></script> 
 
 </body> </html>

Now when we open this file in the web browser we should see the following page containing the initial elements for our feed reader.


Note that this markup would be perfectlty functional for browsers not running Javascript, in the sense that the page would still offer valid links to the feeds, but they would obviously take the browser to a new location instead of displaying the feeds within our reader.


Full Screencast



Step 3: Override the Links Default Onclick Event

So far, when we click on a link our browser is being directed to a new location, the location specified in the link's 'href' attribute. This is the normal behaviour of links, but in this case we want to prevent the browser from going anywhere and instead fetch the URL asynchronously, process the response and print the data within the existing markup, meaning that we do not have to leave the site and the news feeds are presented the way we want.

Open the empty file we saved as assets/feedreader.js (this is where we will be writing our script) and add the following javascript:

 $(document).ready(function() { // Hook to click event for ajax-feed-trigger links $('a.ajax-feed-trigger').click(function(e) { // Prevent default click action e.preventDefault(); 
 
 // Store reference to container object in local var var container = $('#ajax-feed-container'); 
 
 // Empty container div and temporarily add "loading" style container.empty().addClass('loading'); 
 
 // Get links href attribute var href = $(this).attr('href'); alert(href); 
 
 // Get feed using jGFeed 
 
 }); // End a.ajax-feed-trigger click event });

The code above overrides the default behaviour of the links onclick event using jQuery's click method on all 'a' tags of class 'ajax-feed trigger'. Inside the click method the first thing we do is prevent the default behaviour using javascript's preventDefault(), then we empty the container div, add the loading class that will allow us to style the loading 'state' and for now we just show the value of the href attribute of the clicked link. We will use a call to alert() to check that our code works so far.

Back in the browser, when we click on the links we should see an alert dialog with the URL of the clicked link.



Step 4: Getting the News Feed as a JSON Object Using jGFeed

The next step will be to send the actual HTTP request with jGFeed and checking that we got the response we expected.

The jGFeed plugin takes the following arguments:

  • url - URL of the feed that you want to load
  • callback - callback function to call after RSS feed is loaded
  • num (optional) - number of blog entries to load (defaults to 3)
  • key (optional) - Google API key to use while loading RSS feeds.

More info about jGFeed:
http://jquery-howto.blogspot.com/2009/05/google-feeds-api-jquery-plugin.html

In this example we will pass the value of the link's href attribute as the url, an anonymous callback function and we will specify that we want to limit the results to 5.

In assets/feedreader.js add the call to jGFeed so that the script should now look like the listing below:

 $(document).ready(function() { // Hook to click event for ajax-feed-trigger links $('a.ajax-feed-trigger').click(function(e) { // Prevent 
default click action e.preventDefault(); 
 
 // Store reference to container object in local var var container = $('#ajax-feed-container'); 
 
 // Empty container div and temporarily add "loading" style container.empty().addClass('loading'); 
 
 // Get links href attribute var href = $(this).attr('href'); 
 
 // Get feed using jGFeed $.jGFeed( href, function(feeds) { // Check for errors if (!feeds) { // there was an error container.append('Error fetching feed.'); return false; } }, 5 ); // End jGFeed }); // End a.ajax-feed-trigger click event });

We can now use Firebug to see what jGFeed is doing. Click on any of the links, open the 'Net' tab in Firebug and locate the JSON request. It should say something like 'GET load?v1.0&callback=jsonp12'. If you expand the view you should be able to see the JSON object and the data inside it.



Step 5: Building the Markup for Each Feed Entry Dynamically

Now that we know that we are getting the data we can proceed to do something with it. In this case we want to iterate through the entries in the feed and append a string with HTML markup to the existing container we created in our index.html file.

However, before iterating through the entries we need to prepare the container. We hide the div to make sure that we don't see anything until we have finished manipulating the contents of the container tag. We also remove the loading class as now the div is hidden and append the feed title in a heading tag.

We use a for loop to iterate over the entries property of the feeds object that is returned by jGFeed to our callback function. Within the loop we simply build a string with some HTML markup to display the properties of each entry. At the end of each iteration we append this HTML string to the feed container.

Once we have finished populating the container div with all the entries we call JQuery's show() method to fade in the div and finally make it visible again.

 $(document).ready(function() { // Hook to click event for ajax-feed-trigger links $('a.ajax-feed-trigger').click(function(e) { // Prevent default click action e.preventDefault(); 
 
 // Store reference to container object in local var var container = $('#ajax-feed-container'); 
 
 // Empty container div and temporarily add "loading" style container.empty().addClass('loading'); 
 
 // Get links href attribute var href = $(this).attr('href'); 
 
 // Get feed using jGFeed $.jGFeed( href, function(feeds) { // Check for errors if (!feeds) { // there was an error container.append('Error fetching feed.'); return false; } 
 
 container.hide(); container.removeClass('loading'); container.append('<h2>' + feeds.title + '</h2>'); 
 
 // Process feed entries for (var i=0; i<feeds.entries.length; i++) { var entry = feeds.entries[i]; 
 
 // Build HTML string for entry var html = '<div class="ajax-feed-item">'; html += '<hr /><h2><a href="' + entry.link + '">'; html += entry.title + '</a></h2>'; html += '<div 
class="ajax-feed-date">'; html += entry.publishedDate + '</div>'; html += '<div class="ajax-feed-author"> Posted by '; html += entry.author + '</div>'; html += '<div 
class="ajax-feed-content-snippet">'; html += entry.contentSnippet + '</div>'; html += '</div>'; 
 
 container.append(html); } 
 
 container.show('slow'); }, 5 ); // End jGFeed }); // End a.ajax-feed-trigger click event });

If you click on any of the links now you should see the news feeds as shown in the screenshot below.



Step 6: Adding a 'Read More' Link to Display Full Content of Feed Entries

Ok, the example seems to be working pretty well, but there is still a lot of tweaking we could do. In this case we are going to add a 'read more' type of link that will display the full contents of the entry. Until now we have only used the contentSnippet property in each entry object. This is useful because we want to display the entries in a compact list, but we also have the full content in another property called 'content', so we are going to add a read more (+/-) link that will toggle between the snippet and the full content so that we can read the whole feed entry without leaving our page.

To do this, we first need to add the content to the HTML string we were building inside the loop, but we are going to set the height of the content divs to zero and their overflow to 'hidden' in order to hide them as we are only going to use them to hold the actual data. Our link will simply swap the contents of the 'visible' snippet div with the 'invisible' content div.

Just before we show the feed at the end of the callback function we passed to jGFeed, we will also need to add the onclick event handler for our 'read more' links. We do this the same way we did with the links we are using to fetch the feed, using jQuery's click() method.

 $(document).ready(function() { // Hook to click event for ajax-feed-trigger links $('a.ajax-feed-trigger').click(function(e) { // Prevent default click action e.preventDefault(); 
 
 // Store reference to container object in local var var container = $('#ajax-feed-container'); 
 
 // Empty container div and temporarily add "loading" style container.empty().addClass('loading'); 
 
 // Get links href attribute var href = $(this).attr('href'); 
 
 // Get feed using jGFeed $.jGFeed( href, function(feeds) { // Check for errors if (!feeds) { // there was an error container.append('Error fetching feed.'); return false; } 
 
 container.hide(); container.removeClass('loading'); container.append('<h2>' + feeds.title + '</h2>'); 
 
 // Process feed entries for (var i=0; i<feeds.entries.length; i++) { var entry = feeds.entries[i]; 
 
 // Build HTML string for entry var html = '<div class="ajax-feed-item">'; html += '<hr /><h2><a href="' + entry.link + '">'; html += entry.title + '</a></h2>'; html += '<div class="ajax-feed-date">'; html += entry.publishedDate + '</div>'; html += '<div class="ajax-feed-author"> Posted by '; html += entry.author + '</div>'; html += '<div class="ajax-feed-content-snippet">'; html += entry.contentSnippet + '</div>'; html += '<div id="ajax-feed-content-'+i; html += '" class="ajax-feed-content" '; html += 'style="height:0px; overflow:hidden;">'; html += entry.content + '</div>'; html += '<div><a class="ajax-feed-readmore" '; html += 'href="' + i + '">+</a></div>'; html += '</div>'; 
 
 container.append(html); } 
 
 // Hook to click event for ajax-feed-trigger links $('a.ajax-feed-readmore').click(function(e) { // Prevent default click action e.preventDefault(); 
 
 var content_id = $(this).attr('href'); var div_id = 'ajax-feed-content-' + content_id; var content_div = $('#' + div_id); var content_txt = content_div.html(); var snippet_div = 
content_div.prev(); var snippet_txt = snippet_div.html(); 
 
 // Swap text content between divs content_div.html(snippet_txt); snippet_div.html(content_txt); 
 
 if ($(this).html() === '-') { $(this).html('+'); } else { $(this).html('-'); } }); 
 
 container.show('slow'); }, 5 ); // End jGFeed }); // End a.ajax-feed-trigger click event });

The reader should now display a '+' and '-' button that toggles between the content snippet and the full contents for each entry.



Step 7: Styling the Reader

The feed reader now works... hurray! But let's be honest, it doesn't look very good. So let's put the icing on the cake. Below I have included some styles as an example of how the reader could be skinned. You may have noticed that we used classes for each of the elements in our markup, so it should be relatively easy to refer to each element usin CSS selectors in the same way that we selected them with jQuery.

 /* Colours: red: #C74C44 dark red: #8C413A grey: #3F474C dark beige: #B5B095 beige: #DBD5B6 */ 
 
 html { color: #F0F0F0; background-color: #8C413A; font-family: "Helvetica", sans-serif; } 
 
 body { font-size: 12px; margin: 10px 20% 30px 20%; } 
 
 div, ul, li { margin: 0; padding: 0; } 
 
 img { border: 0; } 
 
 li { list-style: none; } 
 
 h1, h2, h3 { font-size: 100%; font-weight: normal; } 
 
 h1 { color: #DBD5B6; font-size: 4.6em; line-height: 1.1em; text-shadow: 1px 1px 0.3em #3F474C; background-image: url(logo.png); background-repeat: no-repeat; padding: 16px 0 0 148px; margin: 0px; height: 114px; } 
 
 h2 { color: #DBD5B6; font-size: 2.6em; text-shadow: 1px 1px #3F474C; padding: 0; margin: 0 0 25px 0; } 
 
 a { color: #DBD5B6; text-decoration: none; } 
 
 ul.menu { margin: 44px 0px 24px 0px; padding: 0px; } ul.menu li { display: inline; margin: 0px 5px 0px 0px; padding: 12px; background-color: #3F474C; border: 1px solid #B5B095; 
-moz-border-radius: 5px; -webkit-border-radius: 5px; font-size: 1.2em; text-shadow: 1px 1px #3F474C; } ul.menu li:hover { background-color: #B5B095; text-shadow: 1px 1px 0.2em #3F474C; } 
 
 div#ajax-feed-wrapper { padding: 20px; overflow: hidden; background-color: #C74C44; border: 1px solid #B5B095; -moz-border-radius: 5px; -webkit-border-radius: 5px; } 
 
 .loading { width: 36px; height: 36px; background-image: url(ajax-loader.gif); background-repeat: no-repeat; background-position: 20px 20px; } 
 
 div.ajax-feed-item { background-color: #8C413A; padding: 10px; margin-bottom: 10px; } 
 
 div.ajax-feed-item h3 { font-size: 1.8em; text-shadow: 1px 1px 0.3em #3F474C; padding: 0; margin: 3px 0 15px 0; } 
 
 div.ajax-feed-date, div.ajax-feed-author { font-size: 0.8em; } 
 
 div.ajax-feed-content-snippet { margin: 3px 0px 10px 0px; padding: 15px 5px 5px 5px; border-top: 1px solid #C74C44; text-shadow: 1px 1px #3F474C; } 
 
 a.ajax-feed-readmore { display: table-cell; padding: 1px 5px; border: 1px solid #DBD5B6; } 
 
 a.ajax-feed-readmore:hover { background-color: #B5B095; }

Voila! That already looks a lot better ;-)


Advertisement