Anatomy of a Drag and Drop
The mechanics of a drag and drop are pretty interesting and most library implementations do similar things, albeit to various extents. As I explain the inner workings, I'll touch on Yahoo's, Mootools', and Script.aculo.us' implementations, what to expect, and how they differ.
A drag and drop essentially consists of a few events (and by events, I mean, moments in time, not DOM events). There's the moment a drag is started, there's the continuous dragging of an item, and finally there's the moment the item is no longer being dragged (ie: the drop). The items being dragged are sometimes referred to as draggables.
On top of these events, there is also the interaction with other elements on the page; dragging over and out of an element and whether the dragged element has been dropped on top of another element on the page. The elements upon which draggable items are dropped onto are sometimes referred to as droppables. I'll cover droppables in the next part. For now, let's go over the draggables.
Starting the Drag
In a drag and drop, we need to know when to start the drag. This is done by attaching a
mousedown event on an object. Normally, this is attached to the object that we want to drag.
Alternatively, this could be attached to a drag handle. A drag handle is an element other than the container itself that should initiate the drag. For example, if you had a "panel" and you only wanted the title bar to initiate the drag then the title bar would be your drag handle. None of the implementations stop you from specifying a drag handle that exists outside of the draggable. (Not that I'm saying they should stop you but it's interesting to note and maybe there are some cool things you could do with this.)
- Mootools requires an element reference or an element id to be passed in for the draggable and its handle. The element to be dragged also needs to have its position explicitly set to relative or absolute.
- Script.aculo.us can take an element reference or an element id for the draggable. For the handle, you can also specify a class name. It will try and locate the first item within the draggable that has that class name. If it can't find it, it'll try and find an object on the page with that id. Script.aculo.us will also take an element reference.
- YUI takes just an id (not an element reference) but allows you to specify as many drag handles as you'd like for a single draggable object.
With the item that we want to drag, a
mousedown event is attached to the handle (or the draggable itself, if no handle was specified).
- Mootools (revision 83) is fairly obtrusive in that it overwrites the
mousedownevent of the draggable upon initialization (that is, any applied like
element.onmousedown = function).
mousedown event is fired, the gears of the drag are set in motion. First, by applying
mousemove onto the document. The
mousemove event fires as we continue to move the mouse around the page. Each time the event fires, the X and Y values of where the mouse is on the page are captured. The
mouseup event determines when we've completed the drag.
- YUI allows you to specify a drag threshold. This value requires the user to move the mouse a certain number of pixels before the drag actually begins.
Each of the libraries will make a callback —
onStart in Script.aculo.us and Mootools, and
startDrag in YUI. This allows us to prepare the drag element or other elements on the page for this event. Script.aculo.us gives us an extra callback called
starteffect. It's intended to apply an effect to the element being dragged, such as changing the opacity or other effect.
- Mootools, like on the element's
mousedownevent, is fairly obtrusive in that it overwrites the
mousemoveevents of the
document. If you're using addEventListener (or their equivalents) then you should still be okay.
As the mousemove event fires, the X and Y values are used to reposition the draggable item around the page. This is done by calculating the difference between the last X and Y coordinates and the current coordinates. The change is then applied to the top and left properties of the element.
- Mootools lets you enable element resizing (instead of drag and drop) by changing the height and width properties of an element instead of top and left.
Each library also implements a callback function to be fired each time the mousemove event does; onDrag in YUI and Mootools, and
change in Script.aculo.us.
- Script.aculo.us has a Draggables singleton that you can register listeners with. Any time an object is dragged (or started or stopped), an onDrag callback (or onStart or onEnd, respectively) is fired for every item registered with the Draggables object.
Stopping the Drag
mouseup event fires on the document, the
mousemove events are removed from the document. One final callback is made;
onEnd for Script.aculo.us,
endDrag for YUI, and
onComplete for Mootools.
- Script.aculo.us will fire off two more callbacks. The first is
reverteffectand the other is
reverteffect, by default, will animate the object from the drop point back to its original point. By default,
endeffectresets the opacity of the object back to 100%. Overriding the default means any functionality there before will not execute. For example, if you were to override
endeffect, the opacity would remain unless you manually reset it.
I put together a quick example of a drag and drop implementation. This is a home brewed demonstration to show that the mechanism is fairly straightforward. Where the complexity comes in is in handling the variety of situations that might be thrown at it, and that's where libraries really have their advantage.
(For fun, I created an example that changes height and width to demonstrate element resizing.)
Creating a draggable is only a small part of the equation. Next up I'll cover the concept of a drop zone (or droppable as they're known from Script.aculo.us and Mootools) and how to determine that you're over a droppable.