Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List Sorting with HTML5 Drag'n'Drop - Drop Above or Below Depending on Mouse

Background

I am working on a sortable list, to avoid having to manually type in sorting order numbers in a database. It works via HTML5's drag'n'drop functionality, i.e. the new drag* events in Javascript.

I currently have it working for the most part. I can click, and drag, and it'll sort itself.

Problem

From what I can tell, the drop, along with the dragstart and dragend events, only are aware of the element they are going into. They can't tell if the mouse is on the top half of the dropzone, or the bottom half.

What I'd like, is when I'm hovering over the top half of a list item, for the dragged content to be placed ABOVE the item. Then if I'm hovering over the bottom half, for the dragged content to be placed BELOW the item.

Currently:

In the screencap below, I show a working (simplified) example of my code. I'm using a border-bottom on the drop target to show that it's the target. Notice how when "Item 1" is above "Item 2", "Item 2" is lit up on the bottom, regardless of if I'm hovering over the top half or bottom half.

enter image description here

Code

var dragging = null;

document.addEventListener('dragstart', function(event) {
		dragging = event.target;
    event.dataTransfer.setData('text/html', dragging);
});

document.addEventListener('dragover', function(event) {
    event.preventDefault();
});

document.addEventListener('dragenter', function(event) {
    event.target.style['border-bottom'] = 'solid 4px blue';
});

document.addEventListener('dragleave', function(event) {
    event.target.style['border-bottom'] = '';
});

document.addEventListener('drop', function(event) {
    event.preventDefault();
    event.target.style['border-bottom'] = '';
    event.target.parentNode.insertBefore(dragging, event.target.nextSibling);
});
ul {
  margin:0;
  padding:0
}
li {
  cursor:move;
  display:block;
  padding:20px 10px;
  background:white;
  border-bottom:solid 1px gray;
}
<ul>
    <li draggable="true" class="sortable-bulk">List Item 1</li>
    <li draggable="true" class="sortable-bulk">List Item 2</li>
    <li draggable="true" class="sortable-bulk">List Item 3</li>
    <li draggable="true" class="sortable-bulk">List Item 4</li>
    <li draggable="true" class="sortable-bulk">List Item 5</li>
    <li draggable="true" class="sortable-bulk">List Item 6</li>
    <li draggable="true" class="sortable-bulk">List Item 7</li>
    <li draggable="true" class="sortable-bulk">List Item 8</li>
    <li draggable="true" class="sortable-bulk">List Item 9</li>
    <li draggable="true" class="sortable-bulk">List Item 10</li>
</ul>

Question

Is there a way I can have it drop either above or below, not always below, depending on the mouse position while dragging?

like image 809
Andy Mercer Avatar asked Jun 07 '17 14:06

Andy Mercer


People also ask

How do I drag-and-drop with mouse?

For example, to drag-and-drop an object, such as an icon, you first move your mouse cursor over it. Then, press and hold down the left mouse button, move the object to the location you desire, and release the mouse button to set it down.

Does HTML5 support drag-and-drop?

Now HTML 5 came up with a Drag and Drop (DnD) API that brings native DnD support to the browser making it much easier to code up. HTML 5 DnD is supported by all the major browsers like Chrome, Firefox 3.5 and Safari 4 etc.

When the input device mouse is moved while the object is being dragged which of the following event will get fired?

9) The drop event fires when the user releases the mouse button while dragging an object.


1 Answers

Answer

So in a twist of fate, a comment response on another question pointed me to the answer here. wolf-war deserves credit here for pointing me to the right event and method.

Anyway, on to the answer. The solution lies in using the dragover event, rather than using the dragenter event. dragover keeps firing as long as you are hovering.

Code Changes From Question Code

We get rid of the dragenter code:

document.addEventListener('dragenter', function(event) {
    event.target.style['border-bottom'] = 'solid 4px blue';
});

Replace it with:

document.addEventListener('dragover', function(event) {
    event.preventDefault();
    var bounding = event.target.getBoundingClientRect()
    var offset = bounding.y + (bounding.height/2);
    if ( event.clientY - offset > 0 ) {
        event.target.style['border-bottom'] = 'solid 4px blue';
        event.target.style['border-top'] = '';
    } else {
        event.target.style['border-top'] = 'solid 4px blue';
        event.target.style['border-bottom'] = '';
    }
});

Then in the drop section, we check to see which border the drop target has, and use that to insert the dragged content either above or below.

Full Working Code:

var dragging = null;

document.addEventListener('dragstart', function(event) {
    var target = getLI( event.target );
    dragging = target;
    event.dataTransfer.setData('text/plain', null);
    event.dataTransfer.setDragImage(self.dragging,0,0);
});

document.addEventListener('dragover', function(event) {
    event.preventDefault();
    var target = getLI( event.target );
    var bounding = target.getBoundingClientRect()
    var offset = bounding.y + (bounding.height/2);
    if ( event.clientY - offset > 0 ) {
       	target.style['border-bottom'] = 'solid 4px blue';
        target.style['border-top'] = '';
    } else {
        target.style['border-top'] = 'solid 4px blue';
        target.style['border-bottom'] = '';
    }
});

document.addEventListener('dragleave', function(event) {
    var target = getLI( event.target );
    target.style['border-bottom'] = '';
    target.style['border-top'] = '';
});

document.addEventListener('drop', function(event) {
    event.preventDefault();
    var target = getLI( event.target );
    if ( target.style['border-bottom'] !== '' ) {
        target.style['border-bottom'] = '';
        target.parentNode.insertBefore(dragging, event.target.nextSibling);
    } else {
        target.style['border-top'] = '';
        target.parentNode.insertBefore(dragging, event.target);
    }
});

function getLI( target ) {
    while ( target.nodeName.toLowerCase() != 'li' && target.nodeName.toLowerCase() != 'body' ) {
        target = target.parentNode;
    }
    if ( target.nodeName.toLowerCase() == 'body' ) {
        return false;
    } else {
        return target;
    }
}
ul {
  margin:0;
  padding:0
}
li {
  cursor:move;
  display:block;
  padding:20px 10px;
  background:white;
  border-bottom:solid 1px gray;
}
<ul>
    <li draggable="true">List Item 1</li>
    <li draggable="true">List Item 2</li>
    <li draggable="true">List Item 3</li>
    <li draggable="true">List Item 4</li>
    <li draggable="true">List Item 5</li>
    <li draggable="true">List Item 6</li>
    <li draggable="true">List Item 7</li>
    <li draggable="true">List Item 8</li>
    <li draggable="true">List Item 9</li>
    <li draggable="true">List Item 10</li>
</ul>
like image 112
Andy Mercer Avatar answered Oct 14 '22 11:10

Andy Mercer