Netflix, jQuery, JSONP, and OData

At the last MIX conference, Netflix announced that they are exposing their catalog of movie information using the OData protocol. This is great news! This means that you can take advantage of all of the advanced OData querying features against a live database of Netflix movies.

In this blog entry, I’ll demonstrate how you can use Netflix, jQuery, JSONP, and OData to create a simple movie lookup form. The form enables you to enter a movie title, or part of a movie title, and display a list of matching movies. For example, Figure 1 illustrates the movies displayed when you enter the value robot into the lookup form.

 

clip_image002

Using the Netflix OData Catalog API

You can learn about the Netflix OData Catalog API at the following website:

http://developer.netflix.com/docs/oData_Catalog

The nice thing about this website is that it provides plenty of samples. It also has a good general reference for OData. For example, the website includes a list of OData filter operators and functions.

The Netflix Catalog API exposes 4 top-level resources:

  • Titles – A database of Movie information including interesting movie properties such as synopsis, BoxArt, and Cast.
  • People – A database of people information including interesting information such as Awards, TitlesDirected, and TitlesActedIn.
  • Languages – Enables you to get title information in different languages.
  • Genres – Enables you to get title information for specific movie genres.

OData is REST based. This means that you can perform queries by putting together the right URL.

For example, if you want to get a list of the movies that were released after 2010 and that had an average rating greater than 4 then you can enter the following URL in the address bar of your browser:

http://odata.netflix.com/Catalog/Titles?$filter=ReleaseYear gt 2010&AverageRating gt 4

Entering this URL returns the movies in Figure 2.

clip_image004

Creating the Movie Lookup Form

The complete code for the Movie Lookup form is contained in Listing 1.

Listing 1 – MovieLookup.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Netflix with jQuery</title>
    <style type="text/css">
        #movieTemplateContainer div
        {
            width:400px;
            padding: 10px;
            margin: 10px;
            border: black solid 1px;
        }

    </style>

    <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js" type="text/javascript"></script>
    <script src="App_Scripts/Microtemplates.js" type="text/javascript"></script>
</head>
<body>


<label>Search Movies:</label>
<input id="movieName" size="50" />
<button id="btnLookup">Lookup</button>


<div id="movieTemplateContainer"></div>

<script id="movieTemplate" type="text/html">
    <div>
        <img src="<%=BoxArtSmallUrl %>" />
        <strong><%=Name%></strong>
        <p>
        <%=Synopsis %>
        </p>
    </div>
</script>


<script type="text/javascript">

    $("#btnLookup").click(function () {

        // Build OData query
        var movieName = $("#movieName").val();
        var query = "http://odata.netflix.com/Catalog" // netflix base url
            + "/Titles" // top-level resource
            + "?$filter=substringof('" + escape(movieName) + "',Name)"  // filter by movie name
            + "&$callback=callback" // jsonp request
            + "&$format=json"; // json request

        // Make JSONP call to Netflix
        $.ajax({
            dataType: "jsonp",
            url: query,
            jsonpCallback: "callback",
            success: callback
        });

    });

    function callback(result) {
        // unwrap result
        var movies = result["d"]["results"];

        // show movies in template
        var showMovie = tmpl("movieTemplate");
        var html = "";
        for (var i = 0; i < movies.length; i++) {
            // flatten movie
            movies[i].BoxArtSmallUrl = movies[i].BoxArt.SmallUrl;

            // render with template
            html += showMovie(movies[i]);
        }
        $("#movieTemplateContainer").html(html);
    }

</script>

</body>
</html>

The HTML page in Listing 1 includes two JavaScript libraries:

<script src=”http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js” type=”text/javascript”></script>

<script src=”App_Scripts/Microtemplates.js” type=”text/javascript”></script>

The first script tag retrieves jQuery from the Microsoft Ajax CDN. You can learn more about the Microsoft Ajax CDN by visiting the following website:

http://www.asp.net/ajaxLibrary/cdn.ashx

The second script tag is used to reference Resig’s micro-templating library. Because I want to use a template to display each movie, I need this library:

http://ejohn.org/blog/javascript-micro-templating/

When you enter a value into the Search Movies input field and click the button, the following JavaScript code is executed:

// Build OData query
var movieName = $("#movieName").val();
var query = "http://odata.netflix.com/Catalog" // netflix base url
    + "/Titles" // top-level resource
    + "?$filter=substringof('" + escape(movieName) + "',Name)"  // filter by movie name
    + "&$callback=callback" // jsonp request
    + "&$format=json"; // json request

// Make JSONP call to Netflix
$.ajax({
    dataType: "jsonp",
    url: query,
    jsonpCallback: "callback",
    success: callback
});

This code Is used to build a query that will be executed against the Netflix Catalog API. For example, if you enter the search phrase King Kong then the following URL is created:

http://odata.netflix.com/Catalog/Titles?$filter=substringof(‘King%20Kong’,Name)&$callback=callback&$format=json

This query includes the following parameters:

  • $filter – You assign a filter expression to this parameter to filter the movie results.
  • $callback – You assign the name of a JavaScript callback method to this parameter. OData calls this method to return the movie results.
  • $format – you assign either the value json or xml to this parameter to specify how the format of the movie results.

