HTML5 Drag and Drop in Elm
A mixed bag
Compared to the ‘official’ drag-and-drop example, one advantage of HTML5 drag and drop is that you no longer need a subscription to keep track of the dragged object’s position. Saving you a storm of
Msg as well. But it is not all good news.
The good news
HTML5 has built-in support for drag and drop. Which comes with a bunch of goodies:
- You can label any DOM-element as draggable and/or as drop-zone.
- The browser will show the user a nice “ghost-image” of whatever it is that they are dragging.
- The browser shows the user whether or not they can drop whatever they are dragging when they hover over an element.
- And to top it off, there are various events fired off around drag and drop that you can tap into, to handle the drag and drop operations in your own code.
Benefits in Elm
In Elm there are two main advantages of HTML5 drag and drop over the “standard” subscribe-to-mouse implementation:
- No need for subscriptions: it is enough to include an event listener on the draggable item and on the drop zone. And you can also — straight in your view — fire an event when the dragged item is entering, over, of leaving some other DOM element.
- With these events, no additional state is needed to detect hover-over; to track if the dragged item over is over some other item in the DOM. Your drop zones will fire an event once the dragged item is dropped on it.
However, there are also a couple of peculiarities you need to consider when you implement this in Elm:
event.dataTransfer.setDragImage()to change this ghost to another image. And an image only. There is no simple way to change the ghost-image to some other HTML element. It also has the curious property that the new image will always have your cursor in the top-left corner. Also if you started your drag in the middle of the draggable item. (examples and demo over at kryogenix).
- 2. Drop-zone enablement in Elm is not entirely clean. To enable something as a valid drop-zone, you need to
ondragoverevent. Whether or not you actually have a valid use for that event in your app. In Elm, you could include some
NoOpmessage to handle this (to use with a custom
onDragEnterfunction), but that does muddle your code. Another way to achieve this in Elm, is setting an
- 3. Drop-zone enablement is unsafe. When you enable something as a drop-zone in your app, the user can drop anything on that drop-zone. This could be an item that you made draggable and expect. (and BTW,
The way I protect the drop-zones in Elm, is to only enable elements as a drop-zone while some other element from my Elm-app is being dragged. (by keeping track of a dragging-state in the model).
Oh, and it is a good idea that your custom onDrop handler has a
preventDefault()as well. Otherwise your browser will open any file or image or link in a new Tab or Window once the user drops it.
event.dataTransferobject to get to the data that is being dropped. However (you may have guessed), most access is through methods, not properties. So not directly available in Elm. If the user selects some text snippet and drops it on a drop-zone, there is no property Elm can read to get find out what the snippet was. Or if the user drops files from their machine on your drop-zone, your Elm code can get to little more than the filename. So without ports, drag-and-drop-file-upload is not possible.
- 5. Additional ports needed for Firefox. I tested in Chrome and IE (and suspect it works in Safari too). But Rohan Orton (in response here) pointed out that in Firefox HTML5 drag and drop of HTML items only works if you explicitly call the
event.dataTransfer.setData()function. So in Elm, you would need a port for this.
Even with the caveats above, HTML5 drag-and-drop can still be quite useful in Elm.
If you want to implement strictly within elm — i.e. dragging elm-rendered items and dropping them on other elm-rendered items — then HTML5 is straightforward, and not having to use the subscriptions is a win.