Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JQuery UI Drag/Droppable with multiple scopes?

I want to have several classes of draggables, each one corresponding to a class of droppables. But in addition, I want to have a separate "waste bin", where all the draggables can be dropped until a suitable droppable can be found for them.

Now, this can be easily achieved with an accept function. However, I may have up to 20 classes, each one with 30-40 draggables/droppables. So if I use an "accept" function for this, the moment I pick up a draggable, my chrome freezes as it runs tests for every droppable on the screen :(

This can be solved if I use the 'scope' property, since it seems to be using some different way. However, when I use a scope, I can't seem to implement the "waste bin" concept, since it can only have one scope!

Is there some way to by-pass this problem? Give the draggables more than one scope, or giving the waste bin many scopes? Or maybe some other solution I can't think of?

like image 381
Gilthans Avatar asked Jun 16 '12 19:06

Gilthans


1 Answers

Internally jQuery UI will run the following code whenever you start dragging a draggable to determine which droppables are eligible to receive the draggable.

var m = $.ui.ddmanager.droppables[t.options.scope] || [];
var type = event ? event.type : null; // workaround for #2317
var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();

droppablesLoop: for (var i = 0; i < m.length; i++) {

    if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue;   //No disabled and non-accepted
    for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
    m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue;                                   //If the element is not visible, continue

    if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables

    m[i].offset = m[i].element.offset();
    m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };

}

As you can see the code is non trivial and would explain why you're seeing slow performance every time you start dragging.

One thing to note is that the very first thing checked in the droppablesLoop is whether the droppable is disabled.

Therefore, to increase performance you could always manually disable the appropriate droppable widgets which will make you quickly jump out of the code block above. You can do this by using the start event on the draggable, which will fire first.

$('.draggable').draggable({
    start: function() {
        $('.invalid-droppable-elements').droppable('option', 'disabled', true);
    },
    stop: function() {
        $('.invalid-droppable-elements').droppable('option', 'disabled', false);
    }
});

This essentially makes you implement the accept / scope logic yourself and the performance impact is up to your algorithm. It shouldn't be that bad to implement though. The reason the plugins are as slow as they are is because they have to handle for a LOT of different situations.

jQuery UI does not support adding multiple scopes to individual draggable / droppable elements but you could roll that functionality on your own.

I put an example together to show this here - http://jsfiddle.net/tj_vantoll/TgQTP/1/.

like image 177
TJ VanToll Avatar answered Oct 22 '22 18:10

TJ VanToll