Making Draggables Making draggables is easy. The Draggable constructor (the initialize method that gets called when you instantiate a class) takes care of assigning the events that control drag- ging; it also handles communication with any droppables it encounters. The default options for Draggable enable an element to be moved anywhere on the page—but to make an element interact with potential drop targets, you’ll have to specify some options of your own. So let’s add some options. Reload the page, and then run this code in the console: $('container').select('li').each( function(li) { new Draggable(li, { revert: true }); }); Play around with the draggables and you’ll notice the difference: they now move back (or revert) to their original positions at the end of the drag. Another option controls how much freedom the draggable has in its movement. Reload and try this code: $('container').select('li').each( function(li) { new Draggable(li, { snap: 50 }); }); Notice that these draggables move far less fluidly. The 50 value for snap tells them to “snap” to points on the page that are separated by 50 pixels in both directions. Before, each movement of the mouse also moved the draggable; now, the draggable moves to the closest x/y coordinates that are both multiples of 50. We could also have specified two values for snap; [10, 50] creates a grid of points 10 pixels apart on the x axis and 50 points apart on the y axis. Better yet, we can set snap to a function that determines, on each movement of the mouse, where the draggable should snap to. Advanced usage of draggables will be covered later in the chapter. One other option deserves mention. The handle option lets you specify a smaller piece of the draggable—not the whole draggable—to serve as the drag-enabled area of the element. A drag operation intercepts all clicks and cancels the associated events, so handles can be used to enable dragging without interfering with form element inter- action, link clicking, and so on. CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES260 Let’s change part of our HTML to see this in action: <ul id="container"> <li id="item_1"><span class="handle">@</span> Lorem</li> <li id="item_2"><span class="handle">@</span> Ipsum</li> <li id="item_3"><span class="handle">@</span> Dolor</li> <li id="item_4"><span class="handle">@</span> Sit</li> <li id="item_5"><span class="handle">@</span> Amet</li> </ul> These spans will act as our “handles,” so let’s style them to stand out: #container .handle { background-color: #090; color: #fff; font-weight: bold; padding: 3px; cursor: move; } The standard “move” cursor (which looks like arrows in four directions on Windows, and a grabbing hand on the Mac) provides a visual clue to the user that the handles are clickable. Make these changes, reload the page, and run this in the console: $('container').select('li').each( function(li) { new Draggable(li, { handle: 'handle' }); }); You’ll be able to drag the items around using their handles, but the rest of the ele- ment won’t respond to clicking and dragging. Notice, for instance, how you can select the text inside each element—an action that was prev ented when the draggable hogged all the mouse actions on the element (see Figure 11-2). CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES 261 Figure 11-2. Draggables are now moved by their handles. Other Draggable Options I’ve covered the most important options for Draggable, but there are others you might find handy as well. The constraint Option The constraint option can restrict a draggable to movement along one axis. Setting it to "horizontal" limits the draggable to horizontal movement only; setting it to "vertical" limits it to vertical movement only. By default, this option is not set. This option is typi- cally used by sortables to enforce linear ordering of items. The ghosting Option The ghosting option controls how the draggable behaves while being dragged. When it is set to false, which is the default, dragging an element moves the element itself. When it CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES262 is set to true, dragging an element copies the element and moves the copy. At the end of the drag operation, this “ghost” element will be removed (but not before reverting to its original spot, if it needs to). I can think of two common scenarios in which ghosting should be enabled. One is when the drag implies duplicating an item, rather than moving it (i.e., the original item will remain when the drag is done). The other is when the tentativeness of the drag oper- ation needs to be emphasized. For instance, think of the Finder or Windows Explorer; files are ghosted upon drag because the drop could fail for a number of reasons. Perhaps the destination is read-only, or perhaps there are file name conflicts at the destination. Instead of the file being visually removed from one space, it remains there until the OS can be sure it can move the file to the destination folder. The zindex Option script.aculo.us sets a default CSS z-index value of 1000 on the dragged element; in most cases, this is a high enough value that the draggable will appear above everything else on the page. If your web app defines z-index values higher than 1000, you should use the zindex property to specify a higher value when you construct draggables. Start, End, and Revert Effects By default, an element that’s being dragged becomes translucent, as you may have noticed. This is the draggable’s start effect. The opacity change is a common pattern to signify something is being moved—both Windows Explorer and Mac OS X’s Finder employ it—but this effect can be customized if need be. To override the start effect, set the starteffect parameter to a function that takes an element as its first argument. That function will be called any time a drag starts. new Droppable('foo', { starteffect: function(element) { element.morph("background-color: #090"); } }); This should remind you of the callback pattern you’ve encountered already—most recently in Chapter 10 in the discussion on effects. There are two other kinds of effects: the end effect and the revert effect. The end effect, of course, is called when the drag ends—when the user releases the mouse and “drops” the draggable. The revert effect is called when a draggable reverts to its original location; keep in mind that the revert option must be set to true for this effect to play. CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES 263 Exploring Droppables The counterpart to Draggable, Droppables is the object that manages all draggable- friendly containers. When an element is made a droppable, it listens for mouseover events to determine when an element is being dragged into it. Then it negotiates with the given draggable to determine if they’re compatible. Making Droppables The interface to Droppables is a bit different from that of Draggable. It’s not a class, so you don’t declare instances of it. Instead, you use the Droppables.add method: Droppables.add('some_element', options); To illustrate, let’s add a new ul to our page. It will be empty at first, but will accept any li that’s dragged into it. (Note that we’ve removed the handles we added in the previ- ous exercise.) <ul id="container"> <li id="item_1">Lorem</li> <li id="item_2">Ipsum</li> <li id="item_3">Dolor</li> <li id="item_4">Sit</li> <li id="item_5">Amet</li> </ul> <ul id="drop_zone"></ul> Let’s also style the two containers so that we can clearly see their boundaries. We’ll also float them to the left so that they appear side by side rather than stacked. #container, #drop_zone { width: 200px; height: 300px; list-style-type: none; margin-left: 0; margin-right: 20px; float: left; padding: 0; border: 2px dashed #999; } CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES264 All this typing into the Firebug console is getting tedious, so let’s add some code to draggable.html that will run on dom:loaded: <script type="text/javascript"> document.observe("dom:loaded", function() { $('container').select('li').each( function(li) { new Draggable(li); }); Droppables.add('drop_zone'); }); </script> Here, we’re declaring our new ul as a droppable, but we’re not providing an options argument. By default, this droppable will “accept” any draggable that is dropped onto it. Reload the page and drag an item from the left box to the right box. Nothing hap- pens. You can drag the list items wherever you like, but you could do that already. Don’t worry, this is by design. script.aculo.us does not make any assumptions about the purpose of the drag/drop operation, so it will only do what you tell it to do. Using Callbacks for Droppables There are two callbacks for droppables: onHover and onDrop. When a draggable is dragged over a compatible droppable (i.e., a droppable that would accept it), the onHover callback fires. When it is dropped onto a droppable, the onDrop callback fires. To illustrate this in the simplest way possible, let’s add an onDrop callback to the pre- ceding code: Droppables.add('drop_zone', { onDrop: function() { console.log("dropped!"); } }); Now reload the page and try again. Make sure the Console tab of Firebug is visible so that you can see when messages are logged. You should see something like Figure 11-3. CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES 265 . apart on the x axis and 50 points apart on the y axis. Better yet, we can set snap to a function that determines, on each movement of the mouse, where the draggable should snap to. Advanced usage. four directions on Windows, and a grabbing hand on the Mac) provides a visual clue to the user that the handles are clickable. Make these changes, reload the page, and run this in the console: $('container').select('li').each(. { new Draggable(li, { handle: 'handle' }); }); You’ll be able to drag the items around using their handles, but the rest of the ele- ment won’t respond to clicking and dragging. Notice,