Building a Chumby-style feed reader using SJS
Stratified JavaScript (SJS) might still be work in progress, but that doesn't mean it can't be used for building some (semi-)useful things.
Consider e.g. the 'FeedChumby' RSS feed reader which automatically cycles through the posts of some popular RSS feeds, 'slide-show' style - similar to the way in which a Chumby cycles through its widget stream.
In this blog post I will dissect the code for this webapp, tutorial-style.
Accessing feeds
At its core, FeedChumby uses the Google AJAX Feeds API which allows us to retrieve RSS feeds without having to worry about any crossdomain issues and without requiring any server support from our side.
SJS gives us access to this API through the unassuming function
Ajax.Google.Feeds.loadFeed(url);
which returns a JSON results object as described in the Google documentation.
Because we're using SJS, we can call this function synchronously - no callback or other asynchronous construct needed. All the asynchronous action is automatically choreographed behind the scenes: loading the Google API if it hasn't been loaded yet, making sure the 'feeds' module is loaded, and finally loading the actual feed itself. While all of this is going on, the browser stays perfectly responsive.
Coupled with SJS's 'Ajax.DOM.waitForEvent' library function (which - as one would expect from the name - waits for a particular DOM event), loading and displaying a feed in response to a button click is then as easy as this:
Ajax.DOM.waitForEvent("click", button);
var result = Ajax.Google.Feeds.loadFeed(feed_url);
renderResult(result);
Here is a complete example: google-feeds.html
Basic program structure
By sticking 'loadFeed' into a loop, iterating over a few different feeds and over individual feed posts, and pausing for a few seconds after each post is displayed (using SJS's "hold()" primitive), we've got the basic structure for our Chumby-style feed reader - again no asynchronous constructs (like e.g. setTimeout) needed:
var feeds = [ "...slashdot...", "...digg...", "...elreg...", ... ];
while (true) {
for (var i=0; i<feeds.length; ++i) {
var result = Ajax.Google.Feeds.loadFeed(feed[i]);
for (var j=0; j<result.feed.entries.length; ++j) {
renderFeedEntry(result.feed.entries[j]);
hold(10000); // <-- wait 10s before continuing with next post
}
}
}
Let's also add a button that allows the user to shortcut through the 10s waiting time. The easiest way to provide this sort of functionality is by using SJS's '@' combinator (as described in an earlier blog post, "A @ B" executes both A and B in parallel, and returns as soon as either of them have finished evaluating; at the same time cancelling the still pending expression):
for (...) {
...
hold(10000) @ Ajax.DOM.waitForEvent("click", nextButton);
}
As a little refinement, we can replace the 'hold(1000)' by code that will provide the user with some UI feedback during the waiting period:
function countdown() {
for (var i=0; i>0; --i) {
counter.innerHTML = i;
hold(1000);
}
}
...
for (...) {
...
countdown() @ Ajax.DOM.waitForEvent("click", nextButton);
}
Here is a complete example with the functionality so far: feedchumby.html
Adding 'pause' functionality
Again using '@' and 'Ajax.DOM.waitForEvent()', we can modify our countdown() function to stop counting down while in a paused state:
var paused = false;
function countdown() {
var i=10;
while (i>0) {
counter.innerHTML = i;
if (paused) {
pauseButton.innerHTML = "Play";
Ajax.DOM.waitForEvent("click", pauseButton);
paused = false;
pauseButton.innerHTML = "Pause";
}
(hold(1000), --i) @
(Ajax.DOM.waitForEvent("click", pauseButton), paused=true);
}
}
Note the '@' expression here. It combines the two simpler expressions "(hold(1000), --i)", which waits for 1s and then decrements 'i', and "(Ajax.DOM.waitForEvent("click", pauseButton), paused=true)", which waits for a click on 'pauseButton' and then sets the variable 'paused' to true. (In both 'normal' JavaScript and also SJS, for two expressions A and B, "A,B" is an expression that means "execute first A and then B".)
Here is the example with the pause functionality rolled in: feedchumby2.html
Adding birectional navigation
To add functionality that allows the user to navigate back to the previous post, we need to change our loop logic a little bit to account for the situtation where we're moving past the beginning of a feed to the previous feed. The gory details are here: feedchumby3.html.
Note the '@' expression in the main loop:
(countdown(), ++j) @
(Ajax.DOM.waitForEvent("click", "next"), ++j) @
(Ajax.DOM.waitForEvent("click", "previous"), --j);
Previously we only waited for the countdown to finish or for the user to click on the 'next' button. Now we're also waiting for clicks on the 'previous' button, and we're incrementing/decrementing variable 'j' to communicate the direction of navigation.
More embellishments
The absence of any asynchronous constructs (callbacks, setTimeouts, ...) makes the code quite modular. It's easy to add little features here and there without having to take a "global view" of the application, and the '@' combinator relieves us from a great deal of manual bookkeeping.
The final version of the FeedChumby adds a couple more small touches, like e.g. an animated pause button, and it contains some CSS to make the application more presentable: feedchumby4.html
I think FeedChumby is a pretty good example of the power of SJS for writing Ajax-style applications. The real win, however, will only show in big 'mashup' style applications that access several different APIs concurrently. Expect to see more demos coming along with APIs such as the Google Translation API, Google Contacts, Facebook Connect, and others.