Simple Drag and Drop in Ember Octane
Tags: ember, javascript, tutorial
Published: 2020-04-09 02:05:35

This tutorial will show you how to implement a drag and drop interface in Ember Octane without the use of any addons.  It does assume you have at least a basic understanding of [Ember Octane](https://emberjs.com/editions/octane/).

I have had a lot of experience with the Javascript drag and drop API in the past. In fact, I even maintain the [ember-drag-drop](https://github.com/mharris717/ember-drag-drop) addon. Though I must admit that it is not very up to date anymore and to some extent with Ember Octane is not really needed as much as it was before.

First we are going to implement a very basic drag and drop.  One of the big changes in Ember Octane that makes implementing a drag event easier is [modifiers](https://www.pzuraq.com/coming-soon-in-ember-octane-part-4-modifiers/). In simple terms modifiers allow us to access native DOM elements and events in our templates.

First lets setup the template elements.

```handlebars
{{! simple-drag.hbs }}
<div class="dropZone">
    Drop Zone
</div>

<div class="dragMe" draggable="true">   
    Drag Me
</div>
```

As you can see we now have two simple div's. One for the drop area and one for the draggable element. The only thing here to notice is we have added the `draggable` attribute to the "drag me" element. This just notifies the browser that this div element is now draggable. You can only pass true or false to it.

Before the draggable element is active though, you are **required** to have a `dragstart` event attached to the element. Lets add that now.

```handlebars
{{! simple-drag.hbs }}
<div class="dropZone">
    Drop Zone
</div>

<div {{on "dragstart" this.dragHasStarted}} class="dragMe" draggable="true">   
    Drag Me
</div>
```

We are using the on modifier to attach to the `dragstart` javascript event.  Here is the class for that component.

```javascript
//simple-drag.js
export default class SimpleDrag extends Component {

    @action dragHasStarted() {
        console.log("Drag has started")
    }

 }
```

You can see that the `dragHasStarted` is an "action".  You must decorate your function with action when using it with a modifier. The main reason for this is that it takes care of binding your event handler so you can access `this` and it will reference your component class.

Now if you drag your element it will have the default dragging behaviour in your browser.

To set up the drop events add to the component template and class the two required drag events `drop` and `dragover`.

```handlebars
{{! simple-drag.hbs }}
<div 
  {{on "drop" this.dropItem}} 
  {{on "dragover" this.dragOver}}
  class="dropZone">
    Drop Zone
</div>

<div {{on "dragstart" this.dragHasStarted}} class="dragMe" draggable="true">   
    Drag Me
</div>
```

```javascript
//simple-drag.js
export default class SimpleDrag extends Component {
    
    @action dropItem(dragEvent) {
        dragEvent.preventDefault();
        console.log('Item dropped');
    }

    @action dragOver(dragEvent) {
        dragEvent.dataTransfer.dropEffect = "move";
    }

    @action dragHasStarted() {
        console.log("Drag has started")
    }

 }
```

In order for the `drop` event to fire it **must** also have a `dragover` event, so make sure you have one even if it does nothing.  Two other things to note with this code. 

The first is that we are accessing the native "event" in the `dropItem` function.  We use it to `preventDefault()`.  This is also important as after you drop an item it can trigger events on the drop target that you may want to avoid.

The second is in the `dragOver` function we are setting the dropEffect to "move".  This tells the browser what kind of action we intend to take when the item is dropped.  For more information see the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API).  Also, be careful with the `dragover` event as it fires multiple times per second so you don't want to do too much here.

Ok, this should now allow you to get console logs on start of drag and drop. Using those two event functions you should be able to implement most of the logic that you want the drag and drop action to perform.  

There are a number of other drag events though. Lets take a look at the final full code that implements a few more events that give the user a more interesting interface and you more control over it.
```handlebars
{{! simple-drag.hbs }}
<div 
    {{on "drop" this.dropItem}} 
    {{on "dragover" this.dragOver}}
    {{on "dragleave" this.dropLeave}}
    {{on "dragenter" this.dropEnter}}
    class="dropZone {{if this.overDropZone "dropZone-over"}}"
    role="button"
>
    Drop Zone
</div>

<div 
    {{on "dragstart" this.dragHasStarted}}
    class="dragMe" 
    draggable="true"
    role="button"
>   
    Drag Me
</div>

<p>Drag Status: "{{this.statusText}}"</p>
```
```javascript
//simple-drag.js
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

export default class SimpleDrag extends Component {

    @tracked statusText = "";
    @tracked overDropZone = false;

    @action dragHasStarted() {
        this.statusText = "Drag has started";
    }

    @action dropItem(dragEvent) {
        dragEvent.preventDefault();
        this.statusText = "Drop Has Completed";
        this.overDropZone = false;
    }

    @action dragOver(dragEvent) {
        dragEvent.preventDefault();
        dragEvent.dataTransfer.dropEffect = "move";
    }

    @action dropLeave() {
        this.statusText = "Drag has left drop zone";
        this.overDropZone = false;
    }

    @action dropEnter() {
        this.statusText = "Drag is entered Drop Zone";
        this.overDropZone = true;
    }

 }
```

So we have added a few more events like `dragenter` and `dragleave`. These allow us to let the user know when they are over the drop zone by adding a class.  We have also added a status text so you can see what events are firing and when. 

You can try this final code out for yourself on this [Ember Twiddle](https://ember-twiddle.com/8912fd46f7a66c92a2118c8199b17c2a).
You can find the full [project on github](https://github.com/dgavey/drag-drop-ember-octane-examples) as well.

I haven't covered accessibility support in this tutorial, but that must be considered to make the drag and drop inclusive for all users. You will at a minimum want to add some keyboard event support.

In my next post we will take what we learned here and implement a sortable list. Follow along for more drag and drop and other Ember tutorials.

For more drag and drop event information visit the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API).