Advertisement

Create an Amazon Books Widget with jQuery and XML

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

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.

Preface

One thing to remember as you seek to try this out on your own is that Internet Explorer security settings do not allow you to make XmlHttpRequest calls from the local file system. Even though you are not using a server-side language, you still need to run the source code from a Web server like Apache's HTTP Server. Uploading the files to a Web hosting account would work as well.

This tutorial is using the minified jQuery 1.2.6 core JavaScript file, which can be downloaded here from Google Code. No other plugins are necessary. Here is a screenshot of the widget in its final form:

Step 1: Dissecting the Interface

I created the following graphic with Illustrator, and this is the framework for the books widget. The final sliced images can be replaced easily to create design elements that suit your needs. The illustration includes the next and previous buttons, as well as the container for the book images. The source ZIP file contains a layered EPS if you would like to make edits without having to start from scratch.

One thing I wanted to do with this widget was to make sure it was flexible enough for just about any column size. That meant that it not only needed to be a fluid width, but it also needed to accept pixel units of measurement. Books can wrap gracefully to multiple rows spaced evenly apart, down to a single column, or can span across in a single row as wide as you like. This next screenshot helps to visualize how that will happen.

The light pink solid blocks are to demonstrate the image slicing grid. There are the two buttons, as well as four corners, the top and bottom background, and then the left and right background. The dark pink solid lines are to demonstrate containment blocks, which will eventually end up as a few divs and an unordered list. To allow for a fluid layout an inner div will contain the left side background, and the unordered list will be nested within this parent div, which will contain the right side background.

Step 2: 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>

There is nothing groundbreaking about the HTML, but I would like to point out a few things. The first is with regard to the "overclear" class that appears on several elements. This is an excellent method for clearing floats without the need for additional markup. I discuss this technique in a blog post titled Six indispensable CSS tips and tricks I use on every project. By declaring a width, and setting the overflow property to hidden on a parent div, child elements that are floated will no longer need a trailing element with the clear property.

The second thing I would like to point out is the loader list item. Since I am going to grab all of the books in the XML file at once, the loader should appear immediately. I generated a loader from Ajaxload, and then centered it as a background. When the XML is finished loading, I remove the list item from the DOM, and the loader disappears. Here is a screenshot of what it looks like with just the HTML and CSS applied.

The widget has a fixed width, and for this tutorial it will be a single row with four books displayed at each view. If no width was applied, then it would span the full length of the page, or the width of its parent container.

Step 3: 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;
}

There is one exception, and that is the informational tool tip applied to a book when you roll over the information icon. Each tool tip contains the class "books_tool_tip", and are child elements of the page body. These are positioned with JavaScript according to the mouse position at the moment a user moves the cursor onto the icon.

The "books_pointer_left" and "books_pointer_right" classes manage the arrows that are associated with the book details tool tip. The tool tip falls to the right of the cursor, but if it is outside of the viewable browser window (called the viewport), then it will shift to the left side. The classes are swapped, and the arrow shifts to the opposite side as well. This allows you to place the widget in a left column or right column layout.

Step 4: 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[JavaScript: 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 5: 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 JavaScript:

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 );
		}
	};
}();

I will not cover every piece of functionality, but I did want to highlight a few very important aspects of the script -- the first being the "loadXml" method. This is one of jQuery's AJAX utilities, and one of the easiest AJAX implementations to use. You can read more about it in the official documentation. After retrieving an XML file, many developers will perform actions on the data all within the success portion of the call. This is difficult to troubleshoot, and I prefer to pass that data outside to other methods to act upon it. This is thinking in object-oriented terms, and it can be a good habit.

The "preloadBooks" method is what parses the XML data, and turns each node into relevant XHTML, including both a book and a book's tool tip. The great thing about jQuery is that XML nodes can be treated just like HTML nodes. You do not have to learn two styles of syntax, with the only caveat being that you have to use jQuery's text() method to grab content between a start and end tag. With HTML, you would use the html() method.

There is a large portion of the HTML that needs to be built through JavaScript. This often involves string concatenation. The traditional approach is to use the addition arithmetic operator, but a faster approach is to place portions of a string inside an array, and then join them. I do this in several places, and especially when it happens continuously throughout a loop, then this is the preferred style.

Now that the books HTML has all been inserted into the DOM, it is time to attach the appropriate events for browsing. The action of browsing happens in the "browseBooks" method. This method accepts the "browse" parameter, which takes one of two arguments, "prev" or "next". This is not a scrolling action, but a fade in/fade out transition. The method will establish the first (current) position, the maximum number of books to browse, the number of books left to browse, and then it will perform the transition. This also helps to determine when the previous or next buttons should be displayed in order to restrict users from browsing beyond the number of books listed.

The tool tip involves a small amount of custom JavaScript, and I wanted to describe two functions -- the "getMouseCoord" and "getViewport" methods. These are cross-browser implementations for determining the mouse position in accordance with how far the page has scrolled up/down or left/right. You should never have to edit these, and I have successfully used them on several projects without any issue. The "show" method also handles the scenario I described earlier, when the tool tip falls outside of the calculated viewport.

Step 6: The Final Widget

Final Product

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>
jQuery Amazon Book List

Enjoy your Amazon.com books widget!

  • Subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.


Advertisement