Previous Page
Next Page

Hack 54. Create a Drag-and-Drop Bookstore

Set up a book shelf from which users can drag books into a basket, with their choices logged on a server.

This hack allows the user to drag an image of a book from one area of the page into another region of the view called the "basket." The book is then processed as though it was being purchased, and a small message appears from the server. The application sends the book information as an Ajax-style request, and the rest of the view does not change as this transaction takes place. The hack uses the Rico library's drag-and-drop functionality. This open source JavaScript library makes it fairly easy to designate some regions of the page as "drop zones" and other page elements as "draggable." Rico takes care of the underlying graphical programming, which is a real win for the developer.

Figure 6-6 shows what the hack looks like in the Mac OS X version of Firefox 1.5.

Figure 6-6. Tasteful book selection

When the user selects a book from the book shelf and drags it into the basket, the page sends the book information to the server, and a reply message appears. Figure 6-7 shows what happens when you drag the Google Maps Hacks book into the basket.

Figure 6-7. The checkout commences on Google Maps Hacks

Here's the code for the web page:

    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <script src="js/prototype.js" type="text/javascript"></script>
    <script src="js/rico.js" type="text/javascript"></script>
    <script src="js/mydraggable.js" type="text/javascript"></script>
    <script src="js/talkdrop.js" type="text/javascript"></script>
    <style type="text/css">
        @import "/parkerriver/stylesheets/hacks.css";
    <title>Add books to the Basket</title>
<div id="shelf"  class="shelf" style="background-color: #6198C4">
<div id="title" class="title">Book Shelf</div>
<div id="b1" class="book_con"><img src=
  "img/books/0596100949_xs.gif"  id="Beyond Java" alt="Beyond Java"/></div>
<div id="b2" class="book_con"><img src=
  "img/books/0596101422_xs.gif" id="Java Enterprise" alt="Java Enterprise"/></div>
<div id="b3" class="book_con"><img src=
  "img/books/0596101651_xs.gif" id="Greasemonkey Hacks" alt="Greasemonkey Hacks"/>
<div id="b4" class="book_con"><img src=
  "img/books/0596101899_xs.gif"  id="Skype Hacks" alt="Skype Hacks"/></div>
<div id="b5" class="book_con"><img src=
  "img/books/jsvltjspckbk.s.gif" id="Java JSP Cookbook" alt=
  "Java JSP Cookbook"/></div>
<div id="b6" class="book_con"><img src=
  "img/books/googlemapshks.s.gif" id="Google Maps Hacks" alt=
  "Google Maps Hacks"/></div>
<div style="float: left;">&nbsp;&nbsp;</div>
<div id="basket" class="basket" style="background-color: #ffD800">
<div id="shelftitle" class="title">Basket</div>
<div style="clear: both;">&nbsp;</div>
<div id="outcome" class="msg" style=
  "clear: both; font-size: 1.2em; color: green "></div>

