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.

Caveats

However, there are also a couple of peculiarities you need to consider when you implement this in Elm:

  • 1. Limited styling options for ghost-image. The “ghost-image” that the users sees while dragging, is a copy of the dragged item (with the styling from the moment the dragging started). In Javascript, you can call a method 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).
    In Elm, you would always need a port to change the ghost image. (Elm cannot directly access methods on a javascript object).
  • 2. Drop-zone enablement in Elm is not entirely clean. To enable something as a valid drop-zone, you need to preventDefault() in the ondragover event. Whether or not you actually have a valid use for that event in your app. In Elm, you could include some NoOp message to handle this (to use with a custom onDragEnter function), but that does muddle your code. Another way to achieve this in Elm, is setting an attribute "ondragover" "return false" on the drop element, but this is kind of an ugly hack. The value of the attribute is not really a property, but it is a one-line piece of javascript code (ugh).
  • 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, <img> are draggable by default). But it could also be something completely different: some text, or files, links or images from other apps on the user’s machine. So be aware that your drop-zone is an unsafe entrance to your Elm environment, that you should guard really carefully. Javascript has some methods you can call, to inspect whatever is dropped. In Elm your options are limited (unless, again, ports).
    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 apreventDefault() as well. Otherwise your browser will open any file or image or link in a new Tab or Window once the user drops it.
  • 4. Limited access to data of dropped items. Whenever something is dropped in a drop-zone, javascript provides an event.dataTransfer object 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.

Closing thoughts

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.

If you are looking for more advanced stuff — e.g. let users drag images or files from the outside world onto a drop zone in your Elm app — then you will need ports and quite a bit of javascript, to access the dataTransfer object in javascript.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.