Tuesday, 14 December 2010

River of News, consuming a JSONP data feed

UPDATE:

With the release of JQUERY 1.5 this post is now out of date, also Dave is updating his JSONP feed to make consuming of the feed easier. Once this is completed I'll update the blog with a new post on, but for now see post on River of News news group .

Recently Dave Winer, a pioneer of RSS, published a river of news feed via JSONP. This article shows how to consume such a feed and the pitfalls of JSONP. For starters check out the demo at http://martimedia.com/news.htm

river-of-news

To create the page I made use of the following JavaScript libraries

  • jQuery – Makes writing clean / cross browser JavaScript a breeze
  • jQuery.templates –Separates the presentation (HTML) from code(js)
  • jQuery.jsonp – Provides jsonp error handling

Step 1 – Download the news feed

In the past I’ve just used the jQuery $.get command to download the feed, but luckly, Dave was having teething problems causing timeouts and formatting errors. But $.get wasn’t returning any feedback on these errors. After a bit of goggling I found the following article on StackOverflow which indicates that JSONP requests don’t make use of XHR hence lack of error handling. To overcome this problem jQuery.jsonp is recommended. This is similar to $.get but supports error handling for timeouts and invalid JSONP.

   1: $.jsonp({
   2:               dataType: 'jsonp',
   3:               timeout: 10000,
   4:               url: 'http://jsonp.scripting.com/daveRiver.json?callback=?',
   5:               success: function (data,status) {
   6:                     ProcessData(data);
   7:               },
   8:  
   9:               error: function(XHR, textStatus, errorThrown){
  10:                       if ( textStatus == "timeout")
  11:                           $("#newsFeed").text("Sorry, your request to scripting.com has timed out, please try again later");
  12:                       else if ( textStatus == "error")
  13:                           $("#newsFeed").text("Sorry, response from scripting.com is corrupted, please try again later");
  14:                   }
  15:       });

Step 2 – Process the news feed

Typically we would now write a load of jQuery to create HTML response, but in the new world of jQuery.templates we’re going to separate presentation from the code. This makes the code a lot cleaner and easier for others to reuse. In our example we’re not going to do any processing of the data, but just bind the data to the jQuery template with the following code:

   1: $("#newsFeed").html("");
   2: $("#newsTmpl").tmpl(data.updatedFeeds).appendTo("#newsFeed");

Step 3 – Generate HTML via a template

If you’re not familiar with templates then check out encosia blog . For Dave’s feed we have an array of feeds, which contain one or more stories. To keep us on our toes, Dave returns an object if the feed contains 1 item or an array for 2 or more items.

   1: <script id="newsTmpl" type="text/x-jquery-tmpl">
   1:  
   2:  
   3:   {{each(f,feed) updatedFeed}}
   4:       <h2>${feed.feedTitle}</h2>
   5:       {{if feed.item.length > 1 }}
   6:       {{each(i,item) feed.item}}
   7:  
   8:                   <div class='item'>
   9:                   <span class='date'>${format_date(item.pubDate)}</span> ${item.title} 
  10:                   <a href='${item.link}'>${get_hostname_from_url(item.link)}</a><br/><br/>
  11:                   </div>
  12:           
  13:           {{/each}}
  14:       {{else}}
  15:                   <div class='item'>
  16:                   <span class='date'>${format_date(feed.item.pubDate)}</span> ${feed.item.title} 
  17:                   <a href='${feed.item.link}'>${get_hostname_from_url(feed.item.link)}</a><br/><br/>
  18:                   </div>
  19:       {{/if}}
  20:  
  21:   {{/each}}
  22:   
</script>

Step 4 – Some formatting

To make it look pretty I made use of the colorzilla gradient editor , and a couple of helper functions to format the date and hostname.

