// Callback when mouse button is pressed. This checks if an item is draggable, and
// if so initiates the process.
function mousedown(e)
{
var obj = moz ? e.target : event.srcElement;
// Trace up DOM tree to see if item clicked on is draggable. This allows
// for the fact that you may click, for example, on a TD while the enclosing
// TR is the draggable object.
while (obj.tagName != "HTML" && obj.tagName != "BODY" && !isdraggable(obj)) {
obj = moz ? obj.parentNode : obj.parentElement;
}
if (isdraggable(obj)) {
// If draggable, set a global flag to track this, and save a pointer
// to the object in a global variable as well (dragobj).
isdrag = true;
dragobj = obj;
// origx, origy is original starting location of dragged object
origx = dragobj.style.left;
origy = dragobj.style.top;
// x,y is absolute co-ordinates within the window
x = moz ? e.clientX : event.clientX;
y = moz ? e.clientY : event.clientY;
// While offsetX, offSetY depend on where exactly you clicked on the object.
// Thus if you click in the middle of the object, it will be 'attached' to
// the mouse at that point, and not the upper left corner, for example.
offsetX = moz ? e.layerX + 2: event.x + 2;
offsetY = moz ? e.layerY + 2: event.y + 2;
}
}

mousedown, called when a mouse button is pressed, is the first main piece of code. So, when something is clicked on, we check if it is draggable. But if not, we also need to check if its parent is draggable, and so on. This is because when you click on something, the browser will usually return you a handle to the lowest, deepest item in the DOM tree. So, for example, if you had a span tag marked as draggable surrounding a p tag and clicked on the text in between, you would get back a handle to the p tag, which is not itself marked draggable. Checking its parent however, we would see that it is indeed draggable as a result of being a child of a draggable item.

Assuming we have clicked on something draggable, we set the global flag to true and save the object in dragobj. We also save several sets of coordinates: the original location of dragobj, the relative position of the mouse when we clicked, and the absolute position where we clicked.

// Callback when mouse is moved. It will change the cursor when you move over an object
// you can - or cannot - swap with.
function movemouse(e)
{
// Check if we are dragging an object
if (isdrag) {
// If so, set the dragged object's position relative to how much the mouse has moved
// since first clicked.
dragobj.style.left = moz ? origx + e.clientX - x + offsetX + 'px' : origx + event.clientX - x + offsetX;
dragobj.style.top = moz ? origy + e.clientY - y + offsetY + 'px' : origy + event.clientY - y + offsetY;
var obj = moz ? e.target : event.srcElement;
// If we are over an element that we cannot swap with, change its cursor style
// to show that a swap is forbidden
if (obj != dragobj && isdraggable(obj) && !allowswap(dragobj,obj)) {
obj.style.cursor = 'wait';
// save in a handle to the object in a global so we can reset the cursor later
lastobj = obj;
}
else if (lastobj) // reset the cursor as soon as we move off a non-swappable object
lastobj.style.cursor = 'pointer';
return false;
}
else {
// Sometimes can get stuck with no drop icon, so restore cursor just to be safe,
// when not dragging but passing over a draggable item
var obj = moz ? e.target : event.srcElement;
if (isdraggable(obj))
obj.style.cursor = 'pointer';
}
}

Whenever the mouse moves, we first check if something is being dragged. If so, we compute where the dragged object should be (after all it does NOT magically follow the mouse around by itself!) and move the object accordingly. Basically the formulas ensure the object moves the same amount the mouse has moved, since the object was clicked on. We also look to see if we are hovering over another draggable object. If so, we use allowswap to determine if a swap can take place. If not, the cursor turns into a "wait" shape (varies from computer to computer but this is the standard busy cursor). If a swap can take place or we are not over a draggable object, we leave the cursor alone, and restore it if we had changed it previously.

Note that, as an extra precaution, if nothing is being dragged we still restore the cursor to normal if hovering over a draggable object. This is because sometimes the cursor can get stuck in the "wait" state, depending how quickly the user moves the mouse, releases the button, etc. That is, the logic mentioned above for restoring the cursor is not failproof.

// Callback when mouse is released - checks if a swap should occur and
// returns dragged object to its starting position if not.
function mouseup(e)
{
if (isdrag) { // If something is being dragged
// Get the object over which the mouse button was just released
var obj = moz ? e.target : event.srcElement;
// Check if mouse was release over an object we can swap with
if (obj != dragobj && isdraggable(obj) && allowswap(dragobj, obj)) {
// A swap is allowed - swap color, tooltip and contents of the
// dragged object with that it was released over
var htm = obj.innerHTML;
obj.innerHTML = dragobj.innerHTML;
dragobj.innerHTML = htm;
var col = obj.style.color;
obj.style.color = dragobj.style.color;
dragobj.style.color = col;
var titl = obj.title;
obj.title = dragobj.title;
dragobj.title = titl;
// Set the position of the object we were dragging (dragobj) where the
// other object (obj) is located and move obj to the original location
// of dragobj before it was dragged (origx, origy).
dragobj.style.left = obj.style.left;
dragobj.style.top = obj.style.top;
obj.style.left = origx;
obj.style.top = origy;
}
else {
// No swap can occur so return dragged object to its starting position
dragobj.style.left = origx;
dragobj.style.top = origy;
}
// Restore cursor to pointer if it was changed in movemouse above
if (lastobj) {
lastobj.style.cursor = 'pointer';
}
}
isdrag = false; // Nothing is being dragged now
}

mouseup does the most legwork if the mouse is released while dragging an object. If released over a draggable object, we check with allowswap if a swap can take place. If it can, we swap the textual contents, color, and tooltips (title attribute) of the two objects. You may conceivably prefer to just swap ALL their attributes at this point, depending on your goals. Then we move the dragged object to the location of the object we released the mouse over, and move that object in turn to the original starting place of the dragged object. Thus the swap is complete. On the other hand, if a swap is forbidden, we merely return the dragged object to its starting location. Either way, we restore the mouse cursor in case it had been changed to a "wait" cursor in movemouse above, and we clear the isdrag flag to indicate we are no longer dragging an object.

Finally, to instantiate the objects is quite easy. Just remember that draggable objects have ids that start with dragdrop:

The objects were placed in a table, as well as given different colors, for convenience only, but the only important things here are the ids of the draggable items, which are also referenced in allowswap, and the fact that the draggable objects have position:relative in their style attribute. The formulas used to compute positions in the JavaScript assume relative positioning for these objects. Also note that the surrounding table has id drag_drop, which is referenced at the very beginning in the window.onload function.

Summary

Hopefully, the preceding examples have shown you some of the creative ways in which div tags can be used to section off and selectively display portions of a web page. The drag-and-drop example is just a very basic example of an extremely powerful concept in some of the most advanced web pages available today. With just a bit more work, it can be used, for example, to make a fully customizable home page, where you can drag around and swap various news feeds, images, and more to your heart's content. The only limits are your imagination!

Howard Feldman
is a research scientist at the Chemical Computing Group in Montreal, Quebec.