Google Reader

A Little HTML5: Using JSON and File API to View Starred Google Reader Posts

by on May 26, 2013 10:00 am

A couple of months ago, Google announced that it was retiring its web feed reader Google Reader. As a long-time user of the service, I was disappointed. But there are a couple of substitutes out there like FeedlyThe Old ReaderNetVibes, etc. Google gives you the option of exporting your data from Reader through Google Takeout. This is where my inspiration for a little project started.

When you go through the process to export your data through Takeout, you receive a zip file. It contains an XML file with all of your subscriptions in OPML format, as well as 7 JSON files containing information like your social connections, stories you “liked,” stories you shared with others, notes you took about stories, stories that you put in your favorites list, etc. The problem I found is that none of the above mentioned substitutes know what to do with any of these JSON files.

I typically used my favorites list (the “starred” items) as an “I’ll read this later” group. But now, I have a 1.5 MB file of saved items and no way to read them.

So, let’s create a way to read them using JSON and the File API!

The Solution

google-reader-import.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="robots" content="noindex">
        <title>Google Reader Import</title>
        <link type="text/css" rel="stylesheet" href="google-reader-import.css">
    </head>
    <body>
<h1>Google Reader Import</h1>
        <input type="file" id="fileInput" />
        <div id="stories" class="stories"></div>
        <script src="google-reader-import.js">

        </script>
    </body>
</html>

I also created a CSS file to style the posts similarly to how they are presented in the original Google Reader application:

google-reader-import.css

.stories {
    margin: 10px 0px 0px;
    max - width: 650px;
}
    .entry - main {
    padding: 5px 10px 0px 13px;
    margin - bottom: 20px;
    border - top: 0px none;
    border - bottom: 0px none;
    background: none repeat scroll 0 % 0 % rgb(247, 247, 247);
    border: 1px solid rgb(233, 233, 233);
    min - height: 20px;
    padding: 19px;
    margin - bottom: 20px;
    background - color: rgb(245, 245, 245);
    border: 1px solid rgb(227, 227, 227);
    border - radius: 4px 4px 4px 4px;
    box - shadow: 0px 1px 1px rgba(0, 0, 0, 0.05) inset;
}
    .entry - date {
    float: right;
    color: rgb(102, 102, 102);
    text - decoration: none;
    margin: 0px;
}
    .entry - title {
    color: rgb(17, 85, 204);
    font - size: 140 % ;
    margin: 0px;
    max - width: 650px;
}
    .entry - title - link {
    color: rgb(17, 85, 204);
    text - decoration: none;
}
    .entry - author {
    color: rgb(102, 102, 102);
    text - decoration: none;
    margin: 0px;
}
    .entry - source - title - parent {
    color: rgb(102, 102, 102);
}
    .entry - source - title {
    color: rgb(17, 85, 204);
    display: inline - block;
    text - decoration: none;
}
    .entry - body {
    color: rgb(0, 0, 0);
    padding - top: 0.5em;
    max - width: 650px;
    margin: 0px;
}
    .item - body {
    margin: 0px;
}

However, the real work is done in the JavaScript.

The first function I’ve named handleFileSelect. When the user selects a file, the handleFileSelect() function gets called. We use the JavaScript File API to read in the file, using the FileReader to handle actually asynchronously loading the JSON file. After creating the new FileReader object, we set up its onload function, then call readAsText() to start the read operation in the background. When the entire contents of the JSON file are loaded, we pass it to the JSON.parse() function in our onload callback. Our implementation of this routine takes the JSON objects and iterates through its item list, where each item represents a single post from our starred list.

The second function, createStoryEntry, creates the HTML for each post using a format similar to the original application. The date in the JSON is along in seconds, so we take it and multiply by 1000 to convert it to milliseconds and create a JavaScript Date object so we can create a user-friendly date representation.

Another thing to consider is that some posts have full content, while others just contain a summary of the full content. We do a quick check to determine if the post has full or partial content and then set our content to the correct value. Each post is then appended as a child to our “stories” div:

google-reader-import.js

function createStfunction createStoryEntry(jsonItem) {
    var publishedDate = new Date(jsonItem.published * 1000);
    var content = (typeof jsonItem.content === 'undefined') ? jsonItem.summary.content : jsonItem.content.content;
    var entryMain = document.createElement("div");
    entryMain.className = "entry-main";
    entryMain.innerHTML = '</pre>
<div class="entry-date">' + publishedDate.toDateString() + ' ' +
 publishedDate.toTimeString() + '</div>
<h2 class="entry-title">' +
 '<a class="entry-title-link" href="' + jsonItem.alternate.href + '" target="_blank">' +
 jsonItem.title + '</a></h2>
<div class="entry-author">' +
 '<span class="entry-source-title-parent">from <a class="entry-source-title" href="' +         jsonItem.origin.htmlUrl + '" target="_blank">' + jsonItem.origin.title +
 '</a></span></div>
<div class="entry-body">
<div>' +
 '
<div class="item-body">
<div>' + content + '</div>
</div>
' +
 '</div>
</div>
<pre>
';
    return entryMain;
}