Putting it all together in less than 100 lines of CSS/HTML/JS we have …..

   1: <!DOCTYPE html>
   2: <html >
   3:  
   4: <head>
   5:     <title>River of News</title>
   6:     <meta name="Description" content="Example of consuming a JSONP data feed (http://scripting.com) via jQuery and jQuery Templates." /> 
   7:  
   8:     <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.2.0/build/cssfonts/fonts-min.css">
   9:  
  10:     <style type="text/css">
  11:     body { /* generated via http://www.colorzilla.com/gradient-editor/ */
  12:     background: #87e0fd; /* old browsers */
  13:     background: -moz-linear-gradient(left, #87e0fd 0%, #53cbf1 40%, #05abe0 100%); /* firefox */
  14:     background: -webkit-gradient(linear, left top, right top, color-stop(0%,#87e0fd), color-stop(40%,#53cbf1), color-stop(100%,#05abe0)); /* webkit */
  15:     filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#87e0fd', endColorstr='#05abe0',GradientType=1 ); /* ie */
  16:     }
  17:  
  18:     #page { width:800px; padding:20px 60px 20px 60px; margin:60px; min-height:600px; background:white; }
  19:     .date { font-size:80%; color:#888; vertical-align:top; }
  20:     #footer { padding-top:20px; }
  21:     </style>
  22: </head>
  23:  
  24: <body>
  25:  
  26: <div id="page" >
  27:  
  28:    <h1>River of News</h1>
  29:    <p>Powered by Dave Winer <a href="http://scripting.com/stories/2010/12/06/innovationRiverOfNewsInJso.html">river of news</a> feed</p>
  30:    <hr />
  31:                 
  32:        <div id='newsFeed'>Loading news from scripting.com, please wait.....
  33:        <!-- news feed via template inserted here -->
  34:        </div>
  35:  
  36:     <script id="newsTmpl" type="text/x-jquery-tmpl">
   1:  
   2:  
   3:     {{each(f,feed) updatedFeed}}
   4:         <h2>${feed.feedTitle}</h2>
   5:         {{if feed.item.length > 1 }}
   6:         {{each(i,item) feed.item}}
   7:                     <div class='item'>
   8:                     <span class='date'>${format_date(item.pubDate)}</span> ${item.title} 
   9:                     <a href='${item.link}'>${get_hostname_from_url(item.link)}</a><br/><br/>
  10:                     </div>
  11:             
  12:             {{/each}}
  13:         {{else}}
  14:                     <div class='item'>
  15:                     <span class='date'>${format_date(feed.item.pubDate)}</span> ${feed.item.title} 
  16:                     <a href='${feed.item.link}'>${get_hostname_from_url(feed.item.link)}</a><br/><br/>
  17:                     </div>
  18:         {{/if}}
  19:  
  20:     {{/each}}
  21:     
</script>
   1:  
   2:     <hr />
   3:     <p id=footer>Check out my <a href="http://martimedia.blogspot.com/">martimedia blog</a>, 
   4:     or my company website at <a href=http://martimedia.com/>martimedia.com</a></p>
   5: </div>
   6:  
   7: </body>
   8:  
   9: <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js">
   1: </script> 
   2: <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js">
   1: </script>
   2: <script type="text/javascript" src="http://jquery-jsonp.googlecode.com/files/jquery.jsonp-1.0.4.min.js">
   1: </script>
   2:  
   3: <script type="text/javascript">
   4:  
   5:     $(function () { 
   6:         $.jsonp({
   7:                 dataType: 'jsonp',
   8:                 timeout: 10000,
   9:                 url: 'http://jsonp.scripting.com/daveRiver.json?callback=?',
  10:                 success: function (data,status) {
  11:                         $("#newsFeed").html("");
  12:                         $("#newsTmpl").tmpl(data.updatedFeeds).appendTo("#newsFeed");
  13:                 },
  14:                 error: function(XHR, textStatus, errorThrown){
  15:                         if ( textStatus == "timeout")
  16:                             $("#newsFeed").text("Sorry, your request to scripting.com has timed out, please try again later");
  17:                         else if ( textStatus == "error")
  18:                             $("#newsFeed").text("Sorry, response from scripting.com is corrupted, please try again later");
  19:                         else
  20:                             $("#newsFeed").text("Error, status: " + textStatus + ", please try again later" );
  21:                     },
  22:         });
  23:  
  24:     });
  25:     
  26:     function format_date(date) {
  27:        var newDate = new Date( Date.parse(date));
  28:        return newDate.toLocaleTimeString();
  29:     }
  30:  
  31:     function get_hostname_from_url(url) {
  32:         url = url.match(/:\/\/(.[^/]+)/)[1];
  33:         url = url.replace("www.","").replace("feedproxy.","").replace("feeds.","");
  34:         return url;
  35:     }
  36:     
</script>
  37:   
  38: </html>

Feel free to use the code to create your own river of news, I just ask you to post your examples in the comments section below.

10 comments:

K said...

Thanks for the work and sharing the script. I've put it to use here: http://www.kenbooth.net/newsriver/

Julien said...

Nice! Can't wait to see people use http://superfeedr.com and its abritrary content support to subscribe to that JSON!

Shawn McCollum said...

Here's my entry based on your code.

http://tubejumper.com/winer_feed.html

Anonymous said...

Good excuse for me to finally play with jQuery. I went with a more "subtraction.com" inspired black & white theme.

http://river.havagan.com/

You can follow the design progression of the jQuery templates:

http://river.havagan.com/?template=feed_template_bw_detailed

http://river.havagan.com/?template=feed_template_bw_minimal_v1

http://river.havagan.com/?template=feed_template_bw_minimal_v2

(the current default template)

FooBarBoy said...
This comment has been removed by the author.
FooBarBoy said...

I never understood the idea of 'subscribing' to a json 'feed', but I'm starting to get the idea! Love these different templates!

Would love to learn how to make http://noagedanewsnetwork/ look cooler.

Thanks for starting the thread here MD!

AC

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Unknown said...
This comment has been removed by a blog administrator.
Unknown said...
This comment has been removed by a blog administrator.