Submit your widget

Amazon Books Widget with jQuery and XML Image Scroller

Created 14 years ago   Views 20162   downloads 3425    Author n/a
Amazon Books Widget with jQuery and XML Image Scroller
View DemoDownload
135
Share |

It makes sense to forgo database tables and server-side code when you need to store a limited amount of non-sensitive data. Accessing this data can be a snap with jQuery because the library was built to traverse XML documents with ease. With some custom JavaScript and jQuery magic you can create some interesting widgets. A good way to demonstrate this functionality is by building a browsable Amazon.com books widget.

 

Step 1: The HTML

 

Before I get to the HTML, it is worthwhile to note that I am not using PNG files. You could very well substitute PNGs for the GIFs, and it would not affect the functionality. It would mean, however, you would need to implement a fix for the lack of PNG transparency support in Internet Explorer. There are several jQuery plugins that are available.

<!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" dir="ltr" lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Amazon.com Books Widget</title>
<link rel="stylesheet" href="css/books.css" type="text/css" media="screen" />
</head>
<body>

 <div id="books">
  <div class="overclear buttons">
   <a href="#" class="prev"><img src="images/books_prev.gif" width="40" height="30" alt="Previous" /></a>
   <div class="showing"><!-- showing --></div>
   <a href="#" class="next"><img src="images/books_next.gif" width="40" height="30" alt="Next" /></a>
  </div>
  <div class="overclear top">
   <img src="images/books_left_top.gif" width="20" height="20" alt="" class="float_left" />
   <img src="images/books_right_top.gif" width="20" height="20" alt="" class="float_right" />
  </div>
  <div class="inner">
   <ul class="overclear">
    <li class="loader"><!-- loader --></li>
   </ul>
  </div>
  <div class="overclear btm">
   <img src="images/books_left_btm.gif" width="20" height="20" alt="" class="float_left" />
   <img src="images/books_right_btm.gif" width="20" height="20" alt="" class="float_right" />
  </div>
 </div>

</body>
</html>

 

Step 2: The CSS

 

The CSS is fairly straightforward and self-explanatory, so I will not be spending a great deal of time explaining all the facets of each selector. Almost all selectors are child elements of the parent container with the identifier “books”. You can see that the width applied is optional. Removing it will allow the widget to expand and contract freely.

/* foundation */

body {
 font: 100% normal "Arial", "Helvetica", sans-serif;
}
#books {
 width: 515px; /* optional */
}
#books img {
 border: 0;
}
#books .clear_both {
 clear: both;
}
#books .float_left,
#books  ul li {
 float: left;
 display: inline;
}
#books .float_right {
 float: right;
}
#books .overclear {
 width: 100%;
 overflow: hidden;
}

/* styles */

#books .buttons {
 position: relative;
 height: 30px;
 margin: 0 0 5px 0;
}
#books .prev {
 position: absolute;
 top: 0;
 left: 0;
 visibility: hidden;
}
#books .next {
 position: absolute;
 top: 0;
 right: 0;
}
#books .showing {
 margin: 5px 60px 0 60px;
 text-align: center;
 font-size: .8em;
}
#books .top {
 background: url(../images/books_top.gif) repeat-x;
}
#books .inner {
 padding: 0 0 0 20px;
 margin: 0 0 -20px 0;
 background: url(../images/books_left_mid.gif) repeat-y;
}
#books  ul {
 margin: 0;
 padding: 0;
 list-style-type: none;
 background: url(../images/books_right_mid.gif) repeat-y top right;
}
#books  ul li {
 display: none;
 position: relative;
 margin: 0;
 padding: 0 20px 20px 0;
 font-size: .8em;
 z-index: 1;
}
#books  ul li.loader {
 display: block;
 float: none;
 height: 115px;
 margin: 0 0 20px -20px;
 background: url(../images/books_loader.gif) no-repeat center center;
}
#books  ul li a.info {
 position: absolute;
 bottom: 20px;
 right: 20px;
}
#books  ul li a.thumb {
 display: block;
 border: 1px solid #ddd;
}
#books  ul li a.thumb img {
 display: block;
 margin: 0;
 padding: 3px;
}
#books .btm {
 background: url(../images/books_btm.gif) repeat-x;
}
.books_tool_tip {
 display: none;
 position: absolute;
 top: 0;
 left: 0;
 width: 350px;
 z-index: 9999;
}
.books_tool_tip .books_pointer_left {
 position: absolute;
 top: 0;
 left: 0;
 width: 10px;
 height: 10px;
 background: url(../images/books_pointer_left.gif);
}
.books_tool_tip .books_pointer_right {
 position: absolute;
 top: 0;
 right: 0;
 width: 10px;
 height: 10px;
 background: url(../images/books_pointer_right.gif);
}
.books_tool_tip .inner {
 border: 1px solid #ddd;
 padding: 15px 15px 3px 15px;
 margin: 0 0 0 9px;
 background: #fff;
}
.books_tool_tip .inner_right {
 margin: 0 9px 0 0;
}
.books_tool_tip .inner p {
 font-size: .8em;
 margin: 0;
 padding: 0 0 12px 0;
}

 

