Quick multi-site javascript RSS feed

Posted on February 21, 2011

I just finished quickly putting up a simple, compilation site. This was a business was a group of successful blogs, who had recently moved all their emails addresses to be under one header. As a consequence, people were occasionally hitting the email url. At the time the url just redirected to the flagship site, but the owner wanted the site to be a bit more informative without too much effort.

All that was needed was an intro and an RSS feed for each of the sites. I used the Google Feed API and jQuery and it was pretty simple. The one snafu had to do with javascript closures and the Google Feed API callback.

Now that you’ve got the background, let’s get started.

First, set up your html.

<div id="site_id1" class="site">
     <h2>Recent posts</h2>
      <ul class="feed"></ul>
</div>
<div id="site_id2" class="site">
     <h2>Recent posts</h2>
     <ul class="feed"></ul>
</div>
<div id="site_id3" class="site">
      <h2>Recent posts</h2>
       <ul class="feed"></ul>
</div>

I have a div for each site and a ul tag that will contain the RSS feed.

Now grab a Google Feed API key and let’s set up the Google Feed API.

<script type="text/javascript" src="https://www.google.com/jsapi?key=YOUR_API_KEY"></script>
<script type="text/javascript">
    google.load("feeds", "1");

Next, we’ll set up a javascript object which will hold each site’s RSS url, html element id, and the number of entries to show. We’ll also set up an html template for the li elements that each entry will go in. I used jTemplates syntax, because that’s what I’m used to. In other words, “{#title}” will be used as a placeholder for the entries title.

var RMW = {};
RMW.sites = [];
RMW.sites.push({ id : 'site_id1', url : 'http://feeds.feedburner.com/site1?format=xml', num : 5 });
RMW.sites.push({ id : 'site_id2', url: 'http://feeds.feedburner.com/site2?format=xml', num : 5 });
RMW.sites.push({ id : 'site_id3', url: 'http://twitter.com/statuses/user_timeline/site3.rss', num : 2 })
RMW.template = { str : "<li><a href='{#link}'>{#title}</a></li>", link : "{#link}", title : "{#title}" };

Now, let’s get to the nitty gritty. This is where we write the code to grab the feeds and process the results.

function parse_posts(list, result) {
  var tmp = RMW.template;
  var posts = [];
  if (!result.error) {
    for (var i = 0; i < result.feed.entries.length; i++) {
      var entry = result.feed.entries[i];
      posts.push(tmp.str.replace(tmp.link, entry.link).replace(tmp.title, entry.title));
    }
  }
  
  if(posts.length === 0) {
    posts = "<li>Coming soon!</li>"
  }else {
    posts = posts.join(''); //turn it into a string
  }
  list.html(posts);
}

$(document).ready(function() {
      
  var tmp = RMW.template;
  for(var i=0; i<RMW.sites.length; ++i) {
    var site = RMW.sites[i];
    var feed = new google.feeds.Feed(site.url);
    feed.setNumEntries(site.num);
    site.list = $('#' + site.id + ' .feed');
    site.list.empty();
    site.load = function(list) {
      return function(result) { 
        parse_posts(list, result);
      };
     }
    feed.load(site.load(site.list));
  }
});

OK, so what did we just do? Let’s jump to line 31.

First, we looped through the sites in our RMW.sites array:


  for(var i=0; i&lt;RMW.sites.length; ++i) {
    var site = RMW.sites[i];

For each site, we set up the google feed.

 var feed = new google.feeds.Feed(site.url);
    feed.setNumEntries(site.num);

Then we get the html ul element and empty it. (Somewhat pointless in this example.)

    site.list = $('#' + site.id + ' .feed');
    site.list.empty();

Now it might get confusing. What the hell is all this?

    site.load = function(list) {
      return function(result) { 
        parse_posts(list, result);
      };
     }

This is where issues with javascript closures come in. Here’s what’s happening:

  • We set up the google feed using our “site” variable.
  • When we tell google to “load” the site, it goes off into AJAX land and waits for a response.
  • We then move on to the next element, which changes the value of the site variable
  • At some point, google comes back to us with the response and reads the current value of the site variable. This could happen at any time and it will load the feed into the last site’s ul element.

In other words, because the site variable is being reused and is not being evaluated until after google comes back with the RSS content, it will likely not contain the correct site. So then google puts the data into the wrong site’s ul element.

After a bit of trial and error, I came up with this solution. Basically, what I’m doing is putting a closure inside another closure. I’m adding a function to each site object. This function takes the ul element (list) and then returns another function that calls parsePost using that list. The returned function is what gets passed to Google’s load method as a callback.

    site.load = function(list) {
      return function(result) { 
        parse_posts(list, result);
      };
     feed.load(site.load(site.list));
     }

Why does this work? Because the value of list gets evaluated (i.e. Javascript looks at site.list and gets the actual reference now), the returned function contains the correct html element to put the data in. Make sense or does it still feel like Jedi-mind-tricks?

The last thing we do is put the results into html.

function parse_posts(list, result) {
  var tmp = RMW.template;
  var posts = [];
  if (!result.error) {
    for (var i = 0; i &lt; result.feed.entries.length; i++) {
      var entry = result.feed.entries[i];
      posts.push(tmp.str.replace(tmp.link, entry.link).replace(tmp.title, entry.title));
    }
  }
  
  if(posts.length === 0) {
    posts = "&lt;li&gt;Coming soon!&lt;/li&gt;"
  }else {
    posts = posts.join(''); //turn it into a string
  }
  list.html(posts);
}

This is mostly pretty straight-forward. If there wasn’t an error, we loop through the entries and use the html template we set to create the html by replacing our special title and link placeholders with the entry’s title and link.

Now you may be confused by these lines:

var posts = [];
...
posts.push(tmp.str.replace(tmp.link, entry.link).replace(tmp.title, entry.title));
...
posts = posts.join(''); //turn it into a string
...
list.html(posts);

What am I doing here? I am creating a string of html with the entry’s info (title and link in this case). I am pushing that string onto an array (posts). Then, I use join with an empty space delimiter to turn the array into a string (i.e. return a string where each array element is separated by an empty string). Then, I set the list element’s html to that full string.

Why am I doing that and not just using $(”) with the template string and appending that element to the list?
Answer: Because this way is way fucking faster. I’m not sure that matters so much in this particular case, but I’ve gotten in the habit of doing it that way.


1 Reply to "Quick multi-site javascript RSS feed"

  • Andhie
    February 20, 2012 (3:44 am)
    Reply

    Excellent! This is what i’m looking for 6hours googling, thanks Rebecca!


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.