I want to drag an element into TWO OR MORE droppable areas, but those droppable areas need to be wholly contained within my draggable object.
The problem is, none of
Modifying $.ui.intersect seems to be the best approach. You have different options. If you don't need that much flexibility, you can simply add a tolerance type, 'cover' for example. Then you just need to add a case to the switch that checks the tolerance type in intersect, which can be precisely the inverse of 'fit'. Like this:
case 'fit':
return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
break;
case 'cover':
return (l >= x1 && x2 >= r && t >= y1 && y2 >= b);
break;
See: https://jsfiddle.net/6nyqja4a/4/
Or if you want more flexibility, you add a case where tolerance is a function. Then you can pass a function in the option, which allows you to have precise tolerance for different droppable. Something like this for example: In interserct function:
if (toleranceMode instanceof Function) {
return toleranceMode(draggable, droppable, x1, x2, y1, y2, l, r, t, b);
} else {
switch (toleranceMode) {
case 'fit':
return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
break;
...
And you call it like this:
$('.droppable').droppable({
hoverClass: "yellow",
tolerance: function(drag, drop, x1, x2, y1, y2, l, r, t, b) {
return (l >= x1 && x2 >= r && t >= y1 && y2 >= b);
},
drop: function(event, ui) {
$("#value_" + $(this).data("id")).val(ui.draggable.data("id"));
console.log("Item " + $(this).data("id") + " taken by " + ui.draggable.data("id"));
}
});
See: https://jsfiddle.net/h4wm3r09/3/
From jquery 1.12 $.ui.intersect function is scoped so that it cannot be directly modified afterwards. It is called in $.ui.ddmanager as a local variable, so even if you modify $.ui.intersect, it won't be used. Customizing it is a bit more complex. You can do it this way, basically you rescope intersect and then redefine drag and drop method on $.ui.ddmanager so that it calls the modified intersect:
var intersect = $.ui.intersect = ( function() {
function isOverAxis( x, reference, size ) {
return ( x >= reference ) && ( x < ( reference + size ) );
}
return function( draggable, droppable, toleranceMode, event ) {
if ( !droppable.offset ) {
return false;
}
var x1 = ( draggable.positionAbs ||
draggable.position.absolute ).left + draggable.margins.left,
y1 = ( draggable.positionAbs ||
draggable.position.absolute ).top + draggable.margins.top,
x2 = x1 + draggable.helperProportions.width,
y2 = y1 + draggable.helperProportions.height,
l = droppable.offset.left,
t = droppable.offset.top,
r = l + droppable.proportions().width,
b = t + droppable.proportions().height;
if (toleranceMode instanceof Function) {
return toleranceMode(draggable, droppable, x1, x2, y1, y2, l, r, t, b);
} else {
switch ( toleranceMode ) {
case "fit":
return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );
case "intersect":
return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half
x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half
t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half
y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half
case "pointer":
return isOverAxis( event.pageY, t, droppable.proportions().height ) &&
isOverAxis( event.pageX, l, droppable.proportions().width );
case "touch":
return (
( y1 >= t && y1 <= b ) || // Top edge touching
( y2 >= t && y2 <= b ) || // Bottom edge touching
( y1 < t && y2 > b ) // Surrounded vertically
) && (
( x1 >= l && x1 <= r ) || // Left edge touching
( x2 >= l && x2 <= r ) || // Right edge touching
( x1 < l && x2 > r ) // Surrounded horizontally
);
default:
return false;
}
}
};
} )();
Then this, where you don't change anything, you just redefine them exactly the same way.
$.ui.ddmanager.drag = function( draggable, event ) {
// If you have a highly dynamic page, you might try this option. It renders positions
// every time you move the mouse.
if ( draggable.options.refreshPositions ) {
$.ui.ddmanager.prepareOffsets( draggable, event );
}
// Run through all droppables and check their positions based on specific tolerance options
$.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {
if ( this.options.disabled || this.greedyChild || !this.visible ) {
return;
}
var parentInstance, scope, parent,
intersects = intersect( draggable, this, this.options.tolerance, event ),
c = !intersects && this.isover ?
"isout" :
( intersects && !this.isover ? "isover" : null );
if ( !c ) {
return;
}
if ( this.options.greedy ) {
// find droppable parents with same scope
scope = this.options.scope;
parent = this.element.parents( ":data(ui-droppable)" ).filter( function() {
return $( this ).droppable( "instance" ).options.scope === scope;
} );
if ( parent.length ) {
parentInstance = $( parent[ 0 ] ).droppable( "instance" );
parentInstance.greedyChild = ( c === "isover" );
}
}
// We just moved into a greedy child
if ( parentInstance && c === "isover" ) {
parentInstance.isover = false;
parentInstance.isout = true;
parentInstance._out.call( parentInstance, event );
}
this[ c ] = true;
this[ c === "isout" ? "isover" : "isout" ] = false;
this[ c === "isover" ? "_over" : "_out" ].call( this, event );
// We just moved out of a greedy child
if ( parentInstance && c === "isout" ) {
parentInstance.isout = false;
parentInstance.isover = true;
parentInstance._over.call( parentInstance, event );
}
} );
}
$.ui.ddmanager.drop = function( draggable, event ) {
var dropped = false;
// Create a copy of the droppables in case the list changes during the drop (#9116)
$.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {
if ( !this.options ) {
return;
}
if ( !this.options.disabled && this.visible &&
intersect( draggable, this, this.options.tolerance, event ) ) {
dropped = this._drop.call( this, event ) || dropped;
}
if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ],
( draggable.currentItem || draggable.element ) ) ) {
this.isout = true;
this.isover = false;
this._deactivate.call( this, event );
}
} );
return dropped;
}
https://jsfiddle.net/u6wfj8mj/1/
Obviously, this one is a bit more prone to errors and there might be a better way to achieve this. Normally you could extend the widgets for example, which would be cleaner. But intersect and ddmanager are used both in draggable and droppable and are not directly in these widgets. So it's harder to extend in a clean way. You could also put the logic directly in drag event and drop event of you draggables and droppables, but since there's a default tolerance, not sure it's much better.
How to make a jQuery UI’s Droppable accept a Draggable when it’s top left corner gets inside? I created jquery.ui.droppable-patch.js for jQuery UI Droppable 1.9.2
(function ($, undefined) {
$.ui.intersect = function (draggable, droppable, toleranceMode) {
if (!droppable.offset) return false;
var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
var l = droppable.offset.left, r = l + droppable.proportions.width,
t = droppable.offset.top, b = t + droppable.proportions.height;
switch (toleranceMode) {
case 'fit':
return (l <= x1 && x2 <= r
&& t <= y1 && y2 <= b);
break;
case 'intersect':
return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
&& x2 - (draggable.helperProportions.width / 2) < r // Left Half
&& t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
&& y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
break;
case 'pointer':
var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
return isOver;
break;
case 'touch':
return (
(y1 >= t && y1 <= b) || // Top edge touching
(y2 >= t && y2 <= b) || // Bottom edge touching
(y1 < t && y2 > b) // Surrounded vertically
) && (
(x1 >= l && x1 <= r) || // Left edge touching
(x2 >= l && x2 <= r) || // Right edge touching
(x1 < l && x2 > r) // Surrounded horizontally
);
break;
case "top-left-touch":
return ( y1 >= t && y1 <= b ) && ( x1 >= l && x1 <= r );
case "top-right-touch":
return ( y1 >= t && y1 <= b ) && ( x2 >= l && x2 <= r );
case "bottom-left-touch":
return ( y2 >= t && y2 <= b ) && ( x1 >= l && x1 <= r );
case "bottom-right-touch":
return ( y2 >= t && y2 <= b ) && ( x2 >= l && x2 <= r );
default:
return false;
break;
}
};
}(jQuery));