Making a Sleek Feed Widget With YQL, jQuery & CSS3
You will be surprised at how much data is made available on the web through RSS or ATOM feeds - twitter searches, your latest diggs, Google Search alerts, your own blog categories and so much more. You just have to look for that orange icon and you'll surely find a lot more precious data, just waiting to be put into use.
Today we are making a sleek feed widget, that will fetch any feed and display it in your blog sidebar. You can set it up to show the latest posts from the different categories of your blog, your latest stumbles, or even people mentioning you on twitter.
So go ahead, download the demo archive from the button above, and keep on reading..
Problem Solving
Before stepping into development, we have to clarify for ourselves what we are aiming for, discuss some potential problems, and their solutions.
Problem 1 - Fetching Feeds
The widget is entirely front-end based, so we have to find a way to fetch the feeds directly with JavaScript. AJAX is a great technology, but there are security restrictions that limit it to fetching data only from the current domain. This means we cannot access feeds directly and display them.
This is where YQL comes along. It fetches the feed we want, and makes it available to our script as a regular JSON object that we can later loop and print to the page.
YQL is a free API service from Yahoo, with which you can do much more than just download feeds. It acts as a gateway between you and other API's and lets you manipulate them with a quick to learn SQL-like syntax (hence the name).
You can reed more about it in this tutorial on nettuts, or go to the official YQL page.
Setting up YQL to work is tricky though (we have to dynamically include a <script> tag to the head section of the page, like we did in the Twitter Ticker tutorial few months back). Luckily, jQuery provides a method for just this purpose - getJSON. It does everything behind the scenes, so we don't have to worry about the implementation.
Problem 2 - Different Feed Formats
As with everything else, feeds are available in a number of formats - RSS1, RSS2 and ATOM. They all have their differences and present a challenge, because our code has to be able to loop through the results returned by YQL and successfully display the entries.
The solution to this is to move the functionality that displays the feeds in a separate function and use a number of logical OR-s ( || ) throughout the code. It works with all the feeds that I tested it with, but you could easily make your own version of the function for special cases (for example displaying Flickr streams with a thumbnail).
Problem 3 - Insufficient Space
This is more of a layout problem actually, but is quite an important one. Given the limited width of the blog sidebar area, it becomes evident that it is impossible to display more than a couple of tabs simultaneously, if we go with the regular horizontal placement. So the best option is to have them show in a sleek drop down, which can store all the feeds one could possibly want.
With those issues addressed, we can now move on to development.
Step 1 - XHTML
The first part of the tutorial consists of laying down the XHTML structure for the feed widget. The snippet below (extracted from demo.html in the download archive) is everything you need to show the widget on your page (apart from the CSS and jQuery files, covered in the later steps).
demo.html
<div id="feedWidget"> <div id="activeTab"> <!-- The name of the current tab is inserted here --> </div> <div class="line"></div> <div id="tabContent"> <!-- The feed items are inserted here --> </div> </div>
As the widget is entirely dependent on JavaScript to function, there is little point in providing a fallback solution. The best we can do is to entirely hide it from view if JS is disabled. This is why the feedWidget div is hidden with display:none in the stylesheet file, and shown with jQuery's show() method in script.js (which will be run only if JS is available).
Now lets move to the next step.
Step 2 - CSS
The styling of the widget is defined in styles.css. Only the styles that are directly used by the widget are included here. You can view the rest of the CSS declarations that define the looks of the page itself in that file.
styles.css - Part 1
#feedWidget{ background:url(img/bg.png) repeat-x #47525c; border:2px solid #48535e; margin:0 auto; width:200px; padding:5px; position:relative; /* Remains hidden if JS is not enabled: */ display:none; z-index:20; } #activeTab.hover,.dropDownList{ background:url(img/drop_arrow.png) no-repeat 95% 50% #47525c; border:1px solid #38434d; margin:-1px; cursor:pointer; /* CSS3 round corners: */ -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; } #activeTab,.dropDownList div{ color:white; cursor:pointer; font-size:20px; margin:0 2px 0 0; padding:5px; text-shadow:0 1px 1px black; } .line{ height:1px; overflow:hidden; background-color:#2b353d; border-bottom:1px solid #687581; margin:10px 0; }
Notice that we define a special hover class for the #activeTab div, instead of the regular :hover pseudo-class. This is because the hover style should only be applied if there is more than one tab to be shown, which is impossible to determine with CSS alone. This is why we apply it with JS.
The dropDownList shares a number of properties with the hover class of the #activeTab div. The most effective way to write the CSS is to group those two together, and later individually apply only those rules that differ, as you can see in the snippet below:
styles.css - Part 2
.dropDownList{ background-image:none; position:absolute; border-top:none; padding:5px; /* We reset the roundness of the top corners, inherited by a previous rule: */ -moz-border-radius-topleft: 0; -moz-border-radius-topright: 0; -webkit-border-top-left-radius: 0; -webkit-border-top-right-radius: 0; border-top-left-radius: 0; border-top-right-radius: 0; } .dropDownList div:hover{ background-color:#505e6b; } #tabContent div{ /* The feed entry divs */ background-color:#EEEEEE; color:#555555; font-size:10px; margin-bottom:10px; padding:5px; position:relative; -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; /* CSS3 box shadow: */ -moz-box-shadow:0 1px 1px black; -webkit-box-shadow:0 1px 1px black; box-shadow:0 1px 1px black; }
We apply a number of CSS3 rules here: border-radius for pure CSS rounded corners and box-shadow for adding a drop shadow below the feed items. They are provided with the -moz- and -webkit- vendor prefixes, because the regular version is not yet supported in any browser (but we supply it as well for future-proofing).
Step 3 - jQuery
After including the jQuery library into the page, it is now possible to leverage the methods it provides and build some complex interactions that would otherwise be impossible (or at least would take too much development resources). The JavaScript code is located in scripts.js in the demo files.
script.js - Part 1
/* Configuration: */ var tabs = { "@tutorialzine" : { "feed" : "http://twitter.com/statuses/user_timeline/67315866.rss", "function" : twitter }, "Latest Tutorials": { "feed" : "http://feeds.feedburner.com/Tutorialzine", "function" : rss }, "Smashing Mag": { "feed" : "http://rss1.smashingmagazine.com/feed/", "function" : rss }, "Script & Style" : { "feed" : "http://feeds2.feedburner.com/ScriptAndStyle", "function" : rss } } $(document).ready(function(){ /* This code is executed after the DOM has been completely loaded */ /* Counting the tabs: */ var totalTabs=0; $.each(tabs,function(){totalTabs++;}) $('#feedWidget').show().mouseleave(function(){ /* If the cursor left the widet, hide the drop down list: */ $('.dropDownList').remove(); $('#activeTab').removeClass('hover'); }).mouseenter(function(){ if(totalTabs>1) $('#activeTab').addClass('hover'); }); $('#activeTab').click(showDropDown); /* Using the live method to bind an event, because the .dropDownList does not exist yet: */ $('.dropDownList div').live('click',function(){ /* Calling the showDropDown function, when the drop down is already shown, will hide it: */ showDropDown(); showTab($(this).text()); }); /* Showing one of the tabs on load: */ showTab('@tutorialzine'); });
Notice the tabs object. It contains the declarations of the different feeds we want to use, along with a function that handles the output of those feeds to the page. The name of the property (before the colon) is inserted as a tab name, and when passed to the showTab() function, shows the contents of this feed inside the widget. This is how we load the 'strong>@tutorialzine' tweets on page load.
script.js - Part 2
function showTab(key) { var obj = tabs[key]; if(!obj) return false; var stage = $('#tabContent'); /* Forming the query: */ var query = "select * from feed where url='"+obj.feed+"' LIMIT 5"; /* Forming the URL to YQL: */ var url = "http://query.yahooapis.com/v1/public/yql?q="+encodeURIComponent(query)+"&format=json&callback=?"; $.getJSON(url,function(data){ stage.empty(); /* item exists in RSS and entry in ATOM feeds: */ $.each(data.query.results.item || data.query.results.entry,function(){ try{ /* Trying to call the user provided function, "this" the rest of the feed data: */ stage.append(obj['function'](this)); } catch(e){ /* Notifying users if there are any problems with their handler functions: */ var f_name =obj['function'].toString().match(/function\s+(\w+)\(/i); if(f_name) f_name = f_name[1]; stage.append('<div>There is a problem with your '+f_name+ ' function</div>'); return false; } }) }); $('#activeTab').text(key); } function showDropDown() { if(totalTabs<2) return false; if($('#feedWidget .dropDownList').length) { /* If the drop down is already shown, hide it: */ $('.dropDownList').slideUp('fast',function(){ $(this).remove(); }) return false; } var activeTab = $('#activeTab'); var offsetTop = (activeTab.offset().top - $('#feedWidget').offset().top )+activeTab.outerHeight() - 5; /* Creating the drop down div on the fly: */ var dropDown = $('<div>').addClass('dropDownList').css({ 'top' : offsetTop, 'width' : activeTab.width() }).hide().appendTo('#feedWidget') $.each(tabs,function(j){ /* Populating the div with the tabs that are not currently shown: */ if(j==activeTab.text()) return true; $('<div>').text(j).appendTo(dropDown); }) dropDown.slideDown('fast'); }
The showTab function takes a tab name as a parameter and displays it in the widget, after forming the corresponding YQL URL, and fetching it with the getJSON() method. After this, the response is looped with $.each and the function that was provided in the tab definition is called.
You can additionally switch the active tab from outside the widget code, by calling showTab() with a different tab name (and thus creating custom controls for the widget).
script.js - Part 3
function twitter(item) { /* Formats the tweets, by turning hashtags, mentions an URLS into proper hyperlinks: */ return $('<div>').html( formatString(item.description || item.title)+ ' <a href="'+(item.link || item.origLink)+'" target="_blank">[tweet]</a>' ); } function rss(item) { return $('<div>').html( formatString(item.title.content || item.title)+ ' <a href="'+(item.origLink || item.link[0].href || item.link)+'" target="_blank">[read]</a>' ); } function formatString(str) { /* This function was taken from our Twitter Ticker tutorial - https://blog.daitra.xyz/2009/10/jquery-twitter-ticker/ */ str = str.replace(/<[^>]+>/ig,''); str=' '+str; str = str.replace(/((ftp|https?):\/\/([-\w\.]+)+(:\d+)?(\/([\w/_\.]*(\?\S+)?)?)?)/gm,'<a href="$1" target="_blank">$1</a>'); str = str.replace(/([^\w])\@([\w\-]+)/gm,'$1@<a href="http://twitter.com/$2" target="_blank">$2</a>'); str = str.replace(/([^\w])\#([\w\-]+)/gm,'$1<a href="http://twitter.com/search?q=%23$2" target="_blank">#$2</a>'); return str; }
In the last part of the code, we have the two functions - twitter and rss. These take an object passed from the $.each loop in showTab() and find their way to the link and title of the feed item, depending on whether it is RSS or ATOM.
You can create your own functions and include them in the tabs object. This way you can expand the functionality for feeds that are not limited to text. It is only important that you return the results as a '<div>....</div>' string.
With this our Sleek Feed Widget is complete!
To Wrap It Up
You are free to use and build upon the widget any way you see fit. The code is easy to modify and you can quickly implement all sorts of functionality.
If you liked this tutorial, be sure to follow us on twitter for the latest and greatest web dev resources on the web.
What do you think? How would you modify this code?
Bootstrap Studio
The revolutionary web design tool for creating responsive websites and apps.
Learn more
Great tutorial, man!
Thanks a lot!
Awesome Tutorial, Martin)))
Great thanks)))))
Awesome work Martin Thanks a ton
Wow, that's pretty slick. It would be nice to have one shown by default too.
Great Man, thumbs up. Keep going :)
awesome! coding at its best! Thanks!
Wow that's really cool, I'm going to use this!
Great Tuto.
I share this tuto for my friends :)
Continue to make excellents tutos!
I like it. The only problem I have with these types of solutions is those people with Javascript turned off (a number of corporations I or friends have worked for/with have it disabled by default for most users) but that all depends on the sites Audience.
Thank you for the comments fellas!
@ Greg
Yes, it is unfortunate that some visitors would be unable to see it. This is why you should consider JavaScript-only features only as enhancements to your site, and make sure your core functionality is available to the widest possible audience.
great work, great.
Loved this. I'll see if I can get it to display Buzz Feeds. BTW, would you mind if tried to create a WP Plugin out of this?
You can edit my comments into a solo comment if you wish.
I gave it a try using Google Buzz Feeds. Unfortunately it didn't work very well. I only got the title of the buzz, instead of a content preview. It showed like this:
Buzz by Luciano Santa Brígida from SB Virtual [read]
Buzz by Luciano Santa Brígida from Google Reader [read]
Buzz by Luciano Santa Brígida from Buzz [read]
Forcing me to click on read to [read] to acctually see its content.
Is there anyone to get the content instead of the title of the feed items?
@ Luciano
The default RSS handling function displays the title by default. Unfortunately Google Buzz displays the generic text you posted in your comment, instead of the real title of the entry.
I added a new function - "buzz" to the demo, that can handle buzz feeds (you'll have to re-download the zip archive).
Users would love it as a WordPress plug-in. Just be sure to share the link when it is done : )
Great stuff, thanks for your effort. I want to use two tabs instead of drop menu. Could you provide the code for the same.
@ Stephen
Unfortunately it is not possible to show tabs instead of the drop-down menu without changing almost all of the code (not to mention that the text of the tabs would become unreadable).
it's great widget you maked there !
I got a question, it's only for news feeds from API and XML or it's possible to add, for example, some code from a wordpress plugin ?
In other words, i would to use to put on my sidebar and show different contents, like a categories menu and some that i got to put informations from a wp plugin.
@ Laurent
The most elegant way to do this would be to setup a dummy RSS feed, which contains the data you want it to show (just a text document formatted as a valid RSS feed).
You can use more than one dummy feed to show a list of categories and the data from the plug-in.
You will not have to do any particular modifications to the code of the widget (apart from adding the feeds to the tabs object).
hi there, i've added this to a page on which there is another jQuery script for a navigation menu and a mootools script for a thumbnail effect. When i had just the menu and thumbnail scripts, i had a conflict which i resolved using jQuery.noConflict but when i brought your bit of code in, i got the same conflict again, which is odd because the code itself is in a different file as a script, there's nothing on the actual page. Also, what happens is that if i call the latest JQuery library over the web, your feedreader doesn't work, it wants both that, and the library you've supplied in your files here...
can you explain to me please why that happens?
Thanks for a great widget, it's saved me a serious amount of time.
There is a bug with the Buzz function though, as does not work with most Buzz feeds. Generally item.content[0].content does not exist, as item.content usually isn't an array (it's only an array in the case of additional content such as media being included in the post).
I'm sure there is a more elegant way to solve this, but this works:
This is probably one of the coolest tutorials ever!!! Thanks so much for your hard work and for sharing your knowledge with me, a total noob!!!
It's pretty ehm... sorry I don't have the correct words. But you may know that someone stole your work just to sell it as a Joomla Extension:
http://extensions.joomla.org/extensions/external-contents/news-a-feeds/rss-readers/11453
Just yesterday I saw this very useful tutorial and today I see this. It's pretty sad :(
Good work and keep on it!
PS: Can someone tell me a way to insert it into a page with a single JS so I can let users insert in their webpage but files are in my server but showing my info. I found a rude way but it is not autosizable as this it :(
How would I edit it in order to have 4 of those RSS readers side by side, each with different content?
Thoughts?
What an awesome little widget! I'm using this in a new design right away! Thanks a million!
But I'm having problems having it display in IE.
I assume this is a problem with the CSS, I'm using it in a left hand side column which is in a left floated div. It displays perfectly in Mozilla etc, however IE it doesn't seem to appear at all. I've tried floating the feed widget div but this messes it up. I've tried setting the feed widget div to display:block and it doesn't affect any of the other browser but does show a squashed version of it in IE.
Any ideas where I'm going wrong?
Many thanks.
So I tried the demo -- worked great! I added some sites and they all gave errors in the rss function on IE, but worked fine on Firefox. An example is
http://hackaday.com/feed/
What might be the cause of this and how can I fix it?
John, can you share a link to the widget?
I did it directly on my PC (downloaded the demo from the link above, added my sites, and then pointed the browsers to the demo page) and embedded it in a page. I got the same results on my website: www.jkintl.com.
I found the problem:
item.link[0].href
throws an exception for some sites. My question is now "can I safely comment that out"?
So I put it in a try where the version in the catch had that part removed. Seems to work.
This is awesome! Great Tut!
Great tutorial! great work!
How do I need to modify the javascript so could I have the title of the post act as a link and how could I include a few word of the actual post article below each title in the feed?
Is there anyway to include a little thumbnail pic to if used?
Hi! When I added this to my site and put in my Twitter RSS Feed URL, I just get this error in the JS console and nothing shows up:
Uncaught TypeError: Cannot read property 'item' of null
Here's the Feed URL: http://twitter.com/statuses/friends_timeline/193512563.rss
When you go to it, it requires a password. On your RSS feed in the sample code, it doesn't require a password. How would I make it not require a password for the RSS feed?
Thanks for any help!
Does anyone know how to edit this widget so the title text of the posts are links to the article rather than having the [read] being the link to the article?
Thank you very much , i love it , can you make this widget to save titles from another website ?
Just want to say this tutorial is awesome. I really appreciate it.
Thanks for the great tutorial!
In script.js - Part 1, line 29 (var totalTabs=0;) needs to be moved outside the function. ie Move Line 29 to line 24.
Thanks.
Great tutorial.
Quick question. Is there a way to write the code (eg, documentwrite) to the page, so it shows up in the source.
The only reason I ask is that I want to add some interactivity to each post, like change background on hover etc etc.
At the moment I don't seem to be able to target each element, javascript isn't picking it up.
Does anyone know if there is a way to do this without having to display the html differently?
Let me know if it doesn't make sense.
Thanks
this is awesome. I was dying for this. thanks!!
You're a very talented Developer, Martin!! THX for sharing!!
Nice ! That works great :)
Thank you for sharing this code ^^