Building a Blog Redux - Goodreads feed using Backbone.js (Part 5)

Tuesday, May 8, 2012

This is the fifth post in a series of posts about how I went about building my blogging application.

  1. Building a Blog Redux - Why Torture Myself (Part 1)
  2. Building a Blog Redux - The tools for the trade (Part 2)
  3. Building a Blog Redux - Entity Framework Code First (Part 3)
  4. Building a Blog Redux - Web fonts with @font-face and CSS3 (Part 4)

So I put a right rail column on my blog site thinking I probably would put some content over there to engage you users. I had my tag cloud, and eventually I am going to put a search box there as a future feature, but I wasn't really excited about what else to put there. I thought about a dated archive list but I have a dedicated page for that and besides, who really ever clicks on those links.

I could put some ads, but admittedly, I don't have enough traffic to consider that, and even if I did, I still probably would not do that right away. Although, I did put placeholder on the right side in case I changed my mind.

I could put my twitter feed, but I don't know; I don't think that is very interesting to user reading my blog, plus it's sort of been done by everybody already.

Goodreads

Enter Goodreads. If you read books and are social, Goodreads is the perfect site for you. You can keep track of all the books you read, categorizing them by  "Currently Reading", "Read", and "To Read". You can also categorize books by genres or whatever classification you want. The great thing about categorizing the books is Goodreads will give you recommendations based on how you have your books categorized.

Goodreads is social site, so just like Twitter, Facebook, etc. You make friends and then you can see what books they are reading and they have rated and reviewed.

And just like most other social sites, Goodreads has a developer section with an API and other tools for you to use. You just go to their website, request an application key, agree to their terms of use and then you are pretty much good to go.

The API

The API call I am using is the one called "user.show". It pretty much contains the basic information about, me, the user, such as what my bookshelves are, and (what I am most interested in) my book status updates.

The particular node I want is the action_text. Every time I update my status with a book I read or started reading, I get a new one of these nodes. The node value is HTML encoded so that task is already done by Goodreads.

<action_text>
  <![CDATA[
  gave 4 stars to: <a href="http://www.goodreads.com/book/show/12371896-the-node-beginner-book">
  The Node Beginner Book (ebook)</a> by <a href="http://www.goodreads.com/author/show/5132009.Manuel_Kiessling">
  Manuel Kiessling
  </a>
]]>
</action_text>

To get the XML for this particular message, I just need to make a GET reques to their server. Unlike Twitter, I didn't have to add a whole bunch of entries in the request header (I am going to write a post about how to do that later), I only needed the Goodreads API URL which looks like this.

http://www.goodreads.com/user/show/{user_id_number}.xml?key={your_key}

The call is a basic HttpWebRequest and HttpWebResponse.

public class HttpRequestHelper : IHttpRequestHelper
    {
        public string GetResponse(HttpWebRequest request)
        {
            ServicePointManager.Expect100Continue = false;
 
            if (request != null)
                using (var response = request.GetResponse() as HttpWebResponse)
                {
 
                    try
                    {
                        if (response != null && response.StatusCode != HttpStatusCode.OK)
                        {
                            throw new ApplicationException(
                                string.Format("The request did not compplete successfully and returned status code:{0}",
                                              response.StatusCode));
                        }
                        if (response != null)
                            using (var reader = new StreamReader(response.GetResponseStream()))
                            {
                                return reader.ReadToEnd();
                            }
                    }
                    catch (WebException exception)
                    {
                        return exception.Message;
                    }
                }
            return "The request is null";
        }
 
 
        public HttpWebRequest GetRequest(string fullUrl, string authorizationHeaderParams, string method)
        {
            var hwr = (HttpWebRequest)WebRequest.Create(fullUrl);
            if (! string.IsNullOrEmpty(authorizationHeaderParams)) hwr.Headers.Add("Authorization", authorizationHeaderParams);
            hwr.Method = method;
            hwr.Timeout = 3 * 60 * 1000;
            return hwr;
        }
    }

I have a function to build the HttpWebRequest and then I pass that request to function which makes the request and gets the HttpWebResponse.

Mapping the XML Response

I could have serialized the XML to an object using the XmlSerializer, but since I was only needing a few of the fields from a rather large XML response, I decided to just map the XML to a CLR object manually using LINQ to XML.

var view = new GoodReadsUserShowViewModel {Updates = new List<GoodReadsUpdateViewModel>()};
XDocument doc = XDocument.Parse(xml);

 

Creating the MVC Partial View

