
Parsing RSS is a task that many developers have been faced with. jQuery makes this significantly easier on the client-side, but the good ol’ AJAX is not that bad either if you set things up functionally to minimize on-page code. Two versions of the same RSS Reader with parsing handled with and without the “write less, do more” JavaScript library follow.
DISCLAIMER:
I know I haven’t really captured the spirit of jQuery by using identical functions to straight JavaScript, however, I mainly wanted to show the similarities and differences between using one parser .vs. another.
FOREWARNING:
Thus, I fully accept that a jQuery whiz, which I am not yet, could clearly use the library to write a few lines of a jQuery RSS parser that would easily blow this one away in terms of efficiency and robustness, thus highlighting the advantages over using old-fashioned JavaScript.
JUSTIFICATION:
That’s just not the point of this article, which is merely a quick look at making an AJAX request and parsing XML in jQuery .vs. JavaScript.
Here’s a very basic RSS Reader in jQuery:
/** * parseAtom * Parse Feeds in Atompub (ATOM) feed format * @param feed XML Object */ function parseAtom(feed) { var feed = $(feed).find("feed"); //<feed> var feed_title = $(feed).find("title").first().text(); //<title> var feed_subtitle = $(feed).find("subtitle").first().text(); //<subtitle> var feed_link = $(feed).find("link").first().text(); //<link> var feed_description = $(feed).find("description").first().text(); //<description> var feed_updated = $(feed).find("updated").first().text(); //<updated> var metadata = '<a href="'+feed_link+'">'+feed_title+'</a><br/>' + feed_description + ' | ' + feed_subtitle + '<br/>' + feed_updated + '<div style="clear:both;"> </div>'; //METADATA var news = ''; var entry = 0; $(feed).find("entry").each( function() { //<entry> title = $(this).find("title").text().replace(feed_title,''); //<title> guid = $(this).find("guid").text(); //<guid> id = !empty(guid) ? guid : title.substring(0,12)+'-'+item; //unique ID image = $(this).find("link[type='image/jpeg']").attr("href"); //<image> link = $(this).find("link").text(); //<link> desc = $(this).find("description").text(); //<description> description = desc.replace(/</gi,'<').replace(/>/gi,'>'); if (typeof image==='undefined'||image===null||image==='') { image = 'http://www.canada.com/images/topic/logo_canada.com.gif'; } else { image = 'http://www.canada.com/'+image; } news += '<article><a href="#'+id+'" title="Click once for summary, twice to visit source..."><img class="icon" src="'+image+'" alt="'+title+'" /> '+title+'</a><div id="'+id+'" class="lb"><a href="'+link+'"><img src="'+image+'" alt="'+title+'" /></a><a href="#top" class="close"><img src="close.gif" alt="Close" /></a><div class="story">'+description+'</div></div></article>'; entry++; }); return news; // or metadata+news to show header above news list } /** * parseRSS * Parse Feeds in Really Simple Syndication (or RDF Site Summary) format * @param rss XML Object */ function parseRSS(rss) { var rss = $(rss).find("rss"); //<rss> var rss_channel = $(rss).find("channel"); //<channel> var rss_title = $(rss).find("title").first().text(); //<title> var rss_link = $(rss).find("link").first().text(); //<link> var rss_image = $(rss).find("image").first(); //<image> var rss_image_url = rss_image.find("url").text(); //<url> var rss_image_link = rss_image.find("link").text(); //<link> var rss_image_title = rss_image.find("title").text(); //<title> var rss_description = $(rss).find("description").first().text(); //<description> var rss_language = $(rss).find("language").text(); //<language> var rss_lastBuildDate = $(rss).find("updated").first().text(); //<updated> var rss_copyright = $(rss).find("copyright").text(); //<copyright> var rss_docs = $(rss).find("docs").text(); //<docs> var rss_ttl = $(rss).find("ttl").text(); //<ttl> var metadata = '<a href="'+rss_link+'"><img src="'+rss_image_url+'" title="'+rss_image_title+'": "'+rss_description+'" style="float:left; vertical-align:middle;"/></a><br/>' + rss_lastBuildDate + ' | ' + rss_copyright + '<br/>Following spec at: ' + rss_docs + ' | Cache for: ' + rss_ttl + ' | ' + rss_language + '<div style="clear:both;"> </div>'; //METADATA var news = ''; var item = 0; $(rss).find("item").each( function() { //<item> title = $(this).find("title").text().replace(rss_title,''); //<title> guid = $(this).find("guid").text(); //<guid> id = !empty(guid) ? guid : title.substring(0,12)+'-'+item; //unique ID thumb = $(this).find("media:thumbnail").attr("url"); //<image> image = !empty(thumb) ? thumb : 'rss.png'; link = $(this).find("link").text(); //<link> desc = $(this).find("description").text(); //<description> description = desc.replace(/</gi,'<').replace(/>/gi,'>'); content = $(this).find("content").text(); //<content> news += '<article><a href="#'+id+'" title="Click once for summary, twice to visit source..."><img class="icon" src="'+image+'" alt="'+title+'" /> '+title+'</a><div id="'+id+'" class="lb"><a href="'+link+'"><img src="'+image+'" alt="'+title+'" /></a><a href="#top" class="close"><img src="close.gif" alt="Close" /></a><div class="story">'+description+'</div></div></article>'; }); return news; } function parseFeed(url, type) { contentType = type.toLowerCase(); $.ajax({ url: 'proxy.php?url='+encodeURI(url)+'&path='+getPath(url)+'&f='+contentType+'&e=utf-8', success: function(data) { var news = ''; if (contentType==='atom'||contentType==='feed') { news = parseAtom(data); //use ATOM parser if feed type was set } else { news = parseRSS(data); //use RSS 2.0 parser if feed was not set, or it was a value other than ATOM or FEED (i.e. RSS, mRSS, RSS1.0, RSS2.0, RDF, XML, etc...) } $('#content').html(news); }, error: function(ex) { alert('Error loading feed: '+ex); } }); }
-or-
Here’s the same reader in JavaScript with the AJAX call handled by hand (and thus marginally less cross-browser than the jQuery one, but should work in IE down to 5.5):
/************************************************************/ /* AJAX cross-browser helper utilities */ function loadXMLDoc(docURL) { if (window.XMLHttpRequest) { // FireFox, Opera, Chrome, Safari xhttp = new XMLHttpRequest(); } else { // Internet Explorer xhttp = new ActiveXObject("Microsoft.XMLHTTP"); } xhttp.open("GET",docURL,false); xhttp.send(); return xhttp.responseXML; } function loadXMLString(txt) { if (window.DOMParser) { // FireFox, Opera, Chrome, Safari parser=new DOMParser(); xmlDoc=parser.parseFromString(txt,"text/xml"); } else { // Internet Explorer xmlDoc=new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = "false"; xmlDoc.loadXML(txt); } return xmlDoc; } /************************************************************/ function parseRSS(data) { var rss = data.getElementsByTagName("rss"); //<rss> //Parse RSS Feed channel properties channel = data.getElementsByTagName("channel"); //<channel> title = data.getElementsByTagName("title")[0].childNodes[0].nodeValue; //<title> link = data.getElementsByTagName("link")[0].childNodes[0].nodeValue; //<link> description = data.getElementsByTagName("description")[0].childNodes[0].nodeValue; //<description> image_url = 'rss.png', image_link = '#', image_html=title+' (RSS Feed)'; image = data.getElementsByTagName("image")[0]; //<image> (optional RSS element) if (!empty(image)) { image_url = image.getElementsByTagName("url")[0].childNodes[0].nodeValue; //<url> image_link = image.getElementsByTagName("link")[0].childNodes[0].nodeValue; //<link> image_html = !empty(image_url) ? image_url : '<img src="'+image_url+'" title="'+title+'": "'+description+'" style="float:left; vertical-align:middle;"/>'; } //language = data.getElementsByTagName("language")[0].childNodes[0].nodeValue;//<language> //copyright = data.getElementsByTagName("copyright")[0].childNodes[0].nodeValue; //<copyright> //lastBuildDate = data.getElementsByTagName("lastBuildDate")[0].childNodes[0].nodeValue; //<lastBuildDate> //docs = data.getElementsByTagName("docs")[0].childNodes[0].nodeValue; //<docs> //ttl = data.getElementsByTagName("ttl")[0].childNodes[0].nodeValue; //<ttl> metadata = "<a href=\""+link+"\">"+image_html+"</a>";//"<br/>" + lastBuildDate + " | " + copyright + "<br/>Following spec at: " + docs + " | Cache for: " + ttl + " | " + language + "<div style=\"clear:both;\"> </div>"; //Parse RSS Feed items items = data.getElementsByTagName("item"); //<item> news = "<ol>"; for (i = 0; i < items.length; i++) { title = items[i].getElementsByTagName("title")[0].childNodes[0].nodeValue; //<title> guid = items[i].getElementsByTagName("guid")[0].childNodes[0].nodeValue; //<guid> id = !empty(guid) ? guid : title.substring(0,12)+'-'+item; //unique ID image = 'RSS.png'; try { //pic = items[i].getElementsByTagName("media:thumbnail")[0].getAttribute("url"); //<media:thumbnail> (image) mRSS2.0 only // image = !empty(pic) ? pic : 'RSS.png'; } catch (ex) { } link = items[i].getElementsByTagName("link")[0].childNodes[0].nodeValue; //<link> pubDate = items[i].getElementsByTagName("pubDate")[0].childNodes[0].nodeValue; //<pubDate> desc = items[i].getElementsByTagName("description")[0].childNodes[0].nodeValue; //<description> description = desc.replace(/</gi,'<').replace(/>/gi,'>'); news += '<div class="article"><a href="#lb'+i+'" title="Click once for summary, twice to visit source..."><img class="icon" src="'+image+'" alt="'+title+'" /> '+title+'</a><div id="lb'+i+'" class="lb"><a href="'+link+'"><img src="'+image+'" alt="'+title+'" /></a><a href="#top" class="close"><img src="close.gif" alt="Close" /></a><div class="story">'+description+'</div></div></div>'; } news += "</ol>"; return metadata+news; } function parseAtom(data) { var feed = data.getElementsByTagName("feed"); //<feed> var title = data.getElementsByTagName("title")[0].childNodes[0].nodeValue; //<title> var subtitle = data.getElementsByTagName("subtitle")[0].childNodes[0].nodeValue; //<subtitle> //var link = data.getElementsByTagName("link")[0].childNodes[0].nodeValue; //<link> //var description = data.getElementsByTagName("description")[0].childNodes[0].nodeValue;//<description> var updated = data.getElementsByTagName("updated")[0].childNodes[0].nodeValue; //<updated> var news = '';//METADATA: '<a href="'+link+'">'+title+'</a><br/>' + description + ' | ' + subtitle + '<br/>' + updated + '<div style="clear:both;"> </div>'; var entry = data.getElementsByTagName("entry"); for (var i=0; i<entry.length; i++) { //<entry> title = entry[i].getElementsByTagName("title")[0].childNodes[0].nodeValue; //<title> guid = items[i].getElementsByTagName("guid")[0].childNodes[0].nodeValue; //<guid> id = !empty(guid) ? guid : title.substring(0,12)+'-'+item; //unique ID image = 'RSS.png'; try { pic = entry[i].getElementsByTagName("link")[1].getAttribute("href"); //<image> link[type='image/jpeg'] image = !empty(pic) ? pic : 'RSS.png'; } catch (ex) { } link = entry[i].getElementsByTagName("link")[0].getAttribute("href"); //<link> link[type='text/html'] desc = entry[i].getElementsByTagName("description")[0].childNodes[0].nodeValue; //<description> description = desc.replace(/</gi,'<').replace(/>/gi,'>'); news += '<div class="article"><a href="#lb'+i+'" title="Click once for summary, twice to visit source..."><img class="icon" src="'+image+'" alt="'+title+'" /> '+title+'</a><div id="lb'+i+'" class="lb"><a href="'+link+'"><img src="'+image+'" alt="'+title+'" /></a><a href="#top" class="close"><img src="close.gif" alt="Close" /></a><div class="story">'+description+'</div></div></div>'; } return news; } var FEED_URL = 'http://feeds.wired.com/wired/index'; var type = 'RSS'; var contentType = type.toLowerCase(); var url = 'proxy.php?url='+encodeURI(FEED_URL)+'&f='+contentType+'&e=utf-8'; // xmlDoc = loadXMLString(xml); var xmlDoc = loadXMLDoc(url); var news = ''; if (contentType==='atom'||contentType==='feed') { news = parseAtom(xmlDoc); } else { news = parseRSS(xmlDoc); } document.write(news);
-or-
Conclusion
OK, Admittedly both are equally ugly code but as they say there’s more than one way to skin a cat. This was at least a useful exercise for me to get my head around some of the differences between jQuery and vanilla JavaScript DOM parsing with hand-coded AJAX, for such a task as trivial as displaying an RSS feed’s XML (which clearly got out of hand here).
If I had to do it over again, I would probably see the value of jQuery and design the code with use of the library in mind from the start, doing a better job at using the jQuery style of coding instead of old-school JS function style. The jQuery RSS Reader is more dynamic in that it lets you select from multiple feeds. You could even put in a text box and allow user-powered URL submission, but that’s an entirely different topic.
PETA:
Though I’m quite allergic, I still don’t really approve of skinning cats!
Related articles
- Parsing XML using jQuery (devcurry.com)
- 13 Helpful Mobile Web Design Tools & Resources (thenextweb.com)
- Your choice of cross-browser javascript GUI – Stack Overflow (stackoverflow.com)
- JavaScript & jQuery Modal Dialogs Roundup :MS-Joe (Joe Stagner) (msjoe.com)
- The jQuery Divide:Understand where jQuery ends and JavaScript begins (slideshare.net)
