jQuery Draggable + Sortable - How to reject a drop into the sort?

lopsided picture lopsided · Aug 19, 2011 · Viewed 11.4k times · Source

I have a sortable list of videos and a draggable set of videos. Basically I want to make sure that the videos dragged in are not in the first 5 minutes of video. As the video lengths vary I want to test this on the drop - add up the time up to then and if not 5mins revert and show an error.

I have tried hooking into all of the callbacks for draggable and sortable (including the undocumented revert callback) to do my test but whatever I try, the dom always gets changed (and sortable calls its update callback)...

Does anyone have any suggestions?

Answer

Frédéric Hamidi picture Frédéric Hamidi · Aug 19, 2011

You can revert the drag operation by calling the cancel method of the draggable widget (that method is undocumented, but its name does not start with an underscore, which arguably makes it "safer" to use reliably). It only works during the start event, though, as other events occur too late to trigger the revert animation.

However, the sortable widget will still register a drop even if the drag operation is canceled, so you also have to remove the newly-added item (during the stop event, as the start event occurs too early):

$("#yourSortable").sortable({
    start: function(event, ui) {
        if (!canDropThatVideo(ui.item)) {
            ui.sender.draggable("cancel");
        }
    },
    stop: function(event, ui) {
        if (!canDropThatVideo(ui.item)) {
            ui.item.remove();
            // Show an error...
        }
    }
});

You can see the results in this fiddle (the fourth item will always revert).

Update: As John Kurlak rightfully points out in the comments, the item does not revert because of the call to draggable("cancel"), but because ui.sender is null in our case. Throwing anything results in the same behaviour.

Alas, all the other combinations I tried result in the item being reverted without the animation taking place, so maybe our best bet is to avoid accessing ui.sender and instead write something like:

start: function(event, ui) {
    if (!canDropThatVideo(ui.item)) {
        throw "cancel";
    }
}

The uncaught exception will still appear in the console, though.