16

Firstly, I know this question has been asked before, both in this site and others, but the answers are all rubbish for my scenario (if they're not rubbish entirely), and (at least for the question here: jQuery UI drop event of droppable fires on sortable), the suggestion is just to turn off .sortable entirely, which is most certainly not what I want to do.

Okay, I have this jquery (keep in mind if any options or html ids look silly, this is just for testing so I can try and figure this out):

$(function () {
    $("#sortable").sortable({
        revert: true
    });
    $("#draggable, #draggable2").draggable({
        connectToSortable: "#sortable",
        helper: "clone",
        revert: "invalid"
    });

    $("#sortable").droppable({
        drop: function (event, ui) { alert("done been triggered."); }
    });

    $("ul, li").disableSelection();
});

And here is the valid markup:

<div class="objectPaletteHolder">
    <ul>
        <li id="draggable" class="ui-state-highlight">Drag me down</li>
        <li id="draggable2" class="ui-state-highlight">Drag this down, too</li>
        <li id="draggable2" class="ui-state-highlight">Drag this down, also</li>
        <li id="draggable2" class="ui-state-highlight">Drag this down, as well</li>
        <li id="draggable2" class="ui-state-highlight">Drag this down, too</li>
        <li id="draggable2" class="ui-state-highlight">Drag this down, too</li>
        <li id="draggable2" class="ui-state-highlight">Drag this down, too</li>
        <li id="draggable2" class="ui-state-highlight click">Drag this down, click</li>
        <li id="draggable2" class="ui-state-highlight clicky">Drag this down, clicky</li>
    </ul>
</div>
<div class="editContainer">
    <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>
</div>

I do not have overlapping sortable divs or anything like that, and I think I understand 'why' this is happening (because sortable gets draggables properties by default?), I just can't seem to do anything about it.

The problem, of course, is that the...

drop: function (event, ui) { alert("done been triggered."); }

...is being triggered twice, when it is only needed once.

I would think that there would be a simple solution to this problem, for if this problem required a bunch of complex scripting to fix, than I would think that these particular jquery widgets wouldn't be worth all the trouble. Perhaps I am just confused?

Community
  • 1
  • 1
VoidKing
  • 6,282
  • 9
  • 49
  • 81

3 Answers3

28

That's a known issue using both sortable and droppable on the same element.

You can use the sortable's receive event instead.

jsFiddle Demo

$("#sortable").sortable({
        revert: true,
        receive: function (event, ui) {      
            alert("receive been triggered.");
        }
}).droppable({ });
Itay
  • 16,601
  • 2
  • 51
  • 72
  • omg, that's what I was after. I sifted through all sorts of events among the three different widgets, but I guess I didn't see "receive" or didn't think it applied to what I was after. Anyway, (this answer + you = the bomb-diggity). Thanks, man! – VoidKing Sep 06 '13 at 19:47
  • Cool just notice that it's attached to the sortable, not the droppable :) – Itay Sep 06 '13 at 19:48
  • Indeed. Also, I noticed that you have attached `.droppable({})` after the `.sortable` Could this/should this be done separately? – VoidKing Sep 06 '13 at 19:50
  • Yeah it could. I just prefer chaining. – Itay Sep 06 '13 at 19:51
  • Awesome, one last question, considering your preference, since I may not get the opportunity to ask this as easily later. Is this really just a preference, or are there any real practical upshots to chaining? I usually prefer the other way, for readability's sake, but there may be other downsides I'm not aware of. I'm sure the difference isn't huge or anything, just curious. – VoidKing Sep 06 '13 at 19:53
  • 1
    By chaining you can avoid calling `$("#sortable")` again. The is just one less function call. So it's a little bit more efficient. If I recall correctly, you can cache this object (`$sortable = $("#sortable")`) and use this variable, and that way the `getElementById` won't be called again and again. This article may help [Quick Guide: Chaining in jQuery](http://tobiasahlin.com/blog/quick-guide-chaining-in-jquery/) – Itay Sep 06 '13 at 19:59
2

I had a similar problem. There were 2 droppable elements, one of them had relative position, and another one - absolute. They were not nested, but a part of the one with absolute postion was above the part of the one with relative position (in jQuery docs this is called "intersect"). When I moved a draggable element to the "absolute" droppable, drop event was trigged twice. I have been searching for a few days, but didn't find any solution.

Then I decided to make a simple general workaround. Looks not very attractive, but this will help in any situation with firing 2 drop events.

drop: function( e, ui ) {
    if ( ! $('.already-dropped').length ) {
        $('body').addClass('already-dropped');
        setTimeout( function() {
            $('.already-dropped').removeClass('already-dropped');
        }, 100 );
        // callback code...
    }
}
Arsen K.
  • 5,494
  • 7
  • 38
  • 51
-2

Maybe I'm a little too late but I posted this hopefully it will help someone. This is the code I use. Basically, if the function is called twice, the interval between them should be quick (quicker than what human usually do) so I set the number to be 200 milliseconds.

I used localStorage to set a variable called "last_call". It's the time the function was called. In the next call, we will check if the function was called previously. If it wasn't called, the function will continue executing. If it was called, we will check when the last time it was called. If it was called within 200 milliseconds (which obviously machine call, not human call), we will not call it again.

    var d = new Date();
    var this_time = d.getTime();
    if ( localStorage.getItem("last_call") != null)
    {
        if ((this_time - parseInt(localStorage.getItem("last_call"))) < 100)
        {

            return;
        }

    }
    localStorage.setItem("last_call", this_time);
Dũng Trần Trung
  • 6,198
  • 3
  • 24
  • 20
  • @codingpuss basically because it looks like own crazy implementation of setTimeout(). But even with setTimeout() it will be a hack but not a neat solution. – walv Jan 31 '17 at 04:36