Step 3: The XML

 

There is nothing revolutionary about this XML. As you will see, each book contains a title, author(s), an image, an Amazon URL, a reviews total, and a reviews average. The XML could be normalized in one area, and that is the “author” node. Strictly speaking, there can be several authors, and an author can be one of two types, an author or an editor. However, I kept it simple in order to focus on the core functionality. A good bit of homework would be to see how you could better optimize that node, and then successfully parse it with jQuery.

<?xml version="1.0" encoding="UTF-8"?>
<books>
 <book>
  <title><![CDATA[Design Patterns: Elements of Reusable Object-Oriented Software]]></title>
  <author>Erich Gamma, Richard Helm, Ralph Johnson, John M. Vlissides</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0201633612.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional/dp/0201633612/]]></href>
  <reviews>
   <total>250</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[The Pragmatic Programmer: From Journeyman to Master]]></title>
  <author>Andrew Hunt, David Thomas</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_020161622X.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Pragmatic-Programmer-Journeyman-Master/dp/020161622X/]]></href>
  <reviews>
   <total>131</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Refactoring: Improving the Design of Existing Code]]></title>
  <author>Martin Fowler, Kent Beck, John Brant, William Opdyke</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0201485672.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Refactoring-Improving-Existing-Addison-Wesley-Technology/dp/0201485672/]]></href>
  <reviews>
   <total>139</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Patterns of Enterprise Application Architecture]]></title>
  <author>Martin Fowler</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0321127420.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Enterprise-Application-Architecture-Addison-Wesley-Signature/dp/0321127420/]]></href>
  <reviews>
   <total>56</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Head First Design Patterns]]></title>
  <author>Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0596007124.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Head-First-Design-Patterns/dp/0596007124/]]></href>
  <reviews>
   <total>252</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Introduction to Algorithms]]></title>
  <author>Thomas Cormen, Charles Leiserson, Ronald Rivest, Clifford Stein</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0262032937.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Introduction-Algorithms-Thomas-Cormen/dp/0072970545/]]></href>
  <reviews>
   <total>167</total>
   <average_rating>4.0</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition (2nd Edition)]]></title>
  <author>Frederick P. Brooks</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0201835959.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Mythical-Man-Month-Software-Engineering-Anniversary/dp/0201835959/]]></href>
  <reviews>
   <total>128</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Effective Java (2nd Edition)]]></title>
  <author>Joshua Bloch</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0321356683.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Effective-Java-2nd-Joshua-Bloch/dp/0321356683/]]></href>
  <reviews>
   <total>120</total>
   <average_rating>5.0</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Mastering Regular Expressions]]></title>
  <author>Jeffrey Friedl</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0596528124.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124/]]></href>
  <reviews>
   <total>125</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Introduction to the Theory of Computation, Second Edition]]></title>
  <author>Michael Sipser</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0534950973.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Introduction-Theory-Computation-Second-Michael/dp/0534950973/]]></href>
  <reviews>
   <total>52</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Don't Make Me Think: A Common Sense Approach to Web Usability (2nd Edition)]]></title>
  <author>Steve Krug</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0321344758.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Dont-Make-Me-Think-Usability/dp/0321344758/]]></href>
  <reviews>
   <total>453</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[The Visual Display of Quantitative Information, 2nd edition]]></title>
  <author>Edward R. Tufte</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0961392142.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Visual-Display-Quantitative-Information-2nd/dp/0961392142/]]></href>
  <reviews>
   <total>96</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[[removed] The Definitive Guide]]></title>
  <author>David Flanagan</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0596101996.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/JavaScript-Definitive-Guide-David-Flanagan/dp/0596101996/]]></href>
  <reviews>
   <total>278</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Designing Interfaces: Patterns for Effective Interaction Design]]></title>
  <author>Jenifer Tidwell</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0596008031.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Designing-Interfaces-Patterns-Effective-Interaction/dp/0596008031/]]></href>
  <reviews>
   <total>47</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Universal Principles of Design]]></title>
  <author>William Lidwell, Kritina Holden, Jill Butler</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_1592530079.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Universal-Principles-Design-William-Lidwell/dp/1592530079/]]></href>
  <reviews>
   <total>54</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Ambient Findability: What We Find Changes Who We Become]]></title>
  <author>Peter Morville</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_0596007655.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Ambient-Findability-What-Changes-Become/dp/0596007655/]]></href>
  <reviews>
   <total>46</total>
   <average_rating>4.0</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[The Search: How Google and Its Rivals Rewrote the Rules of Business and Transformed Our Culture]]></title>
  <author>John Battelle</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_1591841410.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Search-Rewrote-Business-Transformed-Culture/dp/B000QRIHXE/]]></href>
  <reviews>
   <total>99</total>
   <average_rating>4.5</average_rating>
  </reviews>
 </book>
 <book>
  <title><![CDATA[Beginning PHP and MySQL 5 (2nd Edition)]]></title>
  <author>W. Jason Gilmore</author>
  <image width="95" height="115">
   <src><![CDATA[images/books/isbn_1590595521.jpg]]></src>
  </image>
  <href><![CDATA[http://www.amazon.com/Beginning-PHP-MySQL-Novice-Professional/dp/1590595521/]]></href>
  <reviews>
   <total>100</total>
   <average_rating>4.0</average_rating>
  </reviews>
 </book>
</books>

 

Step 4: The JavaScript

 

The JavaScript is certainly the most complicated portion of the tutorial. As best I can, I usually begin scripts like this by stubbing out the state and behavior of an object to get a feel for functionality. This particular object is simply called “BOOKS”. I also use what is known as the Module Pattern, which is detailed by Eric Miraglia on the Yahoo! User Interface Blog. This design pattern gives you the ability to create private methods and properties. Whenever releasing a script into the wild (like now), this pattern helps to eliminate the possibility of conflicts with other functions and objects other developers may already be using.

var BOOKS = function(){
 var _P = {
  init : function( params ) {},
  params : null,
  data : null,
  loadXml : function() {},
  first : 0,
  max : 0,
  count : 0,
  preloadBooks : function() {},
  browseBooks : function( browse ) {},
  tooltip : {
   show : function( e, $o ) {},
   hide : function( e, $o ) {},
   getMouseCoord : function( v, e ) {},
   getViewport : function() {}
  }
 };
 return {
  init : function( params ) {
   _P.init( params );
  }
 };
}();

 

All of my private members I placed inside of an object called “_P”. This has more to do with organizational efforts than anything. So long as a member is not in the BOOKS return statement, I could very well have created it as a standalone variable or function. Since I need a way to associate public parameters (settings) with private members, I have one public method. This public initialization method will pass the settings along to a private initialization method, acting as a go-between. I will revisit those settings in the final step.

Here is a look now at the final [removed]

var BOOKS = function(){
 var _P = {
  init : function( params ) {
   _P.params = params;
   _P.loadXml();
  },
  params : null,
  data : null,
  loadXml : function() {
   $.ajax({
    type : "GET",
    url : _P.params.xmlPath,
    dataType : "xml",
    success : function( data ) {
     _P.data = data;
     _P.max = _P.params.perView;
     _P.count = $( "book", data ).length;
     _P.preloadBooks();
     _P.browseBooks();
    }
   });
  },
  first : 0,
  max : 0,
  count : 0,
  preloadBooks : function() {
   $( "ul", "#books" ).empty();
   $( "book", _P.data ).each(function( i ) {
    var title = $.trim( $( "title", this ).text() );
    var href = $.trim( $( "href", this ).text() );
    $( "ul", "#books" ).append([
     "<li><a href='",
     href,
     "' class='info'><img src='",
     _P.params.imgPath,
     "/books_info.gif' width='15' height='16' alt='More Info' /></a><a href='",
     href,
     "' class='thumb'><img src='",
     $.trim( $( "image > src", this ).text() ),
     "' width='",
     $( "image", this ).attr( "width" ),
     "' height='",
     $( "image", this ).attr( "height" ),
     "' alt='",
     title,
     "' /></a></li>" ].join( "" ));
    $( "body" ).append([
     "<div class='books_tool_tip' id='books_tool_tip_",
     i,
     "'><div class='books_pointer_left'><!-- books pointer --></div><div class='inner'><p>",
     title,
     " (by <em>",
     $.trim( $( "author", this ).text() ),
     "</em>)",
     "</p><p><img src='",
     _P.params.imgPath,
     "/stars_",
     $.trim( $( "reviews > average_rating", this ).text() ),
     ,".gif' width='55' height='12' /> (",
     $.trim( $( "reviews > total", this ).text() ),
     ")",
     "</p></div></div>" ].join( "" ));
   });
   $( ".info", "#books" ).hover(function( e ) {
    _P.tooltip.show( e, $( "#books_tool_tip_" + $( "a.info", "#books" ).index( this ) ) );
   }, function( e ) {
    _P.tooltip.hide( e, $( "#books_tool_tip_" + $( "a.info", "#books" ).index( this ) ) );
   });
   $( "#books .prev" ).click(function() {
    _P.browseBooks( "prev" );
    return false;
   });
   $( "#books .next" ).click(function() {
    _P.browseBooks( "next" );
    return false;
   });
  },
  browseBooks : function( browse ) {
   if ( browse == "prev" ) {
    if ( _P.first == _P.count && ( _P.count % _P.max > 0 ) ) {
     _P.first = _P.first - ( ( _P.count % _P.max ) + _P.max );
    } else {
     _P.first = _P.first - ( _P.max * 2 );
    }
   }
   var range = _P.first + _P.max;
   var start = 1;
   if ( range > _P.max ) {
    start = ( ( range - _P.max ) + 1 );
   }
   if ( _P.first == 0 ) {
    $( "#books .prev" ).css( "visibility", "hidden" );
   } else {
    $( "#books .prev" ).css( "visibility", "visible" );
   }
   if ( range < _P.count ) {
    $( "#books .next" ).css( "visibility", "visible" );
   } else if ( range >= _P.count ) {
    range = _P.count;
    $( "#books .next" ).css( "visibility", "hidden" );
   }
   $( "book", _P.data ).each(function( i ) {
    if ( i >= _P.first && i < range ) {
     $( "#books li:eq(" + i + ")" ).fadeIn( "slow" );
    } else {
     $( "#books li:eq(" + i + ")" ).css( "display", "none" );
    }
   });
   _P.first = range;
   $( "#books .showing" ).html([
    "Viewing <strong>",
    start,
    " - ",
    range,
    "</strong> of <strong>",
    _P.count,
    "</strong>" ].join( "" ));
  },
  tooltip : {
   show : function( e, $o ) {
    var v = _P.tooltip.getViewport();
    var pageX = _P.tooltip.getMouseCoord( v, e )[0] + 15;
    var pageY = _P.tooltip.getMouseCoord( v, e )[1];
    $o.find( ".books_pointer_right" ).addClass( "books_pointer_left" ).removeClass( "books_pointer_right" );
    if ( pageX + $o.width() > v.innerWidth + v.pageXOffset ) {
     pageX = pageX - $o.width() - 30;
     $o.find( ".inner" ).addClass( "inner_right" );
     $o.find( ".books_pointer_left" ).addClass( "books_pointer_right" ).removeClass( "books_pointer_left" );
    }
    $o.css( "left", pageX ).css( "top", pageY ).css( "display", "block" );
   },
   hide : function( e, $o ) {
    $o.css( "display", "none" );
   },
   getMouseCoord : function( v, e ) {
    ( !e ) ? e = window.event : e = e;
    ( e.pageX ) ? v.pageX = e.pageX : v.pageX = e.clientX + v.scrollLeft;
    ( e.pageY ) ? v.pageY = e.pageY : v.pageY = e.clientY + v.scrollTop;
    return [ e.pageX, e.pageY ];
   },
   getViewport : function() {
    var viewport = {}
    if ( self.innerHeight ) {
     viewport.pageYOffset = self.pageYOffset;
     viewport.pageXOffset = self.pageXOffset;
     viewport.innerHeight = self.innerHeight;
     viewport.innerWidth = self.innerWidth;
    } else if ( document.documentElement && document.documentElement.clientHeight ) {
     viewport.pageYOffset = document.documentElement.scrollTop;
     viewport.pageXOffset = document.documentElement.scrollLeft;
     viewport.innerHeight = document.documentElement.clientHeight;
     viewport.innerWidth = document.documentElement.clientWidth;
    }
    return viewport;
   }
  }
 };
 return {
  init : function( params ) {
   _P.init( params );
  }
 };
}();

 

Step 5: The Final Widget

The last thing to do is to pass the settings through to the JavaScript initialization method from the HTML. There are three arguments: the path to the XML file, the path to the images used in the JavaScript, and the number of books you would like displayed per view. For this tutorial, it is assumed there is only one books widget per page (called “books”), which is why there is no parameter for ID or class name. Here is the XHTML in final form:

<!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" dir="ltr" lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Amazon.com Books Widget</title>
<link rel="stylesheet" href="css/books.css" type="text/css" media="screen" />
<script language="javascript" type="text/javascript" src="js/jquery-1.2.6.min.js"></script>
<script language="javascript" type="text/javascript" src="js/books.js"></script>
<script language="javascript" type="text/javascript">

 $(function(){
  BOOKS.init({
   xmlPath : "data/books.xml",
   imgPath : "images",
   perView : 4
  });
 });

</script>
</head>
<body>

 <div id="books">
  <div class="overclear buttons">
   <a href="#" class="prev"><img src="images/books_prev.gif" width="40" height="30" alt="Previous" /></a>
   <div class="showing"><!-- showing --></div>
   <a href="#" class="next"><img src="images/books_next.gif" width="40" height="30" alt="Next" /></a>
  </div>
  <div class="overclear top">
   <img src="images/books_left_top.gif" width="20" height="20" alt="" class="float_left" />
   <img src="images/books_right_top.gif" width="20" height="20" alt="" class="float_right" />
  </div>
  <div class="inner">
   <ul class="overclear">
    <li class="loader"><!-- loader --></li>
   </ul>
  </div>
  <div class="overclear btm">
   <img src="images/books_left_btm.gif" width="20" height="20" alt="" class="float_left" />
   <img src="images/books_right_btm.gif" width="20" height="20" alt="" class="float_right" />
  </div>
 </div>

</body>
</html>

 

Enjoy your Amazon.com books widget!