views:

56

answers:

1

See the code here on jsbin. I'm basically trying to create a series of switches. Each filled red square can be dragged up and down. The red outlines on the bottom are drop zones. When a square is dragged over a drop zone that is eligible to accept it the drop zone should turn pink.

There are two problems with this code. One is that while the motion of the toggles is constrained to the y-axis, they can still be dropped on any drop zone. Click and drag a toggle and slide around the bottom row and you'll see the drop zones turn pink even though the toggle stays in place.

Which leads to the second problem. To address this issue I tried to use the scope option, which groups drags and drops. A drag can only drop on a drop zone with the same scope. In the above example the lines that add scope are commented out. The scope for each drag and drop is "Default."

If you uncomment those two lines (click the tab in the upper right if you're new to jsbin, then click preview after a change) you'll see that instead of restricting each drag to one drop zone, they can no longer drop on any drop zone. The callback function never fires.

For convenience, here is the javascript portion of the example:

$(document).ready( function() {
    var draggables = $('div.dragMe'),
    droppables = $('div.dropMe');

    draggables.draggable(
    {
        axis: 'y',
        containment: 'parent'
    });

    droppables.droppable(
    {
        hoverClass: 'dropped',
        drop: dropCallBack
    });

    draggables.each(function(index) {
        //$(this).draggable('option', 'scope', ''+index);
        //droppables.eq(index).droppable('option', 'scope', ''+index);

        $(this).text( $(this).draggable('option', 'scope') )
        droppables.eq(index).text( droppables.eq(index).droppable('option', 'scope') );
    });

    function dropCallBack(e, ui) {
        alert('Dropped!');
    }
});
+1  A: 

There's a bug in jQuery when setting the scope option of a droppable via the option function. jQuery maintains an array with all registered sortables (let's call it S for now), where each key is one specific scope. When you drop a draggable element into the droppable, jQuery then checks the scope attribute of the draggable and checks if the droppable you are trying to drag into is present in S[scope]. If it isn't it means that the droppable you are tring to drop into is not in the same scope as the draggable.

The problem is that when you change the scope option by doing .droppable('option', 'scope', ...), the array S isn't updated. Everything else (as far as I can see) is updated correctly (option attribute of the actual jQuery object etc), leading to "correct" results being returned when getting the scope option via .droppable('option', 'scope').

I found a couple of other people having the same question and no solutions came up, and this very question came up when i googled for solutions ("jquery droppable scope option"), so I thought it could be useful to provide a temporary solution until it is fixed. I made an extention function, not very well tested with regards to possible conflicts with other options, but atleast it's a start. $.ui.ddmanager.droppables is the array I called S previously.

jQuery.fn.extend({

    setDroppableScope: function(scope) {
        return this.each(function() {
            var currentScope = $(this).droppable("option","scope");
            if (typeof currentScope == "object" && currentScope[0] == this) return true; //continue if this is not droppable

            //Remove from current scope and add to new scope
            var i, droppableArrayObject;
            for(i = 0; i < $.ui.ddmanager.droppables[currentScope].length; i++) {
                var ui_element = $.ui.ddmanager.droppables[currentScope][i].element[0];

                if (this == ui_element) {
                    //Remove from old scope position in jQuery's internal array
                    droppableArrayObject = $.ui.ddmanager.droppables[currentScope].splice(i,1)[0];
                    //Add to new scope
                    $.ui.ddmanager.droppables[scope] = $.ui.ddmanager.droppables[scope] || [];
                    $.ui.ddmanager.droppables[scope].push(droppableArrayObject);
                    //Update the original way via jQuery
                    $(this).droppable("option","scope",scope);
                    break;
                }
            }
        });
    }
});

Your example would then look like

draggables.each(function(index) {
    $(this).draggable('option', 'scope', ''+index);
    droppables.eq(index).setDroppableScope(''+index);

    $(this).text( $(this).draggable('option', 'scope') )
    droppables.eq(index).text( droppables.eq(index).droppable('option', 'scope') );
});

Here's the updated jsbin

Simen Echholt
Vindicated! Behaves exactly as expected. Thanks for the insight into a sneaky bug, and for providing the code to fix it. As you said, it reports the correct scope, but then acts like it doesn't care!
jasongetsdown