DEV Community

Cover image for Simple Drag and Drop in Ember Octane
Derek Gavey
Derek Gavey

Posted on • Updated on

Simple Drag and Drop in Ember Octane

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.

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 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. In simple terms modifiers allow us to access native DOM elements and events in our templates.

First lets setup the template elements.

{{! 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.

{{! 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.

//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.

{{! 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>
//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. 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.

{{! 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>
//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.
You can find the full project on github 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.

Discussion (5)

Collapse
abeforgit profile image
Arne Bertrand

Great guide! Just wanted to add that in the first iteration of your dragOver handler you are not calling
dragEvent.preventDefault(). You do add it later, but this tripped me up as I was following along
and not setting the preventDefault actually breaks the dropping behavior.

Collapse
rstudner profile image
Roger Studner

Randomly curious why role="button" showed up mid-post.

Thanks so much for this content!

Collapse
dgavey profile image
Derek Gavey Author • Edited

Ah, mostly that was a copy and paste from the github code I made for this project. Having a role="button", keeps the linter quiet. It's important to define roles on elements without implied actions (like <button>) for screen readers and other accessibility reasons.

It's not required for any drag specific results though.

Collapse
robboclancy profile image
Robbo

You should probably fix up your inconsistent code styles with lines between actions. There are lots of things we could use drag and drop for, might be time to actually do it.

Collapse
jsolano profile image
J.P. Solano

Thank you!, it's incredible that something so important is so poorly explain on the official ember docs. Really appreciate the clarity and good example.