1

so I'm trying to make a schedule table where lectures can be dragged from a cell to another, unless the new cell already has a lecture, in this case dragging will be rejected. I'm trying to do this by adding and removing class 'has_lecture' from the cell to indicate if it should allow moving cells into or not. Here is my code.. but for some reason the remove class doesn't work.

$(".draggable").draggable({
            revert: 'invalid'
        });

        $(".droppable").droppable({
            accept: function () {
                return (!$(this).hasClass('has_lecture'))
            },
            drop: function (event, ui) {
                var $this = $(this);
                ui.draggable.position({
                    my: "center",
                    at: "center",
                    of: $this,
                    using: function (pos) {
                        $(this).animate(pos, 200, "linear");
                    }
                });
                $(this).addClass('has_lecture')

            },
            out: function () {
                    // this line doesn't work
                    $(this).removeClass('has_lecture')
            }
        });

1 Answers1

0

The accept function runs whenever the draggable moves over the droppable. That means it checks when you are dragging in, and it also checks when you are dragging out. Once you have dropped it in and set the has_lecture class, your accept function runs as you attempt to drag it out, and since it has the has_lecture class, it always returns false, and never gives your out function a chance to run.

So, knowing about this sort of unexpected behavior, the idea is: if the drop zone is empty, it should only accept if it does not have the has_lecture class. But if the dropzone is not empty, it should continue to accept the element that is currently in it. That will allow you to drag it out. So, once you drag an element into the dropzone, store a reference to the element on the dropzone via $(this).data('dropped', ui.draggable) and now your accept function can check to see if the element you are attempting to drag in or out is the one that has already been dragged in.

(HTML and css added so the snippet will run)

$(function () {
    $(".draggable").draggable({
        // revert: 'invalid'
    });

    $(".droppable").droppable({
        accept: function (draggable) {
            // new code: new accept clause, accept is not has_lecture OR if the element already dragged onto it is the one you are attempting to drag out
            return (!$(this).hasClass('has_lecture') || $(this).data('dropped') && $(this).data('dropped').is(draggable))
        },
        drop: function (event, ui) {
            var $this = $(this); // << your existing code
            ui.draggable.position({ // << your existing code
                my: "center", // << your existing code
                at: "center", // << your existing code
                of: $this, // << your existing code
                using: function (pos) { // << your existing code
                    $(this).animate(pos, 200, "linear"); // << your existing code
                } // << your existing code
            }); // << your existing code

            $this.addClass('has_lecture'); // << your existing code
            // new code: store a reference to the dropped element on the drop zone
            $this.data('dropped', ui.draggable)

        },
        out: function () {
            $(this).removeClass('has_lecture'); // << your existing code
            // new code: once you have moved it out, then delete the reference to it since your drop zone is now empty again
            $(this).data('dropped', null);
        }
    });
});
    .draggable {
        width: 90px;
        height: 90px;
        padding: 0.5em;
        float: left;
        margin: 10px 10px 10px 0;
        border: 1px solid black;
    }

    .droppable {
        width: 120px;
        height: 120px;
        padding: 0.5em;
        float: left;
        margin: 10px;
        border: 1px solid black;
    }

    h3 {
        clear: left;
    }

    .has_lecture {
        background-color: blue;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<div class="draggable" class="ui-widget-content">
    <p>Drag me to my target</p>
</div>

<div class="draggable" class="ui-widget-content">
    <p>Drag me to my target</p>
</div>

<div class="draggable" class="ui-widget-content">
    <p>Drag me to my target</p>
</div>

<div class="droppable" class="ui-widget-header">
    <p>Drop here</p>
</div>
chiliNUT
  • 18,989
  • 14
  • 66
  • 106
  • Thanks, it worked but do you have an idea how do I initiate the has_lecture class if the draggable item is initially appended inside a droppable? I tried 1- adding has_lecture class once the draggable is dynamically appended, but it got treated as an original class. I mean when I move the draggable from cell a to b has_lecture class won't be removed, but from b to c it gets removed 2- I tried create: function (event, ui){ } but also it didn't work. – Heila Al-Mogren Jan 18 '21 at 02:23
  • 1
    Hey @HeilaAl-Mogren, I think the issue is that if you are going to programmatically pre-append an element to a droppable, you have to use the `jQuery.simulate` plugin; if you just append the element to the dropzone using `append` or something it won't work. See this fiddle I made: https://jsfiddle.net/nLt0q5h2/ and see this other stackoverflow about dynamically appending to droppable: https://stackoverflow.com/questions/16848165/programmatically-drag-and-drop-element-onto-another-element and this codepen where I found the `trigger_drop` function https://codepen.io/xlicodes/pen/ZMLmxZ – chiliNUT Jan 18 '21 at 03:18