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

2

11

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?

Corse answered 19/8, 2011 at 16:57 Comment(0)
B
10

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.

Bedstraw answered 19/8, 2011 at 19:31 Comment(8)
Hey Thanks for the answer! Im struggling to get it to work though - the test (canDropThatVideo) needs to find the position it is being dropped in the list so it can compare with adjacent videos. I tried something like: // get adjacent vids var nextVid = $(ui.item).next(); var prevVid = $(ui.item).prev(); but these dont seem to get the correct ones - probably because its in the start event... Should that work?Corse
@user612911, it's more complicated but it can work. You have to bind to the sort event instead of start and use ui.placeholder.next() and ui.placeholder.prev(), respectively. You'll also probably have to remember the decision you made about the item somehow (maybe with data()), in order for the stop handler to know what to do. (As an aside, you don't need to call $() on the ui members: they're already jQuery objects.)Metcalfe
Great, I have it working well now. I also attached some logic to the start event of the draggable to add some 'cantDropHere' classes to the elements that are not valid drop spaces and then excluded those classes from the sortable. Thanks!Corse
ui.sender seems to be null in your code sample and JSFiddle... I think it is null when the element is coming from a draggable instead of another sortable.Pincushion
@John, strange, the fiddle does use a draggable as the element's source and works for me (I also tested it with jQuery 1.9.1 / jQuery UI 1.9.2 to be sure). Maybe there's something on your end?Metcalfe
@FrédéricHamidi It works, but the error is still being thrown. I think when an error occurs, the draggable cancels the event (or certain code doesn't execute that allows it to be placed). I'll check the jQuery source. You can easily replace ui.sender.draggable("cancel"); with blah and it still works.Pincushion
@John, you're absolutely right, the item reverts because of the error, not because cancel() was caught. Throwing an arbitrary string also reverts the item. I'll update my answer.Metcalfe
In case anybody is interested you can still call draggable.cancel like in this example if you are able to access the reference to draggable that is instantiated on the item being dragged (actual item not clone). You will have to make sure that the actual reference is passed, I attach it to helper as data('element') (through implementation of helper on draggable), after that I have access to initial element that has draggable instance on it through ui.helper.data('element').draggable('cancel').Zoo
A
3

I found a different way. If you dont mind not having the animation of it floating back to it's original place you can always use this

.droppable({
    drop: function (event, ui) {

        var canDrop = false;

        //if you need more calculations for
        //validation, like me, put them here

        if (/*your validation here*/)
            canDrop = true;

        if (!canDrop) {
            ui.draggable.remove();
        }
        else{
            //you can put whatever else you need here
            //in case you needed the drop anyway
        }
    }
}).sortable({
    //your choice of sortable options
});

i used this because i needed the drop event either way.

Aftergrowth answered 1/4, 2014 at 11:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.