7

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;
}

Naftali
  • 144,921
  • 39
  • 244
  • 303
  • Since it didn't solve the issue in the live server, here's a [fiddle](http://jsfiddle.net/ult_combo/Wp4LU/18/) using the disabled property to mimic an `event.stopImmediatePropagation` to other UI widgets, hopefully it can be improved if no better way is found. – Fabrício Matté Aug 23 '12 at 16:21
  • @Neal Dont you think we can use some jquery propert like $this.is(":visible"). I dont know much about jquery but you can say that this is an attempt by a novice – Moons Aug 30 '12 at 07:33

4 Answers4

2

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"
})​;​
Naftali
  • 144,921
  • 39
  • 244
  • 303
krasu
  • 2,037
  • 23
  • 22
  • This works nicely in the fiddle... but it does not seem to be working in my real code (which looks slightly different).... – Naftali Aug 27 '12 at 13:00
2

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"
})​;​
xdazz
  • 158,678
  • 38
  • 247
  • 274
  • Holy awesomeness ^_^ this seems to be working :-). I gotta test it a little more :-D. Thank you so much! – Naftali Aug 30 '12 at 12:51
1

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...)

Community
  • 1
  • 1
mgibsonbr
  • 21,755
  • 7
  • 70
  • 112
0

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.

TecHunter
  • 6,091
  • 2
  • 30
  • 47
  • @Neal it seems that I didn't get the true issue here then. looks like you got your answer from another post. good luck – TecHunter Aug 30 '12 at 13:04