Once I have my object built, I then pass it back to the MVC controller which will then intern pass it back to a partial view.

        [OutputCache(Duration = 60000, VaryByParam = "*")]
        public ActionResult Index(string id)
        {
            try
            {
                GoodReadsUserShowViewModel result = GetGoodReadsUserShowViewModel(id);
                return PartialView("_Goodreads", result);
            }
            catch(Exception ex)
            {
                ErrorSignal.FromCurrentContext().Raise(ex);
                return PartialView("_Goodreads"new GoodReadsUserShowViewModel {ErrorMessage = ex.Message});
            }
        }

Things to note up until this point. Since I know that this feed is not going to change much (I mean I can only read books so fast), I am caching the response for a fairly long time. In this case, performance is more important than timeliness because I don't care that you see the latest status the second after I update it.

Also, I could have passed back a JsonResult, but in this case, Backbone.js actually recommends that you have your data already bootstrapped into the request. Therefore, on my Goodreads partial view, I am taking the view model and passing it to a helper class that will serialize my model JSON and put it in a JavaScript variable.

<script src="@Url.Content("~/js/goodreads.js")" type="text/javascript"> </script>
<script type="text/javascript">
    var grApp = new GoodreadsApp(@Model.ToJson());
    grApp.start();
</script>

 

The helper class ToJson looks like this.

using System.Web.Mvc;
using Newtonsoft.Json;
 
namespace AviBlog.Core.Helpers
{
    public static class JavaScriptSerializerHelper
    {
 
        public static MvcHtmlString ToJson(this object model)
        {
            string json = JsonConvert.SerializeObject(model);
            return new MvcHtmlString(json);
        }
         
    }
}

So when the page is actually rendered the JavaScript model that is rendered will look something like this.

<script type="text/javascript">

    var grApp = new GoodreadsApp({
        "UserId":"3425042",
        "UserName":"avington",
        "Name":"Steve Moseley",
        . . . });

    grApp.start();

</script>

 

Backbone.js Implimentation

Here is what the Backbone.js website has to say about what backbone.js is.

"Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface."

It essentially provides a clean way to bind your front-end code to different events to provide a rich user interaction. In this case, I am using it in a very simple fashion and just binding the initial response of the Goodreads data to template and binding it using jQuery Templates. Later on I am probably going to switch out jQuery Templates for jsRender, but for now jQuery Templates it is.

Derick Baily who probably has the best blog articles on Backbone, put a response in StackOverflow as what the best way to render data on a page on the initial load and as you can see from his response and my code, I pretty much did the same thing. Here is my backjone.js code.

GoodreadsApp = (function (Backbone, _, $) {
    var GoodreadsModel = Backbone.Model.extend({});
 
    var GoodreadsCollection = Backbone.Collection.extend({
        model: GoodreadsModel
    });
 
    var GoodreadsView = Backbone.View.extend({
        el: $('.goodreads-widget'),
 
        initialize: function () {
            this.collection.bind("reset"this.render, this);
        },
 
        render: function () {
            var data = this.collection.models[0].attributes;
            if (data) {
                var $template = $('#goodreads-template');
                var goodreadsHtml = $template.tmpl(data);
                $(this.el).html(goodreadsHtml);
            }
        }
    });
 
    var goodreadsApp = function (initialModels) {
        this.start = function () {
            this.models = new GoodreadsCollection();
            this.myView = new GoodreadsView({ collection: this.models });
            this.models.reset(initialModels);
        };
    };
 
    return goodreadsApp;
})(Backbone, _, jQuery);

In this example taken from StackOverflow, when the start function is called, an empty model is initialized. The reset backbone even is called which clear out the collection of models and fills the collection with a new set (in this case taken from the serialized JSON data). The render event is hooked up to the reset function so that when it is called the template is rendered.

The jQuery template looks like this.

<script id="goodreads-template" type="text/x-jquery-tmpl">
    <h4>Goodreads Status</h4>
    <ul>
        {{each Updates}}
        <li class="clear">
            <img class="goodreads-book-img" src="${BookImageUrl}"/>
            <div><a href="${UserLink}">${Name}</a> {{html ActionText}}</div>
        </li>
        {{/each}}
        <li class="last-goodreads-item">Follow me on <a href="${UserLink}">Goodreads</a></li>
    </ul>
    
 
</script>

Conclusion

I would have put a picture of the result here, but if you take a look over here on the right. Yeah the right hand panel you can see it there for yourself. I think its a pretty cool section for only a couple hours of work. As always, you can see all the code for the application at Github.

Resources

 

comments powered by Disqus