Skip to content

Instantly share code, notes, and snippets.

@lpar
Created January 11, 2012 21:40
Show Gist options
  • Select an option

  • Save lpar/1596907 to your computer and use it in GitHub Desktop.

Select an option

Save lpar/1596907 to your computer and use it in GitHub Desktop.
Atomizer: Small embeddable JavaScript web client for Atom feeds.
/*jslint browser: true, indent: 2 */
// Atomizer: Small embeddable JavaScript web client for Atom feeds (RFC 4287).
//
// Generates unordered lists or tables which can be inserted anywhere on
// the page using innerHTML.
//
// The Atom feed needs to be on the same hostname, port and protocol as the
// web page, because of browser security policies.
// See <http://en.wikipedia.org/wiki/Same_origin_policy>
//
// Does not handle RSS feeds. Generate Atom, it's better.
//
// Guaranteed 100% framework-free. Placed in the public domain to use as you
// like.
var parse8601;
// If the browser is ECMA-262 v5 capable and can parse ISO8601 timestamps,
// use its native parse code for speed. Otherwise, use some ugly JavaScript.
if (isNaN(Date.parse("1968-10-15T13:45:20Z"))) {
// Adapted from http://delete.me.uk/2005/03/iso8601.html
parse8601 = function (string) {
"use strict";
var offset = 0, result = new Date(), time,
regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
"(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\\.([0-9]+))?)?" +
"(Z|(([-+])([0-9]{2}):?([0-9]{2})))?)?)?)?",
d = string.match(new RegExp(regexp)),
date = new Date(d[1], 0, 1);
if (d[3]) { date.setMonth(d[3] - 1); }
if (d[5]) { date.setDate(d[5]); }
if (d[7]) { date.setHours(d[7]); }
if (d[8]) { date.setMinutes(d[8]); }
if (d[10]) { date.setSeconds(d[10]); }
if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= ((d[15] === '-') ? 1 : -1);
}
offset -= date.getTimezoneOffset();
time = (Number(date) + (offset * 60 * 1000));
result.setTime(Number(time));
return result;
};
} else {
parse8601 = function (string) {
"use strict";
return new Date(Date.parse(string));
};
}
// var f = new Feed();
// or
// var f = new Feed(atom-url);
//
// Feed object then has the following methods:
//
// .add(url) - add in entries from a second Atom feed.
// .sort() - sort entries in object by descending update date.
// .truncate(N) - limit to top N entries.
// .newdays - number of days for which entries should show 'New' in red,
// 0 to disable feature (default).
// .updated - JavaScript updated date from feed.
// .cssclass - CSS class to use when generating H3 header.
// .toUL() - return HTML for an unordered list of the entries.
// .toTABLE() - return HTML for a table of the entries.
// Define Feed to be the result of evaluating an anonymous function which
// defines a bunch of stuff internally, hidden to the environment outside
// it, then returns the Feed object which we want to make visible.
var Feed = (function () {
"use strict";
var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'], Entry, Feed, ONE_DAY_MS = 86400000, normalize,
dateToString, dateTimeToString;
dateToString = function (x) {
return x.getDate() + "\u00A0" + MONTHS[x.getMonth()] + "\u00A0" +
x.getFullYear();
};
dateTimeToString = function (x) {
var z = x.getTimezoneOffset(),
minus = (z < 0 ? '+' : '-'),
hh = Math.abs(z / 60),
mm = Math.abs(z) % 60,
zone = minus + ('0' + hh).slice(-2) + ('0' + mm).slice(-2);
return dateToString(x) + " " +
x.getHours() + ":" + x.getMinutes() + "\u00A0" + zone;
};
// Internet Explorer is supposed to have IXMLDOMElement.normalize()
// but it doesn't work, "Object doesn't support this property or method"
normalize = function (node) {
var child, nextChild;
if (node.normalize) {
node.normalize();
return;
}
child = node.firstChild;
while (child) {
if (child.nodeType === 3) {
nextChild = child.nextSibling;
while (nextChild && nextChild.nodeType === 3) {
child.appendData(nextChild.data);
node.removeChild(nextChild);
nextChild = child.nextSibling;
}
} else {
normalize(child);
}
child = child.nextSibling;
}
};
// Entry object represents an individual entry (story) in an Atom feed.
Entry = function (entrynode) {
var kids, node, i;
kids = entrynode.childNodes;
for (i = 0; i < kids.length; i += 1) {
node = kids[i];
switch (node.nodeName) {
case 'title':
normalize(node.firstChild);
this.title = node.firstChild.nodeValue;
break;
case 'link':
this.link = node.getAttribute('href');
break;
case 'updated':
this.updated = parse8601(node.firstChild.nodeValue);
break;
case 'summary':
normalize(node.firstChild);
this.summary = node.firstChild.nodeValue;
break;
}
}
};
Entry.prototype.toLI = function (newdays) {
var txt, isnew = '';
if (this.updated !== undefined && newdays !== 0 &&
(new Date() - this.updated) < newdays * ONE_DAY_MS) {
isnew = "<span style='color: red; font-size: smaller;'>New</span> ";
}
txt = "<li>" + isnew + "<a href='" + this.link + "'>" + this.title + "</a>";
if (typeof this.summary === 'string') {
txt += "&mdash;" + this.summary;
}
return txt + "</li>";
};
Entry.prototype.toTR = function () {
return "<tr><td class='date'>" + dateToString(this.updated) +
"</td><td><a href='" + this.link + "'>" + this.title + "</a></td></tr>";
};
// Create a Feed object to represent one or more Atom feeds
Feed = function (url) {
this.entries = [];
this.base = '';
this.newdays = 0;
this.updated = new Date();
if (typeof url === 'string') {
this.add(url);
}
this.cssclass = 'bar-green-dark';
};
// Sort into descending order of update time/date
Feed.prototype.sort = function () {
var byupdated = function (a, b) {
return b.updated - a.updated;
};
this.entries.sort(byupdated);
};
Feed.prototype.truncate = function (n) {
if (this.entries.length > n) {
this.entries = this.entries.slice(0, n - 1);
}
};
// Add data from an atom feed into the Feed object.
// If you use a URL with a path, subsequent pathless URLs are considered
// to have the same base path. e.g.
// foo.add('http://www.example.com/something/feed.xml');
// foo.add('feed2.xml') => uses base path from line above
/*jslint regexp: true */
Feed.prototype.add = function (url) {
var http = new XMLHttpRequest(), dom, feed, node, i, kids, entry,
entries = [], updated, base = url.replace(/^[^\/]*/, '');
/*jslint regexp: false */
if (url.lastIndexOf('/') < 0) {
url = this.base + url;
} else {
if (this.base === '') {
this.base = base;
}
}
http.open("GET", url, false);
http.send();
dom = http.responseXML;
feed = dom.documentElement;
if (feed.nodeName !== 'feed') {
return;
}
kids = feed.childNodes;
for (i = 0; i < kids.length; i += 1) {
node = kids[i];
switch (node.nodeName) {
case 'title':
normalize(node.firstChild);
this.title = node.firstChild.nodeValue;
break;
case 'updated':
normalize(node.firstChild);
updated = parse8601(node.firstChild.nodeValue);
break;
case 'entry':
entry = new Entry(node);
if (this.mrentry === undefined || this.mrentry < entry.updated) {
this.mrentry = entry.updated;
}
entries.push(new Entry(node));
break;
}
}
if (this.mrupdate === undefined || this.mrupdate < updated) {
this.mrupdate = updated;
this.updated = updated;
}
if (entries.length > 0) {
this.entries = this.entries.concat(entries);
}
};
// Builds an unordered list with title to drop into a web page.
// See http://www.quirksmode.org/dom/innerhtml.html
// and don't use DOM to build page elements.
Feed.prototype.toUL = function () {
var html = ["<h3 class='" + this.cssclass + "'>" + this.title + "</h3><ul>"], i;
if (this.entries.length === 0) {
return "";
}
for (i = 0; i < this.entries.length; i += 1) {
html.push(this.entries[i].toLI(this.newdays));
}
html.push("</ul>");
if (this.mrentry !== undefined) {
html.push("<p class='note'>Updated " + dateToString(this.mrentry) + "</p>");
}
return html.join('');
};
Feed.prototype.toTABLE = function () {
var html = ["<table class='feed'><caption>" + this.title +
"</caption><tbody>"], i;
if (this.entries.length === 0) {
return "";
}
for (i = 0; i < this.entries.length; i += 1) {
html.push(this.entries[i].toTR());
}
html.push("</tbody></table>");
return html.join('');
};
return Feed;
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment