Search code examples
jqueryjquery-uijquery-ui-draggablejquery-ui-sortablejquery-ui-droppable

limit number of elements in a sortable connected with draggables and a droppable


I have a sortable, connected draggables and also a droppable. It is basically a bucket and a sortable list of items. I want to limit the number of items you can drag from the bucket to the list.

My problem is that I got as far as reverting the draggable if the number of items in the list is to high. Also I remove the item from the list so it is not stuck there too. But here is my problem. The update event of the sortable fires last. So when the update event fires the element was removed already so there are less items than allowed. I don't want this as I update my database in the update event.

I know this question has been answered before but read the whole question first.

Here is the html:

<fieldset id="container">
<div style="float: left; width: 49%;">
    <fieldset class="choosen" style="float: left; width: 100%;">
        <legend>choosen</legend>
        <div style="overflow-y: auto; height: 40em;" class="box">
            <ul style="min-height: 40em;" class="imglist grey sortable">
                <li data-id="1" class="acceptable">Element1</li>
                <li data-id="2" class="acceptable">Element2</li>
                <li data-id="3" class="acceptable">Element3</li>
            </ul>
        </div>
    </fieldset>
</div>
<div style="float: left; margin-left: 2%; width: 49%;">
    <fieldset class="bucket" style="float: left; width: 100%;">
        <legend>bucket</legend>
        <div style="overflow-y: auto; height: 40em;" class="box droppable">
            <ul style="min-height: 40em;" class="imglist grey">
                <li data-id="5" class="draggable"><a>Element5</a>

                </li>
                <li data-id="6" class="draggable"><a>Element6</a>

                </li>
                <li data-id="7" class="draggable"><a>Element7</a>

                </li>
                <li data-id="8" class="draggable"><a>Element8</a>

                </li>
            </ul>
        </div>
    </fieldset>
</div>

Here is the jquery code:

var helperClone = function (event, object) {
    if (undefined == object) {
        var helper = $(this).clone(),
            height = $(this).height(),
            width = $(this).width();
    } else {
        var helper = object.clone(),
            height = object.height(),
            width = object.width();
    }
    helper.css({'width': width, 'height': height});
    return helper;
},
sortable = $(".sortable"),
draggable = $(".draggable"),
revertCheck = function () {
    console.log("revertCheck", sortable.find("li").length > 4, sortable.find("li").length);
    if (sortable.find("li").length > 4) {
        return true;
    }
    return false;
};
draggable.draggable({
    connectToSortable: sortable,
    helper: helperClone,
    revert: revertCheck
});
sortable.sortable({
    placeholder: "ui-state-highlight",
    helper: helperClone,
    receive: function (event, ui) {
        console.log("receive", $(this).find("li").length < 4,     $(this).find("li").length);
        if ($(this).find("li").length < 4) {
            ui.item.remove();
            $(this).data().uiSortable.currentItem.addClass('acceptable').css({
            'width': '100%',
                'height': 'unset'
        });
    } else {
        ui.helper.remove();
    }
},
update: function (event, ui) {
    console.log("update", $(this).find("li").length < 4, $(this).find("li").length);
    if ($(this).find("li").length < 4) {
        //do stuff
        console.log("update fired");
    } else {
        console.log("update didn't fire");
        ui.item.effect('highlight', {
            color: 'red'
        });
        }
    }
});

$(".droppable").droppable({
    accept: 'li.acceptable',
    over: function (event, ui) {
        $(this).effect('highlight');
    },
    drop: function (event, ui) {
        var target = $(this).find('ul');
        var clone = ui.draggable.clone().css({
            'display': 'list-item',
                'position': 'unset'
        });
        ui.draggable.remove();
        clone.appendTo(target).show().removeClass('acceptable').draggable({
            connectToSortable: sortable,
            containment: "#container",
            helper: helperClone,
            revert: revertCheck
        });
    }
});
$("ul, li").disableSelection();

Here is a working example: http://jsfiddle.net/adq6exkp/

Other answers recommend calling sortable.sortable("cancel"); in the receive event of the sortable. So in the above code you would add this to the else part of receive event function:

$(ui.sender).sortable('cancel');

Problem is this raises the following error:

Error: cannot call methods on sortable prior to initialization; attempted to call method 'cancel' http://code.jquery.com/jquery-1.10.1.js Line 516

Here a working example for this error: http://jsfiddle.net/adq6exkp/1/


Solution

  • If I understand, you don't want the update to fire if a revert took place.

    You can accomplish this with a boolean.

    sortable.sortable({
        placeholder: "ui-state-highlight",
        helper: helperClone,
        receive: function (event, ui) {
            console.log("receive", $(this).find("li").length < 4, $(this).find("li").length);
            if ($(this).find("li").length < 4) {
                ui.item.remove();
                $(this).data().uiSortable.currentItem.addClass('acceptable').css({
                    'width': '100%',
                        'height': 'unset'
                });
            } else {
                this.reverted = true;
                ui.helper.remove();
            }
        },
        update: function (event, ui) {
            console.log("update", $(this).find("li").length < 4, $(this).find("li").length);
            if (!this.reverted) {
                //do stuff
                console.log("update fired");
            } else {
                this.reverted = false;
                console.log("update didn't fire");
                ui.item.effect('highlight', {
                    color: 'red'
                });
            }
        }
    });
    

    Fiddle