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 selectionWhen 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 HacksHere's the code for the web page: <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <script src="/books/4/254/1/html/2/js/prototype.js" type="text/javascript"></script> <script src="/books/4/254/1/html/2/js/rico.js" type="text/javascript"></script> <script src="/books/4/254/1/html/2/js/mydraggable.js" type="text/javascript"></script> <script src="/books/4/254/1/html/2/js/talkdrop.js" type="text/javascript"></script> <style type="text/css"> @import "/parkerriver/stylesheets/hacks.css"; </style> <title>Add books to the Basket</title> </head> <body> <div style="background-color: #6198C4"> <div >Book Shelf</div> <div ><img src="img/books/0596100949_xs.gif" alt="Beyond Java"/></div> <div ><img src="img/books/0596101422_xs.gif" alt="Java Enterprise"/></div> <div ><img src="img/books/0596101651_xs.gif" alt="Greasemonkey Hacks"/> </div> <div ><img src="img/books/0596101899_xs.gif" alt="Skype Hacks"/></div> <div ><img src="img/books/jsvltjspckbk.s.gif" alt= "Java JSP Cookbook"/></div> <div ><img src="img/books/googlemapshks.s.gif" alt= "Google Maps Hacks"/></div> </div> <div style="float: left;"> </div> <div style="background-color: #ffD800"> <div >Basket</div> </div> <div style="clear: both;"> </div> <div style= "clear: both; font-size: 1.2em; color: green "></div> </body> </html> 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 ZonesNow 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.
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.
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) { if(el.parentNode.id){ return el.parentNode.id; } else { return "no_id_"+el.parentNode.nodeName; } } else { return this.name+"_no_supported_parentNode"; } }, //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, onComplete:function(request){ $("outcome").innerHTML=request.responseText;}}); } } } ); 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.
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 TitlesHow 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 DraggablesThere'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. |