function handleFileSelect() {
    var storiesContainer = document.getElementById('stories');

    var reader = new FileReader();
    reader.onload = function (evt) {
        var parsedStories = JSON.parse(evt.target.result);
        for (var i = 0; i < parsedStories.items.length; i++) {
            storiesContainer.appendChild(createStoryEntry(parsedStories.items[i]));
        }
    };
    reader.readAsText(this.files[0]);
}
document.getElementById('fileInput').addEventListener('change', handleFileSelect, false);

If you want to see more of what you can do with the File API, Mozilla has a great resource on adding additional features like drag-and-drop.

This particular program will only work with a modern browser, but you could also use a JavaScript library like Modernizr to determine if the browser has the capability to use the File API and JSON. Good luck!

– Brice McIver, asktheteam@keyholesoftware.com

  • Share:

Leave a Reply

Things Twitter is Talking About
  • Tech Night is now! Mark D is presenting to the group on #Grunt 101. Good technology talk, food & team makes for a fantastic night.
    April 23, 2014 at 5:10 PM
  • Single Page Application architectures allow for rich, responsive UIs. #JavaScript is a must-know for SPA - http://t.co/6sfk3kt1k3 #tutorial
    April 23, 2014 at 2:15 PM
  • Vacuole #Encapsulation aims to minimize the code necessary for routinely verbose data tasks -http://t.co/fJQzz731rZ
    April 23, 2014 at 9:45 AM
  • DYK? We translate our hands-on experience to custom courses to train your dev teams. Our new course on #AngularJS: http://t.co/Bf3UuClj4Z
    April 23, 2014 at 8:45 AM
  • Interested in #Backbone & #Marionette but not sure where to start? Check out the Marionette-Require-Boilerplate: http://t.co/XDj43BwSS3 #SPA
    April 22, 2014 at 4:50 PM
  • Responsive Design can help in giving your users a consistent app experience across devices. Quick tutorial - http://t.co/BDrT5LvgRo
    April 22, 2014 at 2:31 PM
  • Tips & tricks to save time in the #Eclipse IDE - http://t.co/uGgCkchwNk (keystroke combos, navigation, time tracking & more)
    April 22, 2014 at 8:40 AM
  • Join us! Looking to add to our team a developer w/ advanced #JavaScript & #NodeJS exp (& love of tech variety). Info: http://t.co/cC9CU1RCF9
    April 21, 2014 at 7:35 PM
  • Looking into #ExtJS but don't know where to start? Check out our video tutorial series to find your way around - http://t.co/XFYDT6YNWA
    April 21, 2014 at 4:35 PM
  • We've been tinkering with JS library Famo.us since its public release 4/10. What we've learned so far via a POC app - http://t.co/S77TSKHDKd
    April 21, 2014 at 2:03 PM
  • RT @CompSciFact: Rivest, Shamir, and Adleman published the RSA public key cryptography algorithm in 1978.
    April 21, 2014 at 11:13 AM
  • DYK? When we share/RT/blog/etc, it doesn't mean that Keyhole endorses it - we just like variety of opinions! Info: http://t.co/MXhB9lE9tV
    April 19, 2014 at 3:01 PM
  • A huge welcome to Justin Graber who joined the Keyhole team this week!
    April 18, 2014 at 3:25 PM
  • Pssst... @kc_dc early bird pricing ends on Sunday. Shoot us a note if you want to save 10% off of your ticket with our sponsor promo code!
    April 18, 2014 at 2:49 PM
  • Join our team! Looking for a developer w/ advanced #JavaScript & #NodeJS experience (& love of tech variety). Info: http://t.co/cC9CU1RCF9
    April 18, 2014 at 11:21 AM
  • .@befamous has huge potential to make HTML5/JS/CSS web pages feel as native apps. Here's our inital tech takeaways - http://t.co/S77TSKHDKd
    April 18, 2014 at 9:50 AM
  • Why to use AngularUI Router instead of ngRoute - http://t.co/tBnj5ZCkOw
    April 17, 2014 at 7:55 PM
  • RT @joemccann: Total Number of GitHub Repositories by Programming Language http://t.co/30cekDsE4s
    April 17, 2014 at 4:25 PM
  • JSF + AngularJS = AngularFaces? http://t.co/mXvOTwVbb6 // Interesting insight. Thoughts?
    April 17, 2014 at 3:45 PM
  • RT @MikeGelphman: Great news, guys: @TobiasRush founder of @eyeverify is our latest @MobileMidwest speaker addition http://t.co/8fE8oNfPnX
    April 17, 2014 at 1:35 PM
Keyhole Software
8900 State Line Road, Suite 455
Leawood, KS 66206
ph: 877-521-7769
© 2013 Keyhole Software, LLC. All rights reserved.