Sunday, May 2, 2010

Build your own Bing image search with the Bing API and jQuery

One of the most popular parts of Bing is its image search with its never-ending scroll feature. In this post I'll show you my own implementation of a Bing-like image search using the Bing API and jQuery.



The Bing API

If you missed it, in March I did a series on the Bing API. Use this post to get started!

jQuery and the EndlessScroll plugin

There are two libraries I used: jQuery and the EndlessScroll plugin. I downloaded the EndlessScroll plugin here. Documentation can be found here.

Implementation

You can download the source here. The implementation can also be found below.

   1:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2:  <html xmlns="http://www.w3.org/1999/xhtml">
   3:      <head>
   4:          <title>Bing Image Search with jQuery and the Bing API</title>
   5:          <style type="text/css">
   6:              td { padding: 20px 20px 20px 20px }
   7:              
   8:              #Query { font-size: 16pt }
   9:              #Search    { font-size: 16pt }        
  10:          </style>
  11:          <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
  12:          <script type="text/javascript" src="scripts/jQuery_EndlessScroll.js"></script>
  13:          <script type="text/javascript">                
  14:              //The offset indicates how far into the Bing API result set we are processing.
  15:              //We need this variable to be global so we can maintain the state of the offset between Bing API calls.
  16:              var _offset;                                       
  17:              
  18:              $(document).ready(function(){
  19:                  //Hook up an onclick eventhandler to the Search button.
  20:                  $("#Search").click(StartSearching);
  21:              });                
  22:              
  23:              function StartSearching() {    
  24:                  //Clear the previous results from the Results table.
  25:                  ClearResults();
  26:                  
  27:                  //Reset the offset.
  28:                  ResetOffset();
  29:              
  30:                  //Get the value from the Query textbox.
  31:                  query = $("#Query").val();
  32:              
  33:                  //Start receiving the first results and hookup endless scrolling.
  34:                  ReceiveAndShowResults(query);
  35:                  HookUpEndlessScroll(query);
  36:              }        
  37:                          
  38:              function ClearResults(){
  39:                  //Remove the content of the Results table.
  40:                  $("#Results").find("*").remove();
  41:              }
  42:              
  43:              function ResetOffset(){                
  44:                  //Reset the offset.
  45:                  _offset = 0;
  46:              }
  47:                          
  48:              function HookUpEndlessScroll(query){    
  49:                  //Hookup endless scrolling.
  50:                  $(document).endlessScroll({
  51:                      fireOnce: false,
  52:                      fireDelay: false,
  53:                      loader: "Loading..",
  54:                      callback: function(){        
  55:                          //Up the offset.
  56:                          _offset += 25;
  57:                          
  58:                          //Show more results.
  59:                          ReceiveAndShowResults(query);
  60:                      }
  61:                   });
  62:              }
  63:   
  64:              function ReceiveAndShowResults(query) {            
  65:                  //Build a new API uri.
  66:                  var bingUri = BuildBingApiUri(query, _offset);        
  67:                  
  68:                  //Make the API call.        
  69:                  $.ajax({
  70:                      url: bingUri,
  71:                      success: OnResultsReceived,
  72:                      error: OnError
  73:                   });           
  74:              }
  75:   
  76:              function OnError(XMLHttpRequest, textStatus, errorThrown) {
  77:                  //Show an error when the API call fails.
  78:                  $("#Results").append("<tr><td>Something went wrong..</td></tr>");    
  79:                  $("#Results").append("<tr><td>" + textStatus + "</td></tr>");    
  80:              }
  81:   
  82:              function OnResultsReceived(data, textStatus) {                
  83:                  //If there are no results show a message.
  84:                  if (data.SearchResponse.Image.Results == null || data.SearchResponse.Image.Results == undefined){
  85:                      $("#Results").append("<tr><td>No Results..</td></tr>");    
  86:                      return;
  87:                  }                    
  88:                  
  89:                  //Loop over the results and build the Results table.
  90:                  $.each(data.SearchResponse.Image.Results, function (i, item) {                                        
  91:                      switch (true){
  92:                          case i == 0:
  93:                              $("#Results").append("<tr>");                                        
  94:                              $("#Results").append("<td><img src='" + item.Thumbnail.Url + "'/></td>");
  95:                              break;
  96:                          case i == data.SearchResponse.Image.Results.length:
  97:                              $("#Results").append("<td><img src='" + item.Thumbnail.Url + "'/></td>");
  98:                              $("#Results").append("</tr>");        
  99:                              break;
 100:                          case ((i + 1) % 5) == 0:
 101:                              $("#Results").append("<td><img src='" + item.Thumbnail.Url + "'/></td>");
 102:                              $("#Results").append("</tr><tr>");        
 103:                              break;
 104:                          default:
 105:                              $("#Results").append("<td><img src='" + item.Thumbnail.Url + "'/></td>");    
 106:                              break;
 107:                      }                                
 108:                  });              
 109:              }
 110:   
 111:              function BuildBingApiUri(query, offset) {
 112:                  //Build an uri for the Bing API call.                                
 113:                  var bingApiUrl = "http://api.search.live.net/json.aspx";
 114:                  var bingApiAppId = "3465F86307063B7ADC5D16E6A608A110E437E925";
 115:                  var bingApiImageCount = "25";                
 116:                  
 117:                  var s = bingApiUrl +
 118:                              "?AppId=" + bingApiAppId +
 119:                              "&Sources=image" +
 120:                              "&Query=" + query +
 121:                              "&Image.Count=" + bingApiImageCount +
 122:                              "&Image.Offset=" + offset;
 123:                                  
 124:                  return s;                
 125:              }
 126:         </script>
 127:      </head>
 128:        <body>
 129:          <div id="SearchContainer" align="center">
 130:              <input type="text" id="Query"/>
 131:              <input type="button" value="Search!" id="Search"/>                
 132:          </div>        
 133:          <table id="Results">
 134:          </table>        
 135:      </body>
 136:  </html>


