Drag and drop

Here's a simple accessible drag and drop script. It works with both mouse and keyboard.

This is a drag and drop element with position: absolute.

This is a drag and drop element with position: fixed.

When the '#' link in the example boxes is activated (either by tabbing to it and hitting Enter or by clicking
on it) the element can be dragged by the arrow keys. Pressing Enter or Escape releases it. (Feel free to change
these keys, by the way. I'm not sure what the release keys ought to be, although Enter and Escape are both
defensible.)

The script automatically adds a class="dragged" to an element that's being dragged. You can use
this for some CSS effects.

If you want to do something with the element once the user has dropped it, add your own function
calls to the releaseElement function.

Properties

You should set two properties.

keyHTML contains the HTML of the keyboard-accessible link that
every draggable object needs. I kept the HTML simple—just a link with a class for a bit of styling. You
can use any HTML construct that you like, but keep in mind that you need a link, since (apart from form
elements) links are the only elements that are reliably keyboard-focusable in all browsers; and keyboard users
need to be able to focus on something to trigger the script.

keySpeed gives the speed of the keyboard drag and drop, in pixels per keypress event. I like
the value 10, but I encourage you to experiment with faster or slower movement.

There are seven more properties, but they're all internal to the script. Initially they're all set to
undefined, and the relevant functions will assign values to them. (In fact, I could have left out these
property declarations entirely, but I like declaring the variables I need at the start of my script.)

What a drag and drop is

A drag and drop is a way of moving an element across the screen. In order to be movable at all the element must
have position: absolute or fixed so that it can be moved by changing its coordinates
(style.top and style.left).

(In theory the element could have position: relative,
but this is almost never useful. Besides, the relative case needs some extra position calculations that are not
part of this script.)

Setting the coordinates is pretty simple; it's finding the values that the coordinates should be set to that's
the hard part of this script. Most of the script deals with finding them.

In addition, accessibility must be considered. Traditionally, drag and drop scripts work with the mouse, and
all in all this remains the best option from a usability point of view. Nonetheless, in order to remain accessible
for people who cannot use a mouse, the drag and drop should react to the keyboard, too.

Basics

Let's first review some basics.

Initialising an element

Every drag and drop script starts with initialising the element. This job is done by the following function
(method):

If the function receives a string it interprets that string as an ID. Then it sets a mousedown
event for the entire element, in order to start up the mouse part of the script. Note that I use
traditional event handler registration; that's because I want the this
keyword to work normally in the startDragMouse function.

Then it takes the keyHTML the author has defined and add it to the element. This bit of HTML contains
one link, and since it's added to the end of the element, I'm certain that the last link in the element is the one
that should trigger the keyboard part of the script. The script sets a click event for this link to start up the
keyboard part of the script. It also stores a reference to the main object in relatedElement; we'll need
this reference later on.

Now the script waits for the user to take action.

Basic positioning data

I decided on the following positioning approach: first I read out the initial position of the draggable
object at the time the dragging starts and store it in startX and startY. Later on the
script calculates the change in mouse position or the amount of arrow key strokes to determine how much
the element moves from this initial position.

The startX and startY variables are set by the startDrag function, which is used both by the mouse and by the keyboard script.

First of all, if an element is still selected (in drag mode), release it. (We'll get back to
releaseElement later.)

Then the function finds the current position of the element at the time the dragging starts through the
offsetLeft and offsetTop properties
(see the Find Position page)
and stores them in startX and startY for future
reference.

Then it stores a reference to the element in draggedObject, and it
adds a class "dragged" to the element so that the author can define extra styles for an element that's being
dragged.

Sometimes you want to drag another element than the one the mousedown event takes place on—for instance
because a mousedown on a title bar (but nowhere else) should initiate the drag and drop. In that case,
make sure that draggedObject refers to the object you want to drag.

Once the user moves the element either by mouse or by keyboard, the complicated parts of the script keep track of how much the position
of the element should change. This gives values dX and dY (change of X and Y). I
add these to startX and startY, which gives me the new position of the element.

