Example: RSS Panel

This script detect the RSS in web page and display RSS in the page. Sites like Slashdot or Delicious has such embeded RSS in their home page. For example, Slashdot has code,

In this script, PRO_xmlhttpRequest is used instead of traditional ActiveXObject("Msxml2.XMLHTTP"), because of RSS retrieve might be cross domain.

<link rel="alternate" title="Slashdot RSS" href="http://rss.slashdot.org/Slashdot/slashdot" type="application/rss+xml">

/*
 RSS Reader for GreaseMonkey http://greasemonkey.mozdev.org/
 works with RSS versions 0.91 .. 2.0
*/

// ==UserScript==
// @name		  RSS Panel
// @namespace	 http://www.xs4all.nl/~jlpoutre/BoT/Javascript/RSSpanel
// @description   Displays RSS directly from originating website
// @include	   *
// @exclude	   
// ==/UserScript==

//
// ported from http://userscripts.org/scripts/show/6073
//

(function(){
 // GM "global" scope variables
 RSSPanelVersion = "2.01";
 gReq            = null; // "native" XHR object
 gRss_src        = '';   // RSS feed source

 /* ************************** bel ***************************** 
  *   COLOR & OPACITY Constants for the RSS Reader.
  **************************************************************/
 BACKGROUND       = "#ffc";
 TEXT             = "#000"; 
 BORDER           = "orange"; 
 TITLE_BACKGROUND = "orange";
 TITLE_BORDER     = "#ffc";
 TITLE_TEXT       = "#fff";
 OPACITY          = "0.85";

 function dbg(msg, lvl) {
 PRO_log(msg);
 }
 // initialize XHR object
 function rss_init() {
	 // read rss if a feed is discovered (gRss_src is not null)
	 gRss_src = rss_discover();
	 rss_req(gRss_src);
 }

 function rss_discover() {
	 var src = '';
	 var rss = window.document.getElementsByTagName("link");
	 for (var i=0; i<rss.length; i++) {
		 var type = rss[i].getAttribute("type");
		 if (type && type.toLowerCase() == "application/rss+xml") { 
			 src = rss[i].getAttribute("href");
		 }
		 if (src) break;
	 }
	 if (! src) return '';
	 // Slashdot link looks like HREF="//slashdot.org/index.rss"
	 if (src.indexOf('//') == 0) {
		 src = 'http:' + src;
 	}
 	// absolute path, missing host name
 	if (src.indexOf('/') == 0) {
		 src = window.location.protocol + '//' + window.location.host + src;
	 }
	 // GM_XMLHttpRequest needs fully qualidied URL as of 0.6.4
	 if (src.indexOf('http') != 0) {
		 var base = window.location.href;
		 base = base.substring(0, base.lastIndexOf('/') + 1);
		 src = base + src;
	 }
 	// dbg("src=" + src);
 	return src;
 }
 function rss_req(src) {
	 if (! src) return;
	 // http://diveintogreasemonkey.org/api/gm_xmlhttprequest.html		
	 if (typeof(GM_xmlhttpRequest) == 'function') {
		 // dbg('XHR: GM');
		 GM_xmlhttpRequest(
				 method: 'GET', 
				 url: src,
				 headers: {
					 'User-agent': 'Mozilla/5.0 (compatible) GM RSS Panel'
				 },
				onload: rss_response_wrapper
		});
	} else {
		// handle feed through "native" XHR object
		/*
		if (window.XMLHttpRequest) {
			try {
				gReq = new window.XMLHttpRequest();
			} catch(e) {
				dbg((e.message) ? e.message : e.toString());
				gReq = false;
			}
			// branch for IE/Windows ActiveX version
		} else if (window.ActiveXObject) {
			try {
				gReq = new ActiveXObject("Msxml2.XMLHTTP");
			} catch(e) {
				dbg("Msxml2.XMLHTTP: "+(e.message) ? e.message : e.toString());

				try {
					gReq = new ActiveXObject("Microsoft.XMLHTTP");

				} catch(e1) {
					dbg("Microsoft.XMLHTTP: "+(e.message) ? e.message : e.toString());
					gReq = false;
				}
			}
		}
		*/
		gReq = PRO_xmlhttpRequest();

	}
	 if (gReq) {
		 try {
			 gReq.onreadystatechange = rss_response;
			 gReq.open("GET", src, true);
			 gReq.send(null);
		 } catch(e) {
			 dbg((e.message) ? e.message : e.toString());
		 }
	 }
}
function rss_response_wrapper(res) {
	gReq = res;
	rss_response();
}
function rss_response() {
	//dbg('XHR status: ' + gReq.status);
	// only if req is "loaded"
	if (gReq.readyState == 4) {
		// only if "OK"
		if (gReq.status == 200) {
			// handle result
			// rss_render(gReq.responseXML);
			// responseXML is not available with GM_XHR
			// http://www.mozilla.org/xmlextras/parseserialize.html
			// Aaargggg!
			// Turnabout sets responseXML to the same string as resultText
			if (gReq.responseXML && typeof(gReq.responseXML) != "string") {
				dbg("parsing Native XHR " + typeof(gReq.responseXML));
				rss_render(gReq.responseXML);
				// next try parsing the resultText with DOMParser;
				// first the safe route through XPCNativeWrapper
			} else if (typeof(XPCNativeWrapper) == "function") {
				var dp = new XPCNativeWrapper(window, "DOMParser()");
				dbg('XPC Wrapped DOM Parser: '+typeof(dp));
				var parser = new dp.DOMParser();
				// dbg('DOM Parser: '+typeof(parser));
				var DOM = parser.parseFromString(gReq.responseText, "application/xhtml+xml");
				rss_render(DOM);
				// fallback to content window object; this would fail
				// in GM 0.6.4+ but the safe option has succeeded already.
			} else if (typeof(window.DOMParser) == "object") {
				dbg("parsing DOMParser");
				var parser = new win.DOMParser();
				var DOM = parser.parseFromString(gReq.responseText, "application/xhtml+xml");
				rss_render(DOM);
			} else {
				dbg("parsing msxml2");
				var DOM = new ActiveXObject("msxml2.DOMDocument");
				DOM.loadXML(gReq.responseText);
				if (DOM.parseError.errorCode != 0) {
					dbg(DOM.parseError.reason);
					return;
				}
				rss_render(DOM);
			}
		} else {
			dbg("XHR response error: " + gReq.statusText + "\nURL: " + gRss_src);
		}
	}
}
function dom_setStyle(elt, str) {
	elt.setAttribute("style", str);
	// for MSIE:
	if (elt.style.setAttribute) {
		elt.style.setAttribute("cssText", str, 0);
		// positioning for MSIE:
		if (elt.style.position == "fixed") {
			elt.style.position = "absolute";
		}
	}
}
function dom_createLink(url, txt, title) {
	var a = window.document.createElement("a");
	a.setAttribute("href", url);
	/* ************************** bel ***************************** 
	 *   Added a change here to make sure that the links created 
	 *	with our TEXT color.  
	 * ************************************************************/
	dom_setStyle(a, "color:"+TEXT+";");
	if (title) a.setAttribute("title", title);
	a.appendChild(window.document.createTextNode(txt));
	return a;
}
function dom_getElements(node, elt) {
	var list = node.getElementsByTagName(elt);
	return (list.length) ? list : node.getElementsByTagNameNS("*", elt); 
}
function dom_getFirstNodeValue(node, elt) {
	try {
		var list = dom_getElements(node, elt);
		var chld = list[0].firstChild;
		return chld.nodeValue;
	} catch (e) {
		// dbg("missing element " + elt + "\nError: " + e.message);
		return "";
	}
}
function rss_render(DOM) {
	/* ************************** bel ***************************** 
	 *   This on(dbl-)click function for the open object currently 
	 *	sets the RSS Reader's height appropriately, sets the overflow 
	 *	property to create a scrollbar, resets the right position 
	 *	for the open and close buttons to move them away from the 
	 *	scrollbar, and then sets the inner HTML of the open button
	 *	to the appropriate symbol for conrtact or expand.
	 * ************************************************************/
	var expander = function() {
		// closed state
		if (box.style.height == "15px") {
			box.style.height = "auto";
			box.style.overflow = "auto";
			close.style.right = "13px";
			open.style.right = "27px";
			open.firstChild.nodeValue = "<";
		} else {
			box.style.height = "15px";
			box.style.overflow = "hidden";
			close.style.right = "3px";
			open.style.right = "17px";
			open.firstChild.nodeValue = ">";
		}
	};

	var box = window.document.createElement("div");
	dom_setStyle(box,
			"position:fixed;z-index:998;top:1px;left:1px;margin:0px;background-color:" +
			BACKGROUND + ";border:1px solid " + BORDER +
			";padding:4px;text-align:left;opacity:" + OPACITY + ";font:8pt sans-serif;overflow:hidden;width:250px;height:15px;max-height:100%;margin-bottom:15px;");

	/* ************************** bel ***************************** 
	 *   Create a title (titlebar) element for dragging.
	 *	Set title attribute and style.
	 *	Add a space then the current title of the document.
	 *	Set the pointer on the title bar to be a move pointer.
	 * ************************************************************/
	var title = window.document.createElement("div");
	title.setAttribute("title","Double-Click title to expand/collapse");
	dom_setStyle(title,
			"position:absolute;top:1px;left:1px;z-index:999;margin:0px;background-color:" +
			TITLE_BACKGROUND + ";border:1px solid " + TITLE_BORDER +
			";padding:4px;text-align:left;font:8pt sans-serif;width:246px;height:11px;overflow:hidden;margin-bottom:15px;cursor:move;font-weight:bold;color:"
			+ TITLE_TEXT + ";");
	title.appendChild(window.document.createTextNode(
				dom_getFirstNodeValue(DOM, "title")));

	/* ************************** bel ***************************** 
	 *   Set the pointer on the close button to cursor so that it 
	 *	looks like you can do something with it.
	 * ************************************************************/
	var close = window.document.createElement("div");
	dom_setStyle(close,
			"margin:0px;position:absolute;top:3px;right:3px;width:10px;height:10px;border:1px solid " + TITLE_BORDER +
			";line-height:8px;text-align:center;cursor:pointer;");
	close.setAttribute("title","Click to close panel");
	close.attachEvent('onclick', function() { box.style.display = "none"; });
	close.appendChild(window.document.createTextNode("x"));

	/* ************************** bel ***************************** 
	 *   Create a open (expand/collapse) element for expanding and
	 *	collapsing the RSS Reader.
	 *	Set the cursor to pointer.
	 *	Set the title.
	 * ************************************************************/
	var open  = window.document.createElement("div");
	dom_setStyle(open,
			"margin:0px;position:absolute;top:3px;right:17px;width:10px;height:10px;border:1px solid " + TITLE_BORDER +
			";line-height:8px;text-align:center;cursor:pointer;");
	open.setAttribute("title","Click to expand/collapse");
	open.appendChild(window.document.createTextNode(">"));
	open.attachEvent('onclick', expander);
	/* ************************** bel ***************************** 
	 *   Add the open and close button to the title bar, then add
	 *	the title bar to the RSS Reader.
	 * ************************************************************/
	title.appendChild(open);
	title.appendChild(close);

	box.appendChild(title);

	var ul = window.document.createElement("ul");
	dom_setStyle(ul, "padding-left: 14px; padding-top: 20px");
	var items = [];
	try {
		items = dom_getElements(DOM, "item");
	} catch (e) {
		var li = window.document.createElement("li");
		li.appendChild(window.document.createTextNode("RSS doesn't contain any items!"));
		ul.appendChild(li);
	}
	for (var i=0; i<items.length; i++) {
		var n = items[i];
		var desc = dom_getFirstNodeValue(n, "description");
		var a  = dom_createLink(
				(dom_getFirstNodeValue(n, "link") || "#RSS_MISSING_LINK"),
				(dom_getFirstNodeValue(n, "title") || "Untitled item #" + i),
				desc);
		/* ************************** bel ***************************** 
		 *   Set the style for the link to always be our TEXT color to 
		 *   avoid clashing colors with the links on the existing page.
		 * ************************************************************/
		dom_setStyle(a, "color:" + TEXT + ";");
		var li = window.document.createElement("li");
		/* ************************** bel ***************************** 
		 *   Set the style for the list item to always be our TEXT color 
		 *   to avoid clashing colors with the list items on the 
		 *   existing page.
		 * ************************************************************/
		dom_setStyle(li, "color:" + TEXT + ";");
		li.appendChild(a);
		ul.appendChild(li);
	}
	var div = window.document.createElement("div");
	div.appendChild(ul);
	div.appendChild(dom_createLink(gRss_src, "Link to RSS feed", "RSS feed XML"));
	div.appendChild(window.document.createElement("br"));
	div.appendChild(dom_createLink("http://joe.lapoutre.com/BoT/Javascript/RSSpanel/?v="+RSSPanelVersion, "Check for RSS Panel updates", "Current version: v" + RSSPanelVersion));
	title.attachEvent('dblclick', expander, true);
	box.appendChild(div);
	dom_getElements(document, "body")[0].appendChild(box);

	title.drag = new Drag(title, box); // make draggable
}

// Modified DOM-Drag from Book Burro 0.16
var Drag = function(){ this.init.apply( this, arguments ); };

Drag.fixE = function( e )
{
	if( typeof e == 'undefined' ) e = window.event;
	if( typeof e.layerX == 'undefined' ) e.layerX = e.offsetX;
	if( typeof e.layerY == 'undefined' ) e.layerY = e.offsetY;
	return e;
};

Drag.prototype.init = function( handle, dragdiv )
{
	this.div = dragdiv || handle;
	this.handle = handle;
	if( isNaN(parseInt(this.div.style.left)) ) this.div.style.left  = '0px';
	if( isNaN(parseInt(this.div.style.top)) ) this.div.style.top = '0px';
	this.onDragStart = function(){};
	this.onDragEnd = function(){};
	this.onDrag = function(){};
	this.onClick = function(){};
	this.mouseDown = addEventHandler(this.handle, 'mousedown', this.start, this);
};

Drag.prototype.start = function( e )
{
	// this.mouseUp = addEventHandler(this.handle, 'mouseup', this.end, this);
	e = Drag.fixE(e);

	this.started = new Date();
	var y = this.startY = parseInt(this.div.style.top);
	var x = this.startX = parseInt(this.div.style.left);
	this.onDragStart(x, y);
	this.lastMouseX = e.clientX;
	this.lastMouseY = e.clientY;
	this.documentMove = addEventHandler(document, 'mousemove', this.drag, this);
	this.documentStop = addEventHandler(document, 'mouseup', this.end, this);
	if (e.preventDefault) e.preventDefault();
	return false;
};

Drag.prototype.drag = function( e )
{
	e = Drag.fixE(e);
	var ey = e.clientY;
	var ex = e.clientX;
	var y = parseInt(this.div.style.top);
	var x = parseInt(this.div.style.left);
	var nx = ex + x - this.lastMouseX;
	var ny = ey + y - this.lastMouseY;
	this.div.style.left	= nx + 'px';
	this.div.style.top	= ny + 'px';
	this.lastMouseX	= ex;
	this.lastMouseY	= ey;
	this.onDrag(nx, ny);
	if (e.preventDefault) e.preventDefault();
	return false;
};

Drag.prototype.end = function()
{
	removeEventHandler( document, 'mousemove', this.documentMove );
	removeEventHandler( document, 'mouseup', this.documentStop );
	var time = (new Date()) - this.started;
	var x = parseInt(this.div.style.left),  dx = x - this.startX;
	var y = parseInt(this.div.style.top), dy = y - this.startY;
	this.onDragEnd( x, y, dx, dy, time );
	if( (dx*dx + dy*dy) < (4*4) && time < 1e3 )
		this.onClick( x, y, dx, dy, time );
};

function removeEventHandler( target, eventName, eventHandler )
{
	if( target.addEventListener )
		target.removeEventListener( eventName, eventHandler, true );
	else if( target.attachEvent )
		target.detachEvent( 'on' + eventName, eventHandler );
}

function addEventHandler( target, eventName, eventHandler, scope )
{
	var f = scope ? function(){ eventHandler.apply( scope, arguments ); } : eventHandler;
	if( target.addEventListener )
		target.addEventListener( eventName, f, true );
	else if( target.attachEvent )
		target.attachEvent( 'on' + eventName, f );
	return f;
}


// initialize
rss_init();
})();

Install: RSSPanel.ieuser.js