The page imports four JavaScript files, beginning with prototype.js and rico.js. The Rico library depends on Prototype, as discussed in "Use Prototype's Ajax Tools with Your Application" [Hack #50]. The third imported file, mydraggable.js, is a JavaScript file that encapsulates an object definition. This object defines a page control or widget that can be dragged. I'll show and explain that one in a moment. Finally, talkdrop.js contains the JavaScript that uses the object defined in mydraggable.js.

The regions of the page comprising the book shelf and basket are div elements that are styled using a stylesheet in /parkerriver/stylesheets/hacks.css. Here is the key part of this stylesheet:

msg {font-size: 0.8em;
      margin-bottom: 0.5em;
      margin-left: 0.5em;}
div.title { font-family: Times, Verdana,Arial;
            font-size: 1.4em; color: purple;
            vertical-align: top;
            margin-top: 0.5em;
            margin-left: 0.5em;}
div.shelf { width: 320px;
            height: 320px;
            border: solid medium;
            float: left;}
div.basket { width: 320px;
             height: 320px;
             border: solid medium;
             float: left;}
div.book_con { float: left;
               padding: 0.2em 0.2em;}

Draggables and Drop Zones

Now it's time to look at the code for this hack. When the web page is loaded, the code designates the div elements that contain books as draggable and the div elements that represent the shelf and basket as drop zones. The new MyDraggable sections refer to objects defined in mydraggable.js:

window.onload=function(  ){
    dndMgr.registerDraggable( new MyDraggable('b1','firstbook') );
    dndMgr.registerDraggable( new MyDraggable('b2','book2') );
    dndMgr.registerDraggable( new MyDraggable('b3','book3') );
    dndMgr.registerDraggable( new MyDraggable('b4','book4') );
    dndMgr.registerDraggable( new MyDraggable('b5','book5') );
    dndMgr.registerDraggable( new MyDraggable('b6','book6') );
    dndMgr.registerDropZone( new Rico.Dropzone('basket') );
    dndMgr.registerDropZone( new Rico.Dropzone('shelf') );

The emphasized code is all that is necessary to designate an element as a drop zone. Of course, the objects themselves have to be registered as draggable, or there will be nothing to drop into these hot zones. If you just want to get started with basic drag-and-drop functionality, this code will suffice:

dndMgr.registerDraggable( new Rico.Draggable('firstbook','b1') );

The dndMgr object doing the registering in this design pattern has already been instantiated for you by the Rico package. The first parameter to the Rico.Draggable constructor is a name that the code gives the object; the second is the element's id attribute value on the web page.

The parameters are switched in the MyDraggable constructor; the id of the div is first, and the name is second:

new MyDraggable('b6','book6');

Our hack, however, is designed to do a little bit more than just allow users to drag objects on the page to new locations. The hack wants to identify the objects only when they are dropped into the "basket" drop zone, then make an Ajax connection to communicate the identities of these objects to the server. Therefore, this hack extends Rico.Draggable so that the object can implement these other tasks.

This object is derived from instructions and code explained on the demo page found at

To extend the Rico object, the code uses a popular function of the Prototype library called Class.create( ). If the code creates a JavaScript object using this method, the new object has an initialize( ) method that gets called when the object is created (this feature is not built into JavaScript itself). Prototype also includes an oft-used extension called Object.extend( ) that, in this case, adds the newly declared functions to Rico.Draggable's existing methods:

var MyDraggable = Class.create(  );
MyDraggable.prototype = (new Rico.Draggable(  )).extend( {

    initialize: function( htmlElement, name ) {
        this.type        = 'MyDraggable';
        this.htmlElement = $(htmlElement);
        this.originZone  = "not defined";
    //return the parentNode id, or an alternative if
    //the parentNode does not have a valid id
    getContainer: function(  ) {
        var el = this.htmlElement;
        if(el.parentNode) {
            } else {
                return "no_id_"+el.parentNode.nodeName;
        } else {
    //store the origin of the drag as in "shelf"
    //We'll only make an Ajax request if the origin
    //is "shelf"
    startDrag: function(  ) {
        this.originZone=this.getContainer(  );
    //We'll only make an Ajax request if the origin
    //is "shelf" and the drop zone is "basket"
    endDrag: function(  ) {
        if(this.originZone == "shelf" &&
                this.getContainer(  ) == "basket"){
            var bk=this.htmlElement.childNodes[0].id;
            new Ajax.Request("/parkerriver/s/checkout", {method: "get",
                    parameters: "book="+bk,


} );

The methods are callback functions; in other words, the Rico library calls these methods at different stages of the object's drag behavior. The request trigger is when an object is dragged from the shelf to the basket and then dropped there. Watch what happens when you drag a book to the basket but do not drop it: an animation occurs (the zone darkens and shifts a bit), but the code does not send a request.

This application does not work in the Safari browser. According to its web page, Rico does not yet fully support Safari.

The code uses Prototype's Ajax.Request object, which makes it very easy to put together an Ajax-style request (see "Use Prototype's Ajax Tools with Your Application" [Hack #50]). Ajax.Request makes an asynchronous request by default.

Grabbing the Book Titles

How does the code get the book title to pass along to the server? The draggable objects are div tags containing the book's image. This code gets the book's title:

var bk=this.htmlElement.childNodes[0].id;
//we could also use this, if it is supported in the 
//major browsers
var bk=this.htmlElement.childNodes[0].alt;

this.htmlElement refers to the div element; its first (and only) child node is the image. The code then gets the value of the image's id, such as Google Maps Hacks.

Hacking Draggables

There's room to enrich the behavior of this hack. Just because the user drags a book into the basket does not mean that the user wants to check out right away. We could have other drop zones, such as "Wish List" or "Final Checkout," each doing something unique when the book is dropped into it. Obviously, if this code went beyond a fun hack, the server component would do a lot more than send simple response messages.

Previous Page
Next Page