Reputation: 146302
I have the following:
I am trying to set it up so that when you drag the item, it only gets dropped to the div element which you can see, and is not covered up.
So I used this js:
$(".draggable").draggable({
helper: "clone"
})
$("#bottom, .draggable").droppable({
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
$this.append($dragged.clone());
},
hoverClass: "dragHover"
})
But it drops the element in both places even though only one of the drop zones is not visible!
How do I fix it so that this does not happen?
Fiddle: http://jsfiddle.net/maniator/Wp4LU/
Extra Info to recreate the page without the fiddle:
HTML:
<div id="top">
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
</div>
<div id="bottom"></div>
CSS:
.draggable {
border: 1px solid green;
background: white;
padding: 5px;
}
.dragHover{
background: blue;
}
#top {
height: 500px;
overflow-y: scroll;
}
#bottom {
height: 150px;
overflow-y: scroll;
border: red solid 4px;
}
Upvotes: 7
Views: 1388
Reputation: 6131
If you just want to drop on element you can see you could change your selector :
$(".draggable:visible").draggable({
helper: "clone"
});
$("#bottom, .draggable:visible").droppable({
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
$this.append($dragged.clone());
},
hoverClass: "dragHover"
});
Or when you hide an element change its' draggable class by something else.
Upvotes: 0
Reputation: 160833
Try setting with accept
function.
The working demo.
$("#bottom, .draggable").droppable({
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
$this.append($dragged.clone());
},
accept: function () {
var $this = $(this), divTop= $("#top");
if ($this.is(".draggable")) {
return $this.offset().top < divTop.offset().top + divTop.height() ;
}
return true;
},
hoverClass: "dragHover"
});
Upvotes: 2
Reputation: 22007
According to the sources (jquery.ui.droppable.js), the drop operation will search for every eligible droppable and apply the drop function to every one that intersects with it:
drop: function(draggable, event) {
var dropped = false;
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
if(!this.options) return;
if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
dropped = this._drop.call(this, event) || dropped;
(Old versions had the last "OR" condition reversed, so it would only apply to a single droppable. Try your fiddle using jQuery 1.5.2 / jQuery UI 1.8.9, and see that it only drops to one element, albeit the "wrong" one...)
And every tolerance mode currently implemented in the $.ui.intersect
function only take into account the (x,y) coordinates:
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;
...
So, unless someone adds a z-index aware tolerance mode, your only option is to work around the issue somehow. I'd suggest first adding every droppable candidate to a set and, when it's time to drop, select only the one that is "closest" to the screen:
$("#bottom, .draggable").droppable({
over: function(event, ui) {
if ( !ui.draggable.data("drop-candidates") )
ui.draggable.data("drop-candidates",[]);
ui.draggable.data("drop-candidates").push(this);
},
out: function(event, ui) {
var that = this,
candidates = ui.draggable.data("drop-candidates") || [];
ui.draggable.data("drop-candidates", $.grep(candidates, function(e) {
return e != that;
});
},
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
var candidates = $.data("drop-candidates").sort(closestToScreen);
if ( candidates[0] == this )
$this.append($dragged.clone());
},
hoverClass: "dragHover"
})
Now, implementing the closestToScreen
comparator is the tricky part. The W3C CSS Specification describes how rendering engines should sort elements to paint, but I wasn't able to find so far an easy way to access this information. I also asked this question here at SO, maybe someone will find a good way.
P.S. If modifying the jQuery UI source is an option, you could try implementing a z-index aware tolerance mode using document.getElementFromPoint
, as this answer to said question suggested:
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;
switch (toleranceMode) {
...
case 'z-index-aware':
return document.elementFromPoint(x1,y1) == droppable;
break;
(that would ensure only the element right below the upper-left corner of the draggable would be considered "good enough" as a drop target - not ideal, but better than what we have so far; a similar solution could be adapted to use the mouse pointer coordinates instead)
And, no, you can't use this method with the workaround presented before: at the moment the drop happens, the drag helper is the element closest to the screen... (Edit: d'oh! It wouldn't work if implemented as a tolerance mode either, for the same reason...)
Upvotes: 1
Reputation: 2037
If I got you right - this one should solve your problem - http://jsfiddle.net/Wp4LU/60/ Also you could write custom accept function - http://jqueryui.com/demos/droppable/#option-accept
Code:
var draggableList = $('#top');
$(".draggable").draggable({
helper: "clone"
});
$("#bottom, .draggable").droppable({
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
if ($this.hasClass("draggable")) {
if ($this.position().top >= draggableList.height() ||
$this.position().top + $this.outerHeight() >=
draggableList.height())
return;
}
$this.append($dragged.clone());
},
hoverClass: "dragHover"
});
Upvotes: 2