Disclaimer

I only got this to work on IE. Other browsers had problems making the cross domain API calls, although jQuery should be able to handle this on every browser. Anyone an idea? This implementation also isn't production ready. I'm pretty sure this code needs more logic handling exceptions.

Feedback

Being a jQuery novice, I definitely am looking for your feedback!

Edit: Check the comments for more information on the cross domain API calls!

9 comments:

  1. By default, the XHR doesn't work cross-domain. I'm surprised it even worked in IE. Maybe IE makes an exception for XHR calls to Bing?

    Anyway, to get this to work in other browsers you're going to have to look into leveraging JSONP rather than straight JSON.

    ReplyDelete
  2. I know, I read somewhere in the docs that jQuery.ajax() should be able to detect this automatically and simply make it work?

    ReplyDelete
  3. The documentation I am referring to can be found here: http://docs.jquery.com/Release:jQuery_1.2/Ajax#Cross-Domain_getJSON_.28using_JSONP.29

    ReplyDelete
  4. Hi Jef -

    The api documentation (http://api.jquery.com/jQuery.ajax/) states that jQuery code will attempt to intelligently guess the data type when none is specified. Perhaps the guess is only working in IE. Since you already know the type, I would add dataType: 'jsonp' to the settings object literal.

    ReplyDelete
  5. Thanks for your input! I did try that, but setting the dataType gives other problems. I do get a response though!

    The more I read about it, the more people I find having the same issues. JSONP looks like such a hacky protocol. I tend to lean towards creating your own server side proxy to serve normal JSON.

    A good example can be found in this blogpost: http://notetodogself.blogspot.com/2009/02/using-jquery-with-nyt-json-api.html

    ReplyDelete
  6. You can also find a working example without jQuery in the API documentation: http://www.bing.com/developers/s/API%20Basics.pdf

    ReplyDelete
  7. download link dead pls reupload..

    ReplyDelete
  8. I fixed the download link. The solution seems to be broken in all browsers now though. I guess the latest version of jQuery has something to do with it. If you get this example to work (using jsonp) please report back.

    ReplyDelete
  9. This seems to work for me in all browsers...

    http://www.9lessons.info/2011/02/bing-instant-search-with-jquery-and.html

    ReplyDelete