Behavior, Content, Money – 3 Things you should never give away for free!!!

BCmoney MobileTV

RSS Reader in jQuery .vs. JavaScript (AJAX)

Posted by bcmoney on June 1, 2009 in CSS, JavaScript, XML with No Comments


No Gravatar
RSS

Image via Wikipedia

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. Each of the standard JavaScript AJAX RSS Reader and jQuery RSS Reader are at least slightly dynamic in that they let you select from multiple feeds as well as URL-based submission (by passing in a parameter value for the parameter name url). You could even easily put in a text box and allow user-powered URL submission, but that’s an entirely different topic. Lastly, something that’s also important to note though, is that when working with external feeds (not co-located on the same server) either solution requires a Server-Side Proxy script like the one I described in a post last month to run your request on the backend which will fetch and pass on the data, in order to overcome browser security sandboxes (which exist for good reason).

PETA:
Though I’m quite allergic, I still don’t really approve of skinning cats!

 

Leave a Reply

No trackbacks yet.

Posts with similar tags
Posts in similar categories

BC$ = Behavior, Content, Money

The goal of the BC$ project is to raise awareness and make changes with respect to the three pillars of information freedom - Behavior (pursuit of interests and passions), Content (sharing/exchanging ideas in various formats), Money (fairness and accessibility) - bringing to light the fact that:

1. We regularly hand over our browser histories, search histories and daily online activities to companies that want our money, or, to benefit from our use of their services with lucrative ad deals or sales of personal information.

2. We create and/or consume interesting content on their services, but we aren't adequately rewarded for our creative efforts or loyalty.

3. We pay money to be connected online (and possibly also over mobile), yet we lose both time and money by allowing companies to market to us with unsolicited advertisements, irrelevant product offers and unfairly structured service pricing plans.

  • Archives