steros
steros

Reputation: 1924

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/

Upvotes: 2

Views: 1203

Answers (2)

Julien Gr&#233;goire
Julien Gr&#233;goire

Reputation: 17124

You could handle the helper removal in the update event. That way number of elements in the list is consistent on both receive and update. But since you don't have access to the helper in the update event, you need to store it before. This can be done on receive event. This way:

receive: function (event, ui) {
        //store current helper
        cur_helper = ui.helper;
        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'
            });
        } 
    },
    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 {
            //remove the helper if the list exceeds 3 
            cur_helper.remove();
            console.log("update didn't fire");
            ui.item.effect('highlight', {
                color: 'red'
            });
        }
    }

fiddle: http://jsfiddle.net/p0nft4mj/

Upvotes: 2

Trevor
Trevor

Reputation: 16116

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

Upvotes: 1

Related Questions