It receives a dx and dy calculated by either the mouse or the keyboard scripts and adjusts
the object's style.top and style.left properties. The element moves.

That's pretty simple; the trick lies in finding the correct dx and dy. The mouse and
keyboard scripts do this quite differently, so we'll discuss them separately.

The mouse script

The mouse script is slightly more complicated than the keyboard script when it comes to calculations, but much
simpler in terms of browser compatibility. Therefore we start with the mouse script.

The events

First we have to discuss the events we need. Obviously, a drag and drop needs mousedown,
mousemove and mouseup for selecting the element, dragging it, and releasing it.

Equally obviously, this sequence starts with a mousedown event on the element to be dragged. Therefore all draggable
elements need an onmousedown event handler that readies the element for dragging and dropping. We already saw
the line in startDrag that takes care of this:

element.onmousedown = dragDrop.startDragMouse;

However, the mousemove and mouseup event should be set not on the element, but on the entire document. The reason is
that the user may move the mouse wildly and quickly, and he might leave the dragged element behind. If the mousemove and
mouseup functions were defined on the dragged element, the user would now lose control because the mouse is not over the
element any more. That's bad usability.

If we define the mousemove and mouseup on the document, this problem disappears. Wherever the mouse is, the dragged
element reacts to the mousemove and mouseup events. That's good (or at least better) usability.

In addition you should set the mousemove and mouseup events only when the dragging starts,
and remove them when the user releases the element. This keeps your script clean
and also saves some processing time because mousemove is only evaluated when necessary (ie. when the element is
being dragged).

Mousedown

Once a mousedown event occurs on a draggable element, the startDragMouse function is executed:

First it executes the startDrag function we already discussed. Then it finds the current mouse
position and stores its coordinates in initialMouseX and initialMouseY. Later on we're
going to compare these values to the current mouse position.

Finally it returns false; this is to suppress the default action of the mouse event: start selecting
text. We don't want any text to be selected while the dragging goes on; that'd be annoying.

Then it sets the mousemove and mouseup event handlers on the document, for the reasons discussed above. Because
it's possible that the host page has its own mousemove and mouseup event handlers set on the document, I use
my addEventSimple function that adds my event handlers without disturbing any
that might already exist.

Mousemove

Now, whenever the user moves the mouse the dragMouse function is executed.

The function reads out the current mouse coordinates (clientX and clientY) and subtracts
initialMouseX and initialMouseY from these coordinates.
This results in the number of pixels the mouse has
moved since the start of the drag and drop. This is exactly what setPosition expects, so we send dX
and dY off.

Again we return false to prevent the mousemove event from selecting text.

Mouseup

When the user releases the mouse, releaseElement is called. We'll discuss that function later.

The keyboard script

Now let's turn to the more difficult part: the keyboard script. Unlike a mouse drag and drop, there is no accepted
standard user interface for a keyboard drag and drop (yet). Although the basic interaction is not terribly complicated,
we should still briefly consider it.

Basic interaction

The most obvious keys for moving the element are the arrow keys. That's pretty simple.

Activating and releasing the element is more tricky, though, and this is an area where my script could be
improved.

I decided that the keyboard script can be activated through an extra link I write into all draggable
elements. There aren't many other options; we need a link because links are reliably focusable in all browsers
(OK, form fields are, too. You could use a checkbox, I suppose); and putting the link inside the draggable element
seems the most logical placement (you could place them elsewhere, I suppose, but how is the user to know which link
triggers which element?)

I decided that the element would be released when the user presses Enter or Escape; more or less because I couldn't
think of any other keys. If you opt for other keys
you should add the correct key codes here:

The events

The activation event is click. This event is accessible, since it reacts both to a mouse
click and to an Enter key when the focus is on the element. Therefore the keyboard
script can be activated by tabbing to the link and pressing Enter, or by clicking on the link.

