Figure 11-3. Firebug logs our sanity-check event handler. Aha! Progress. As a developer, you might expect a drop to correspond with a move- ment in the DOM. The act of dragging an li over a ul feels like it ought to detach that li from its current parent and attach it to the new ul. But Firebug’s HTML tab, pictured in Figure 11-4, shows that the target ul is still empty. Figure 11-4. Firebug confirms that the drag-and-drop action did not change the draggable ’s position in the document. We can use our onDrop callback to append the draggable to the droppable. It takes three arguments: the draggable element, the droppable element, and the mouseup event linked to the drop. CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES266 Let’s start by writing a general function that will get called for drops. We can use appendChild, the built-in DOM method, to add the draggable as a child of the droppable. function appendOnDrop(draggable, droppable, event) { droppable.appendChild(draggable); } We don’t have to detach the draggable first; appendChild works for moving elements. (We could have used Element#insert in the same way; it behaves just like appendChild if it’s passed a single element.) Now we should use this function as the onDrop callback for the droppable. With these changes, your script block should look like this: <script type="text/javascript"> document.observe("dom:loaded", function() { function appendOnDrop(draggable, droppable, event) { droppable.appendChild(draggable); } $('container').select('li').each( function(li) { new Draggable(li); }); Droppables.add('drop_zone', { onDrop: appendOnDrop }); }); </script> Now reload the page. Before you try the drag again, go to Firebug’s HTML tab and look at the source tree. Expand the body tag and you’ll see our two uls—one with children, one without. If our code works correctly, though, the first list will lose a child and the sec- ond will gain a child. We’ll see these changes in the source tree—a node gets highlighted for a moment when its DOM subtree is modified. Drag one of the items from the left column to the right. Whoa—the element jumps to the right when we let go of the mouse. Why does this happen? Firebug tells us we got it right—the second ul now has a child. Expanding it in the source tree reveals that the droppable is now a child of the second ul. We need to take a closer look at this. Reload the page and expand the Firebug source tree again. Drill down to one of the draggable elements and click it in the tree (or you can click the Inspect button, and then click a draggable). The element is now highlighted in blue in the source tree, and its CSS styling is shown in the rightmost pane (see Figure 11-5). CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES 267 Figure 11-5. Firebug lets us inspect an element’s entire style cascade. This pane shows the style cascade of the element—the properties from all CSS rules that apply to the element, with more specific selectors nearer to the top. At the very top, under element.style, are the styles that have been assigned to that element directly— whether through inline styles (the element’s style attribute) or through JavaScript (the DOM node’s style property). Firebug shows CSS changes in real time, just like DOM changes. Click and drag on the element you’re inspecting, and move it around the page—but don’t drop it on the other ul just yet. You’ll notice that the element.style rules update as you move the ele- ment. When you’re done moving it, the CSS pane should look like Figure 11-6. Figure 11-6. Firebug helps us understand how our draggable determines its position. So now we know how the element gets moved around the page—it’s given a position of relative, along with top and left values that correspond to the position of the mouse. The style rules persist even after the drag ends. Now drag the element over the empty ul and drop it. Once again, the element moves to the right. Do you understand why now? Remember that when an element has a CSS position of relative, the top and left properties move the element relative to its original position on the page. In other words, when top and left are both 0, the element is in its normal spot—the same place it would occupy if it had a position of static. When we moved the element from one parent to another, we changed the way its positioning was calculated. It’s now offset from the normal space it would occupy in its new parent ul. CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES268 If we want to position the draggable inside the new ul, we can set its left and top properties to 0 in the onDrop callback: function appendOnDrop(draggable, droppable, event) { droppable.appendChild(draggable); draggable.setStyle({ left: '0', top: '0' }); } Reload draggable.html. Now when items are dragged from the first list to the second, they “snap” into place in the new list (see Figure 11-7). Figure 11-7. Now that we adjust the element’s positioning when it’s dropped, it appears to fall into its receiving droppable. Drag-and-Drop: Useful or Tedious? Does this feel a bit more “manual” than usual? Do you think that some of the code you wrote should’v e been handled by script.aculo.us automatically? Keep in mind that Draggable and Droppables are low-level tools. They don’t make assumptions about use cases; in fact, they don’t do much without your explicit mandate. They are designed to eliminate the largest headaches related to drag-and-drop. It’s no fun CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES 269 to write your own code that determines when one element “overlaps” another. It’s no fun to write the same three event handlers for every element that can be dragged. These problems are best solved by code that’s as widely applicable as possible. That said, you can (and should) build custom controls on top of the base logic of Draggable and Droppables. And that brings us to a specialized control that leverages drag- and-drop—script.aculo.us’s own Sortable object uses them for drag-reordering of lists. Exploring Sortables Sortables are a specialized yet common application of drag-and-drop. Sortables are con- cerned with the sequence of a certain group of items, so rather than moving elements from one container to another, the user moves the elements relative to one another. Use cases for sortables are everywhere to be found. Think of, for example, your favorite rental-by-mail service, for which you maintain a queue of DVDs and/or video games you’d like to play. It’s crucial for you to be able to arrange that list in order of most- desired to least-desired. Making Sortables Making a sortable is much like making a droppable. There is one object, Sortable, that manages all sortables on a page. Creating a new one requires a call to Sortable.create: Sortable.create('container'); As with Droppables.add, the first argument refers to the element that will act as a con- tainer. In fact, this simple example already works with our example markup—swap out the code you wrote earlier with this code: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>Draggable demo</title> <style type="text/css" media="screen"> body { font-family: 'Trebuchet MS', sans-serif; } CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES270 #container { width: 200px; list-style-type: none; margin-left: 0; padding-left: 0; } #container li, .foo { background-color: #f9f9f9; border: 1px solid #ccc; padding: 3px 5px; padding-left: 0; margin: 10px 0; } #container li .handle { background-color: #090; color: #fff; font-weight: bold; padding: 3px; } #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; } </style> <script <script src="scriptaculous.js" type="text/javascript></script> CHAPTER 11 ■ ENABLING DRAGGABLES, DROPPABLES, AND SORTABLES 271 . of the base logic of Draggable and Droppables. And that brings us to a specialized control that leverages drag- and- drop—script .aculo. us s own Sortable object uses them for drag-reordering of. you wrote should’v e been handled by script .aculo. us automatically? Keep in mind that Draggable and Droppables are low-level tools. They don’t make assumptions about use cases; in fact, they don’t. that we adjust the element’s positioning when it’s dropped, it appears to fall into its receiving droppable. Drag -and- Drop: Useful or Tedious? Does this feel a bit more “manual” than usual? Do