Created
January 11, 2012 21:40
-
-
Save lpar/1596907 to your computer and use it in GitHub Desktop.
Atomizer: Small embeddable JavaScript web client for Atom feeds.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*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 += "—" + 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