(Strictly speaking, when you click on the link, the element is first activated in mouse
mode (mousedown), then released (mouseup) and then activated in keyboard mode (click).

The rest of the events are more murky, though. The key events are a mess—especially when you want to
read out the arrow keys.

The first problem is that we need an event that allows key repeating; i.e. if the user keeps the arrow
keys depressed, the event should fire again and again, so that the dragged element keeps moving. By ancient custom
we use the keypress event for this function.

Unfortunately IE does not fire the keypress event on arrow keys. That problem is partly offset by the
fact that the keydown event in IE fires repeatedly. So superficially it seems as if we have to use keydown.

As you might have guessed it's not that simple. In Opera and Safari 1.3 the keydown does not repeat; so if the
user keeps the key depressed, nothing happens after the first movement. In these browsers we therefore need keypress. (Mozilla and Safari
3 allow repeating both onkeydown and onkeypress; by far the most civilised solution as far as I'm concerned.)

So ideally we use the keypress event; if it's not supported we use the keydown event. But how do we switch events?
How do we know if the keypress event is enabled?

My solution is to set an event handler for the keypress event. If this handler is executed, keypress is
obviously supported and we can safely switch key events.

The startDragKeys function sets event handlers for keydown and keypress:

Initially the keydown event triggers the dragKeys function which performs the actual
dragging. This very first event always fires, and the element always moves. However, if we did nothing more,
the element would stop moving in Opera and Safari 1.3 .

That's why we also need keypress. The first keypress event triggers the switchKeyEvents function,
which rearranges the event handlers:

It removes the event handlers we just set and adds a new one: dragKeys now fires on the
the keypress event instead of the keydown event. Since this function is only executed in browsers that
support keypress, we have switched key events from keydown to keypress only in these browsers.

Initialising the key script

When the user activates the link in the corner of the draggable element, startDragKeys
is called.

First it calls the startDrag function we already discussed. It sends the relatedElement
to this function; a variable that contains a reference to the draggable element. (We set this variable in
initElement.)

Then it sets the dXKeys and dYKeys variables to 0; these variables will keep
track of the change of position of the element.

Then the event handlers are set as discussed above.

Then (and this is a bit of a hack) it removes the focus from the link the user just clicked. I do this
because of the Enter keystroke with which the user can release the dragged element. If I didn't remove the
focus and the user hits Enter, the element is released, but immediately afterwards a click event would take
place on the link, and the element would again be switched to drag mode. The net result would be that it's
impossible to release the element by pressing Enter. If we remove the focus from the link, this problem disappears.

Finally it returns false because the keystroke the user uses to activate the element should
not perform its default function (i.e. the Enter should not cause the link to be followed).

Dragging by keystrokes

We start by reading out the code of the key the user pressed. (See also the Detecting keystrokes
page.)

Then we use a switch statement (see section 5H of the book) to decide
what we need to do about the keystroke. Purpose of this part of the script is to update dXKeys and dYKeys,
which contain the number of pixels the element has moved since the start of the drag and drop.

The author sets the amount of pixels per keypress event in the keySpeed variable. When the user
presses the left arrow, this amount is subtracted from dXKeys, when he presses the right arrow
it's added. The same goes for up and down: then dYKeys is adjusted.

The script also contains the cases 63232-63235. These are for Safari 1.3, which doesn't use the normal
keyCodes 37-40 for the arrow keys. (Safari 3 does, by the way.)

If the user presses Enter or Escape the element is released (see below) and the function ends.
If you wish to change the keys that release the element, add their keyCodes here.

default:
return true;
}

If the user presses any other key, the event is allowed to take its default action (i.e. the thing that would normally
happen when that key is pressed) and the function ends.

dragDrop.setPosition(dragDrop.dXKeys,dragDrop.dYKeys);

Now dXKeys or dYKeys is updated, and we send both to the setPosition
function that changes the position of the dragged element.

if (evt.preventDefault)
evt.preventDefault();
return false;
},

Finally we have to prevent the default action of the key press; i.e. if the user presses the down arrow,
the page should not scroll down. This is done by calling the preventDefault method of the event
in W3C compliant browsers, and by returning false in IE.

Releasing the element

When the user releases the element, releaseElement is called. It removes all event handlers
the script might have set, removes the class "dragged", wipes the draggedObject and waits for
new user actions.