31

I'm trying to make a list of elements which can be repositioned by dragging and dropping. The first element, Box 1, works just fine 100% of the time. Sometimes the second box works, but none of the others work as expected. They seem to fire off all the drag events at once as soon as you start dragging them.

I'm using the latest Chrome (v23) if that matters.

var $questionItems = $('.question-item');

$questionItems
  .on('dragstart', startDrag)
  .on('dragend', removeDropSpaces)
  .on('dragenter', toggleDropStyles)
  .on('dragleave', toggleDropStyles);


function startDrag(){
  console.log('dragging...');
  addDropSpaces();
}

function toggleDropStyles(){
  $(this).toggleClass('drag-over');
  console.log(this);
}


function addDropSpaces(){
  $questionItems.after('<div class="empty-drop-target"></div>');
  console.log('drop spaces added');
}

function removeDropSpaces(){
  $('.empty-drop-target').remove();
  console.log('drop spaces removed');
}

Here's the HTML:

<div class="question-item" draggable="true">Box 1: Milk was a bad choice.</div>
<div class="question-item" draggable="true">Box 2: I'm Ron Burgundy?</div>
<div class="question-item" draggable="true">Box 3: You ate the entire wheel of cheese?</div>
<div class="question-item" draggable="true">Box 4: Scotch scotch scotch</div>

Here is a jsFiddle of my code: http://jsfiddle.net/zGSpP/5/

Bill
  • 333
  • 1
  • 3
  • 5

6 Answers6

39

Had this problem just now - it is a Chrome bug, you must not manipulate the DOM in dragStart event, or else dragEnd fires immediately. The solution for me turned out to be - use setTimeout() and manipulate the DOM inside the function you timeout.

Ivan Koshelev
  • 3,830
  • 2
  • 30
  • 50
  • This worked for me. Also I had the same code working for me on one page variant but not the other and the only difference between them was that parent element for the manipulated element wasn't rendering (was hidden) in the case when it was failing. – Anton Bielousov Mar 13 '17 at 17:18
  • generally just works well, but sometime must works like this answer..(this worked for me). i don't know what is wrong... – JaeIL Ryu May 31 '22 at 09:01
  • For users of modern web frameworks such as Angular, Vue, and React: also consider data-binding mechanisms that can make these DOM manipulations a bit harder to detect. – David Brem Mar 16 '23 at 11:52
12

It seems to be a Chrome bug : https://groups.google.com/a/chromium.org/forum/?fromgroups=#!msg/chromium-bugs/YHs3orFC8Dc/ryT25b7J-NwJ

sdespont
  • 13,915
  • 9
  • 56
  • 97
9

Not sure whether it is a bug or not, but I think the problem comes in when you change the DOM in the dragstart handler. The reason the first box works I presume has something to do with the fact that its position is before the other boxes in the DOM and when you change the DOM, the position (?) of the first one isn't affected and the dragdrop events fire reliably.

You will need to refactor your code somewhat, and add the dropSpaces in the dragenter event handler. You would need to change your addDropSpaces method to accommodate the fact that it will be called multiple times, though.

Joerg
  • 3,553
  • 4
  • 32
  • 41
1

Life is easier with jQuery. This is exactly what you described you wanted, using jQueryUI.

http://jqueryui.com/draggable/#sortable

which allows sorting. The code:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>jQuery UI Draggable + Sortable</title>
    <link rel="stylesheet" href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" />
    <script src="http://code.jquery.com/jquery-1.8.3.js"></script>
    <script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
    <link rel="stylesheet" href="/resources/demos/style.css" />
    <style>
    ul { list-style-type: none; margin: 0; padding: 0; margin-bottom: 10px; }
    li { margin: 5px; padding: 5px; width: 150px; }
    </style>
    <script>
    $(function() {
        $( "#sortable" ).sortable({
            revert: true
        });
        $( "#draggable" ).draggable({
            connectToSortable: "#sortable",
            helper: "clone",
            revert: "invalid"
        });
        $( "ul, li" ).disableSelection();
    });
    </script>
</head>
<body> 
<ul>
    <li id="draggable" class="ui-state-highlight">Drag me down</li>
</ul>

<ul id="sortable">
    <li class="ui-state-default">Item 1</li>
    <li class="ui-state-default">Item 2</li>
    <li class="ui-state-default">Item 3</li>
    <li class="ui-state-default">Item 4</li>
    <li class="ui-state-default">Item 5</li>
</ul>

</body>
</html> 

also see: http://jqueryui.com/sortable/

Try adding the events manually, the long way, something like :

$questionItems[0].on('dragstart', startDrag); 
$questionItems[0].on('dragend', removeDropSpaces) 
$questionItems[0].on('dragenter', toggleDropStyles) 
$questionItems[0].on('dragleave', toggleDropStyles); 
$questionItems[1].on('dragstart', startDrag); 
$questionItems[1].on('dragend', removeDropSpaces) 
$questionItems[1].on('dragenter', toggleDropStyles)     
$questionItems[1].on('dragleave', toggleDropStyles); 

etc. just to see the effect?

SoluableNonagon
  • 11,541
  • 11
  • 53
  • 98
  • I really need to be able to do more than sort the list, such as when an item is dragged on top of another item they become grouped. – Bill Jan 07 '13 at 22:35
  • you still can, you just do it through jQuery for events. $( ".selector" ).on( "drag", function( event, ui ) {} ); and $( ".selector" ).on( "dragstart", function( event, ui ) {} ); And it has callbacks so you can still use the functions you already built. – SoluableNonagon Jan 07 '13 at 22:42
  • Also, have you tried to add each .on() to each single box item manually? Just to see the effect? – SoluableNonagon Jan 07 '13 at 22:47
  • Something like $questionItems[0].on('dragstart', startDrag); $questionItems[0].on('dragend', removeDropSpaces) $questionItems[0].on('dragenter', toggleDropStyles) $questionItems[0].on('dragleave', toggleDropStyles); $questionItems[1].on('dragstart', startDrag); $questionItems[1].on('dragend', removeDropSpaces) $questionItems[1].on('dragenter', toggleDropStyles) $questionItems[1].on('dragleave', toggleDropStyles); etc. just to see the effect? – SoluableNonagon Jan 07 '13 at 22:49
  • Yeah I'm going to give jQ UI's sortable a go. At this point I just want it to work. Good to know I can tap into the events too. Thanks. – Bill Jan 08 '13 at 15:40
-1

change this function

function addDropSpaces()
{
  $(this).after('<div class="empty-drop-target"></div>');
}

in your version you are adding that div after each of the $questionItems.

-1

You need to prevent the "event" from bubbling up so it doesn't fire up other events. See https://developer.mozilla.org/en-US/docs/DOM/event.stopPropagation

For startDrag, toggleDropStyles, removeDropSpaces functions, you'd write something like this:

function startDrag(ev) {
   ev.stopPropagation();
   ... your code here ...
}
Lance
  • 865
  • 8
  • 19
  • It seems to prevent the rest of the function from executing? http://jsfiddle.net/zGSpP/11/ – Bill Jan 07 '13 at 21:27