2005-05-12 12:08 UTC Beneath The Surface
I started working on the drag and drop part of the HTML5 spec. Since IE already supports drag and drop, and since Safari implements the IE API for drag and drop, it makes a lot of sense to simply use that API.
So I started doing research into the IE drag and drop API. It turns out there is very little useful documentation out there. MSDN has a vague hand-wavy description, Apple have a tutorial that explains how you're supposed to use it, and the rest of the documentation consists of tutorials that simply rehash what those two sources say.
In fact, basically all I got from the official sources of documentation was the names of the events, attributes and methods that form part of the API. Which is a start, but not even remotely enough to actually write an implementable specification.
So I put my physicist training to use. As a scientist, you are supposed to approach problems using a simple process:
- Form a hypothesis that matches everything you know so far.
- Make some testable predictions using this hypothesis.
- Test reality to see if your predictions can be disproved.
- Repeat those steps until you can no longer find anything in reality that disagrees with what your hypothesis predicts.
- Publish your findings, along with all the data you collected, and the hypothesis you ended up with.
- Wait for someone to find a prediction that your hypothesis makes and which doesn't match reality.
- Repeat the entire process.
So I made some guesses as to how the various events that were listed in the documentation were going to act, and I tested to see if that is how they worked. Every time I found that my predictions didn't match what IE did, I adjusted my theory and repeated.
There are seven events I decided to concentrate on:
dragstart, drag, dragenter,
dragover, dragleave, drop, and
dragend. In addition, the event object has a
dataTransfer member, which is an object with, amongst
other things, two attributes of interest: effectAllowed
(string; allowed values "copy", "link", "move", "copyLink",
"copyMove", "linkMove", "all", "none", and "uninitialized") and
dropEffect (string; allowed values "copy", "link", "move",
and "none"). The dataTransfer object also has a
setData() method. It apparently takes two arguments: a
type (either "Text" or "URL") and a string representing the data.
I originally hypothesised that when you drag a selection from the
page to a textarea element or another application, the
source is always left unchanged. It turns out I was wrong about this.
If you set event.dataTransfer.effectAllowed to "move" in
the ondragstart event, and then you drag it to something
that has a default drag-and-drop behaviour that accepts text, such as
a textarea, then the text of the selection is removed
from the DOM by the browser. You don't have to do this
yourself.
Fair enough, I thought, new hypothesis: when the
effectAllowed is "move", the UA always removes the
original selection from the DOM when you drop it.
Nope. Even if you set dropEffect to "move" in
dragenter, dragover, and drop,
and set effectAllowed to "move" in
dragstart, drag, and dragend,
the original selection is left alone if you are dragging a selection
to an element (such as a P) that doesn't have a default
behaviour.
Ok, theory: the deletion is done in the drop event's default action
for elements like textarea.
Nope. If you return false in the event handler for the
drop event of the textarea element, the
textarea stops receiving the new text, but the text
is still removed from its original location.
Ok, maybe it happens as the default action of the
dragend event.
It's possible. Cancelling the event had no effect at all; changing
the dataTransfer object's attributes in that event's
listener had no discernible effect either.
Ok, try another hypothesis. The dragend event is not
cancelable and its default action is to remove the selection if the
event target is a text field of some description (including one in a
native application external to the UA itself), and the final effect
used was "move", irrespective of the data given by
setData() calls.
To test this I set up a test where I dragged a selection to a
textarea, with the dragstart event calling
dataTransfer.setData('URL', 'http://www.example.com/')
and setting effectAllowed to "move". According to the
hypothesis, we should see the URL be ignored and the selected text be
added to the textarea and removed from its original
placed in the DOM.
Nope. IE crashes if you do this.
Ok, let's investigate a different angle. When do the events get fired?
Theory: The drop event is fired when you release the
mouse over the element.
Nope. If you set dropEffect to something that isn't
"compatible" with the value of effectAllowed (e.g. set
one to "copy" and the other to "move") in the earlier events, then
drop is never dispatched.
Theory: In the event sequence dragenter,
dragover, dragleave, the next event is only
called if the previous event's event handler sets
dropEffect to a value compatible with the
effectAllowed value.
Nope. The value of dropEffect has no discernible
effect on whether those three events are fired.
Theory: Once you set one of the effectAllowed and
dropEffect attributes to a value, it keeps that
value.
Not for dropEffect. As far as I can tell, the
dropEffect attribute is reset before each event. The
effectAllowed attribute does seem to keep its value,
though, whenever you set it.
Theory: In the event sequence dragenter,
dragover, drop, the cursor will be the value
common to the value of dropEffect set by the last event
handler and the value of effectAllowed, or a no-drop
cursor if they are not compatible, and the dragenter and
dragover events have a default action that is to set
dropEffect to "none".
It does appear that the cursor acts as described, except that if
you don't set dropEffect but do cancel the event, the
attribute seeems to get a value of its own.
Theory: The dropEffect attribute is always reset to
"none" before an event is dispatched.
Nope. If you drag text from an element that sets
effectAllowed to "copy" to a textarea
element, then the dropEffect attribute is set to "copy"
in the dragend event. And if you just cancel the
dragover event (say), the dropEffect takes
on one of the values that is compatible with the value of the allowedEffect attribute.
Theory: During the dragend event, the
dropEffect attribute is set to the value that represents
the value common to the dropEffect and
effectAllowed attributes as they stood after the last
dragover event.
Nope. Even if you set dropEffect and
effectAllowed to "copy" everywhere, if you don't return
false from the drop handler, the dropEffect
is always set to "none" in the dragend handler.
Theory: The dropEffect attribute is set to the value
that represents the value common to the dropEffect and
effectAllowed attributes as they stood after the last
event before the dragend event, and, the
drop event's default action on elements that aren't
native drop targets is to set it to "none".
Possible. It seems that if you set the value explicitly during
drop, it doesn't matter whether the value is compatible
with the effectAllowed value, that is only checked for
the cursor to use. However, it also seems that the
dropEffect attribute is set to one of the values
compatible with the effectAllowed value before the
drop event is dispatched.
Clearly this merits further study. I'll see if I can write a more complete theory and then test that. Obviously not everything IE does is sane (e.g. crashing) so the final spec won't be 100% compatible, but hopefully it will be close enough.
One thing I discovered which surprised me is that IE actually
tracks every DOM and style change made during a drag and drop
operation, and lets the user invoke an Undo command that undoes those
changes (unless another DOM change has been made since, not counting
changes to style objects). It doesn't include things like
window.status, but it does include changes to
the style object of every element, as well as attributes
like title. And it includes all changes made between (and
including) dragstart and dragend, including
changes made by scripts running on timers.
It isn't perfect. For example, while it does track changes made
directly to the text of the title element, it
doesn't track changes made indirectly to that element via the
document.title API. (Also, oddly, while changing the
document.title DOM attribute changes both the actual
window title and the element's contents, changing the element's
contents directly doesn't affect the window's title. So the undo
command never changes the window's title.)
Another discovery I made is that mouse and key events do not seem
to be dispatched during a drag-and-drop operation. To be precise, at
the start of a drag you get a mousedown followed by a few
mousemoves, but then it jumps into the drag events and
you never see the mouseup.
Pingbacks: 1