Notice that all of the OData parameters — $filter, $callback, $format — start with a dollar sign $.

The Movie Lookup form uses JSONP to retrieve data across the Internet. Because WCF Data Services supports JSONP, and Netflix uses WCF Data Services to expose movies using the OData protocol, you can use JSONP when interacting with the Netflix Catalog API. To learn more about using JSONP with OData, see Pablo Castro’s blog:

http://blogs.msdn.com/pablo/archive/2009/02/25/adding-support-for-jsonp-and-url-controlled-format-to-ado-net-data-services.aspx

The actual JSONP call is performed by calling the $.ajax() method. When this call successfully completes, the JavaScript callback() method is called.

The callback() method looks like this:

function callback(result) {
    // unwrap result
    var movies = result["d"]["results"];

    // show movies in template
    var showMovie = tmpl("movieTemplate");
    var html = "";
    for (var i = 0; i < movies.length; i++) {
        // flatten movie
        movies[i].BoxArtSmallUrl = movies[i].BoxArt.SmallUrl;

        // render with template
        html += showMovie(movies[i]);
    }
    $("#movieTemplateContainer").html(html);
}

The movie results from Netflix are passed to the callback method. The callback method takes advantage of Resig’s micro-templating library to display each of the movie results. A template used to display each movie is passed to the tmpl() method. The movie template looks like this:

<script id="movieTemplate" type="text/html">
    <div>
        <img src="<%=BoxArtSmallUrl %>" />
        <strong><%=Name%></strong>
        <p>
        <%=Synopsis %>
        </p>
    </div>
</script>

 

This template looks like a server-side ASP.NET template. However, the template is rendered in the client (browser) instead of the server.

Summary

The goal of this blog entry was to demonstrate how well jQuery works with OData. We managed to use a number of interesting open-source libraries and open protocols while building the Movie Lookup form including jQuery, JSONP, JSON, and OData.


Discussion

  1. Praveen Prasad says:

    this templating is not good.
    suppose we want to create very simple template in which there are 2 items -place holder to display name, a button to show/hide name. Now for this functionality. we have to bind methods to target element every time binding occurs.
    –while an ideal template should be pure html and with all events bound to it.
    –binding should be done like below
    $(‘#targetElm’).bindJson(‘templateId’,data);
    –now target will have filed templates with no need to bound events again to them.
    this way once created template can be reused again and again. this way we can do things like create an accordion template and recreate new accordion just by binding with new data and so on.

  2. Roland says:

    Stephen, can you check the line 49 of the listing 1. There is something that might be awkward:

    + “&$callbackcallback=callback” // jsonp request

    Regards,

    R.

  3. @Roland — thanks! That appears to be a bug in the syntax highlighter that causes callback to appear twice, the code should be:

    + “&$callback=callback” // jsonp request

    This line of code is critical because it tells the WCF Data Service to support JSONP (otherwise, you’ll get a missing : exception in IE or an invalid label exception from the Firefox).

  4. tperri says:

    @Praveen This is just a small sample application, and is great for getting a start.

    The more customizable functionality described is saved for you to do yourself :)

    Thanks Stephen for this post! :)

  5. JonW says:

    Put Scripts at Bottom:

    I’ve commented on this topic before…
    But one should really as a evangelist try to use best practice (as people tend to use code showed as presented)

    Therefore it is of IMPORTANCE that you (and other) when displaying javascript demos follow best practice.

    Place javascript includes
    developer.yahoo.com/…/rules.html#js_bottom

  6. Mark says:

    Ease up JonW, scripts should be at the bottom agreed, but in the example it has no relevance, there would be extremely little advantage to doing so.

  7. JonW says:

    Mark
    I tend to stongly disagree with you.

    1) People tend to use code showed AS presented.
    Even in this simple demo one could easily follow that simple best practice and pherhaps Kill two birds with one stone

    And complete with,
    2) there is absolutly NO ADVANTAGE in placing the script in the head section, so why do it in the 1:st place?

  8. You have prearranged outstanding inspiration here.

  9. Andrew says:

    Dear Stephen Walther,

    Please give clear information about MicrosoftAjax.js status,

    Can we assume all *.js that is included on asp.net MVC 2 is supported ?

    calling WCF service is easier at microsoft ajax libary than using jQuery
    creating Object oriented stuff/model @mvvm pattern also easier at microsoft ajax library
    we love stringBuilder, Array extension, asp.net authentication

    however,
    animation is better at jQuery, some other stuff, dom manipulation, dom transversal

    your statement that microsoft ajax client side is shifted to jQuery is too confusing, stuff that i just mention is client side i think

    script id="movieTemplate" type="text/html"!!! THIS THING IS NOT STANDARD AND INTRODUCE BUG IN FUTURE, DONT CARE WHATEVER browser did which such tag at this moment

    I better use StringBuilder from microsoft ajax library, to build the template/generate html stuff then inject it using jQuery into DOM

    dont u see how nice is microsoft jax and jQuery together ???

    sorry for a long post