Dojo: Meet Google Book Search

For those of you who read about the re-launching of the SFU Bookswap website (http://sfubookswap.com), you may remember that one of the new features was the integration of the Google Books database.

In this entry I am going to talk about how I integrated the Google Book Search functionality into my existing Dojo framework.

First things first: you need to sign up for a Google AJAX Search API key. You can get your key, as well as a description of the API and documentation from Google here: http://code.google.com/apis/ajaxsearch/signup.html.

Google offers a Search container, which allows the integration of several search sources, including the web, images, blogs, news items and books. I only wanted the Book source so I chose to take the simple route and not use the container class. Instead I used the GbookSearch object directly, which allows me to access the data more easily as well as utilize only the desired functionality.

The very first step in integrating the Google Books search results is including the Google class on your page:

<script type="text/javascript" src="http://www.google.com/jsapi?key=<yourapikey>"></script>

Next, we need to ensure that the Google Search namespace is loaded so we have access to the searching functions. This is as simple as including script tags with the following

google.load("search","1");

Because objects are swell, and will allow us to instantiate multiple searches on the same page, we are going to encapsulate the Google Book Search object in a wrapper object which exposes only the functionality we desire.

dojo.declare("GoogleBookSearch", null, {
    constructor: function(){
        this._bookSearch = new GbookSearch();
        this._bookSearch.setNoHtmlGeneration();
        dojo.connect(GbookSearch,"RawCompletion",this,"resultCallback");
    },
    query: function(q){
        this._bookSearch.execute(q);
    },
    resultCallback: function(){
        console.debug("Got Data Back, Lets Take A look:");
        console.dir(this._bookSearch.results);
        for(var x=0; x<this._bookSearch.results.length; x++){
            document.body.appendChild(this._bookSearch.results[x].html);
        }
    },
    goToPage: function(p){
        this._bookSearch.gotoPage(p);
    },
    getNextPage: function(){
        this._bookSearch.gotoPage(this._bookSearch.cursor.currentPageIndex+1);
    }
});

The first line of this class (the dojo.declare) creates a new GoogleBookSearch object, which does not inherit from any object (the null parameter), which has several functions and attributes.

The Constructor Function:

The constructor function sets up the search object by creating a new GbookSearch instance. To gain a slight speed improvement, we disable HTML generation, which will prevent the GbookSearch class from downloading images and generating Google’s HTML representation of the search results. Finally we connect the “RawCompletion” event that is fired by the GbookSearch object to a callback function in the local object. After every search query is completed, this event is fired by the global object.

The rest of the functions are fairly self explanatory: the query function accepts one argument, which is the query from the user. The query can be an ISBN, author, title or any other combination of book properties that Google stores in their book database. The resultCallback function processes the data after the GbookSearch class has received search results from the server. Notice that they do not come in the form as an argument to the function but, rather, are accessed by iterating through the _bookSearch.results array. The gotoPage and getNextPage move to the next page of search results (as only 4 are returned by default). No other code is needed, as after getting the results for the next page, the “RawCompletion” event is fired once again.

To use your shiny new class, simply instantiate it after the page has loaded. So, for example:

dojo.addOnLoad(function(){
    myBookSearch = new GoogleBookSearch();
});

Make sure you don’t put a var in front of bookSearch, otherwise the object will have only local visibility, and be removed after the function has executed.

Tips and Tricks:

Modifying the callback function.

In my case, I wanted to have a default callback function that is executed when the search results are returned. However, I wanted separate instances of the GoogleBookSearch object to handle search results differently. To do this, we want to “overload” the resultCallback function using dojo’s hitch functionality.

dojo.addOnLoad(function(){
    myBookSearch = new GoogleBookSearch();
    myBookSearch.resultCallback = dojo.hitch(this,function(){
        if( myBookSearch._bookSearch.results.length>0){
           alert('Showing '+myBookSearch ._bookSearch.results.length+' of '+ myBookSearch._bookSearch.cursor.estimatedResultCount +' Results');
        }
    });
});

The dojo.hitch function accepts two arguments, a function or method name as the second argument, and the scope in which the method executes as the first argument. It is crucial that this scope is set correctly as the default behaviour is to execute in the scope of the GoogleBookSearch object which may be ok if only global or Google BookSearch attributes and functions are used, but is insufficient if we are wrapping the GoogleBookSearch object inside another object and want the callback to be defined as a function in the outer object.

Cleaning Up The Results

When returning the results, if any of the attributes of the book were a keyword in the search, for example, the author field contained a keyword, then the keyword in the author field would be enclosed in bold tags. In my application I am importing data into a database so I want it to be as clean as possible. My solution is using a regular expression to remove all HTML tags. For example

myBookSearch._bookSearch.results[0].authors.replace(/(<([^>]+)>)/ig,"")

Extracting the image of the book cover

You may notice that the book covers are dynamically generated for each query and expire after a short time. My workaround for this is to use regular expressions to parse out the book ID and then use the same (permanent) address that Google does when displaying the book covers on the book details page.

This can be accomplished as follows:

var imgUrl = "http://books.google.com/books?id="+/.*id=([^&]*)/g.exec(myBookSearch._bookSearch.results[0].tbUrl)[1]+"&printsec=frontcover&img=1&zoom=1"

The regular expression extracts the book ID from the thumbnail URL and combines it with the persistent book cover URL

There are a bunch of other attributes stored by the GbookSearch object. Look in the Google API to see a bunch of them or, alternatively, use Firebug to snoop the object after results have been returned.

Happy Coding!

External Links

Google AJAX Search: http://code.google.com/apis/ajaxsearch/

Google AJAX Search API: http://code.google.com/apis/ajaxsearch/documentation/reference.html

Firebug: www.getfirebug.com/