I am trying to create multiple jquery droppables next to eachother where some parts might overlap, in these cases I want the one that is on top (z-index wise) to be greedy.
I have tried setting the greedy: true option in the droppable, but this does not seem to help. I have also tried to return false on the drop event and used event.stopPropagation();.
Here is a jsfiddle based on the demo page of jquery.
Is there any way to stop the drop event from propagating if there is another droppable triggering it, preferably the one that has the highest z-index?
Use document.elementFromPoint to check the element directly under the cursor. If it’s not your droppable, don’t do anything.
Add the following to the top of your drop: handler:
var droppable = $(this);
ui.helper.css({pointerEvents: 'none'});
var onto = document.elementFromPoint(event.clientX, event.clientY);
ui.helper.css({pointerEvents: ''});
if(!droppable.is(onto) && droppable.has(onto).length === 0) {
return;
}
Disabling pointer events on the helper is necessary for document.elementFromPoint to ignore the thing your dragging and only checking underneath. I’ve updated your jsFiddle for a quick demonstration. Note that the highlight still affects both elements. You should be able to change that by not letting jQuery UI do the highlighting but instead write it yourself on the draggables drag: event. I have, however, found this to be unusable in practice as this check (and disabling pointer events) is quite slow and may also cause flickering.
You need a function to check if the element is the highest visible element. Something is the highest visible when
The item is later in the dom list
It has a higher z-index set
The below function can be used to see if the current element (either in over method or drop method in the droppable setting) is in fact the highest visible element.
function isHighestVisible(cEl) {
cEl = $(this); //use this if you want to use it in the over method for draggable, else just pass it to the method
//get all active droppable elements, based on the classes you provided
var $list = $('.ui-widget-header.ui-state-active');
var listCount = $list.length;
var HighestEl = null;
var HighestZI = -1;
//one element is always highest.
if (listCount<=1) {
console.log(cEl.attr('id'));
return true;
}
//check each element
$list.each(function(i) {
var $el = $(this);
var id = $el.attr('id');
//try to parse the z-index. If its 'auto', return 0.
var zi = isNaN(parseInt($el.css('z-index'))) ? 0 : parseInt($el.css('z-index'));
if (zi>0) {
//z-index is set, use it.
//I add the listCount to prevent errors when a low z-index is used (eg z-index=1) and there are multiple elements.
//Adding the listCount assures that elements with a z-index are always later in the list then elements without it.
zi = zi + listCount;
} else {
//z-index is not set, check for z-index on parents
$el.parents().each(function() {
$par = $(this);
var ziParent = isNaN(parseInt($par.css('z-index'))) ? 0 : parseInt($par.css('z-index'));
if (ziParent>0) {
zi = ziParent;
return false;
}
});
}
//add the current index of the element to the zi
zi += i;
//if the calculated z-index is highest, change the highest element.
if (HighestEl==null || HighestZI<zi) {
HighestEl=$el;
HighestZI = zi;
}
});
//if the highest found active element, return true;
if (HighestEl.attr('id')==cEl.attr('id')) {
console.log(cEl.attr('id'));
return true;
}
//if all else fails, return false
return false;
}
If i had this issue i would use jQuery's .addclass
.droppablewidgetforce {
z-index: 1000 !important;
}
make a class as such so the layer stays on top no matter what.. this should fix the issue.
The greedy options is just for nested elements that have a droppable parent.
In your code the 2 droppable elements are siblings so the greedy option will not work:
<div id="droppable3" class="ui-widget-header">
<p>Outer droppable</p>
</div>
<div id="droppable3-inner" class="ui-widget-header">
<p>Inner droppable (greedy)</p>
</div>
Here is a messy workaround if you still want to use siblings instead of parents and children.
Live code example.
function normalDraggedOver() {
$( this ).addClass( "ui-state-highlight" )
.find( "> p" )
.html( "Droppeeeed!" );
}
var options = {
activeClass: "ui-state-hover",
hoverClass: "ui-state-active",
greedy: true,
drop: normalDraggedOver
};
var options2 = {
activeClass: "ui-state-hover",
hoverClass: "ui-state-active",
disabledClass: "ui-state-disabled",
hoverStateClass: "ui-state-hover",
greedy: true,
greedyWithSibligs: true,
drop: normalDraggedOver,
over: function () {
/* if we have previous siblings of the inner element we handle them as parents */
if(options2.greedyWithSibligs && $(this).prev().length > 0) {
$(this).siblings().slice(0, $(this).index())
.removeClass(options2.hoverClass)
.droppable('disable')
.removeClass(options2.disabledClass)
.removeClass(options2.hoverStateClass);
}
},
out: function () {
/* all the siblings of the inner element that were handled as parents act as one*/
if(options2.greedyWithSibligs && $(this).prev().length > 0) {
$(this).siblings().slice(0, $(this).index())
.droppable('enable')
.addClass(options2.hoverClass);
console.log($(this).attr('id'));
}
}
};
If just want it to look the same you can set the #droppable3 to position: relative and set the child #droppable3-inner to position: absolute.
HTML:
<div id="droppable3" class="ui-widget-header">
<p>Outer droppable</p>
</div>
<div id="droppable3-inner" class="ui-widget-header">
<p>Inner droppable (greedy)</p>
</div>
Here is the live example
Related
Trying to use jqueryUI. I have a list with components that i want to drop inside a gridsystem. Like a desiger thing.
I have a div that has
$( "#Component1" ).draggable();
And I drop the div above in this div:
$( "#GridDiv" ).droppable({
accept: "#component1"
});
The result I want is, when I drop the component1 div insde the GridDiv
<div id="#GridDiv">
<div id="#Component1"></div>
</div>
Is there a method for this ? i cant find it..
You have to move the element by yourself after drop event:
$( "#GridDiv" ).droppable({
accept: "#Component1",
drop: function( event, ui ) {
var droppable = $(this);
var draggable = ui.draggable;
// Move draggable into droppable
draggable.appendTo(droppable);
}
});
PS: You don't need to use # in the ID field:
<div id="GridDiv">
<div id="Component1"></div>
</div>
Here is a working example
As there is a issue with the styling of the dragged element while appending to another div (see here for further information), here is another approach for you:
$('#GridDiv').droppable({
accept: "#Component1",
drop: function (event, ui) {
var draggable = ui.draggable;
var offset = draggable.offset();
draggable.appendTo( this ).offset( offset );
}
});
Demo
The following demo is slightly different: it removes the original dragged element, so the dragging functionality gets lost:
$('#GridDiv').droppable({
accept: "#Component1",
drop: function (event, ui) {
var draggable = ui.draggable;
var offset = draggable.offset();
draggable.remove().appendTo( this ).offset( offset );
}
});
Demo 2
For resizing the draggable after it is dropped you can use:
draggable.resizable();
Demo 3
Reference
.offset()
.appendTo()
.droppable() - drop-option
.remove()
The above all are not a correct and exact answer because the all the outputs are placed top of the window, eg, the object placed in top:0px and left:0px position.
Try below
var droppable = $(this);
var draggable = ui.draggable;
ui.draggable.position({ of: $(this), my: 'left top', at: 'left top' });
I have three list item, each one with specific data attribute and different color. I have to drag and drop each one of them in box which has the same id and border color as dragging item.
Here is example
http://jsbin.com/xobekewo/3/edit.
Can someone help me with that?
You were on the right track. In accept function you can access the droppable element via this and the draggable element is passed in as the first argument. You can compare those 2 and return wether this droppable should accept the draggable in question. For example:
accept: function Accept(el) {
if (el.hasClass("draggable") && (el.attr("data"))) {
// accept this draggable element if the data attribute
// matches the id of the droppable element
return $(this).attr('id') === el.attr("data");
}
}
Or a full code snippet in your case
$(function() {
$(".draggable").draggable({
revert: "invalid"
});
$(".divel").droppable({
activeClass: "ui-state-default",
hoverClass: "ui-state-hover",
accept: function Accept(el) {
if (el.hasClass("draggable") && (el.attr("data"))) {
return $(this).attr('id') === el.attr("data");
}
}
});
});
Here's your original fiddle modified
http://jsbin.com/xobekewo/10/edit
Some other remarks
You should be using data atrributes instead of just the data attribute, for example instead of <div data="div1"> you should do something like <div data-droppable-id="div1"> and then access it via $("div").data("droppable-id").
I've added CSS .draggable {display: inline-block} - otherwise they have the width of the entire page and interfere with each other when being dragged/dropped, because they overlay each other.
I am currently trying to change the containment of jquery page items as a page loads. The current set up is that each of the items is associated with a class which then sets up the draggable properties of all the items.
Given particular actions, I need to be able to change the containment of particular bars to allow them to move inside different bounds than those initially set.
I've attached a jsFiddle (http://jsfiddle.net/cyVYq/28/) demonstrating what I am currently doing which for some reason will not break the original containment that has been set. Any help would be appreciated.
//find the elements that have changed on pback
var newlyConstrainedItems = $("[id*=container]");
$.each(newlyConstrainedItems, function (key, value) {
var barID = this.id.split("_");
$(barID[1]).draggable({
containment: $("#" + this.id)
});
});
Thanks
You forgot the hash symbol in your id selector:
var newlyConstrainedItems = $("[id*=container]");
$.each(newlyConstrainedItems, function (key, value) {
var barID = this.id.split("_");
$('#'+barID[1]).draggable({
containment: $("#" + this.id)
});
});
So I was working on this application where I want people to be able to drag items from one table data to the other table data, which must be contained within the parent table row.
But whenever I drag it around it seems to stick to the containment excluding the height of any placeholders.
Try it yourself: http://jsfiddle.net/2wy8R/
Any idea of how I can make it select the parent of the parent? Of not, then, how can I make the placeholders count?
Greetings
.
Update: YouTube video of the problem http://youtu.be/PMXcQvJmRGw
OK, here you go. Overridden the default containment as it seems buggy with your scenario. Let me know if this is not a good idea but it seems to work:
http://jsfiddle.net/2wy8R/6/
$('#first, #second').sortable({
connectWith: '.sortable',
placeholder: 'placeholder',
start: function(event, ui) {
ui.placeholder.height(ui.item.height() - 4);
var p = $(ui.helper);
var tr = p.closest("tr");
$(document).mousemove(function(e) {
var pOffset = p.offset();
var trOffset = tr.offset();
if (pOffset.left < trOffset.left) {
p.css({left: trOffset.left});
}
if (pOffset.left + p.width() > trOffset.left + tr.width()) {
p.css({left: trOffset.left + tr.width() - p.width()});
}
if (pOffset.top < trOffset.top) {
p.css({top: trOffset.top});
}
if (pOffset.top + p.height() > trOffset.top + tr.height()) {
p.css({top: trOffset.top + tr.height() - p.height()});
}
});
}
}).disableSelection();
just be careful with it though, as this keeps adding mousemove events to the document. you may want to unbind mousemove before binding it...
I have a list of items each with heavy content. When I apply jQuery sortable, what I wanted to achieve is this, when an item is dragged away and starts sorting, all the item contents will be hidden automatically.
From all my testtings, jQuery doesn't accommodate such changes very well, for example, if I do the following,
$("#sortable").sortable({
start: function(event, ui) {
$(".hidden").addClass("hide");
}
});
jQuery can't automatically figure out the sizes of hidden items and the sorting will be a mess.
So I solved it using an indirection, I firstly check mousedown, mouseup and mousemove event, and once dragging is detected (say when mousedown/mousemove for more than 50ms), I'll hide the items immediately.
And in jQuery, I used some delay to start sorting.
var mouse_down = false;
var mouse_down_time;
var content_hidden;
$(".drag-handle").bind("mousedown", function(e) {
mouse_down = true;
mouse_down_time = e.timeStamp;
$(".drag-handle").bind("mousemove", function(e) {
if (mouse_down && !content_hidden) {
if (e.timeStamp - mouse_down_time > 10) {
$(".hidden").addClass("hide");
content_hidden = true;
}
}
});
}).bind("mouseup", function(e){
$(".drag-handle").unbind("mousemove");
mouse_down = false;
if (content_hidden) {
$(".hidden").removeClass("hide");
content_hidden = false;
}
});
$("#sortable").sortable({
delay:100,
stop: function(event, ui) {
if (content_hidden) {
$(".hidden").removeClass("hide");
$(".drag-handle").unbind("mousemove");
content_hidden = false;
}
}
});
So jQuery will start sorting after item contents are hidden.
Everything works fine except the helper offset. The helper offset seems to bump up for exactly the amount of spaces occupied by the all the item contents above the dragged item (which are hidden now during dragging).
So my question is,
is there a way to dynamically calculate all the item heights before and after hidden, and set the helper offset accordingly?
Thanks.
Well, problem solved.
What I did is dynamically calculate all the items height above the dragged item (before and after hiding contents), and then set the helper offset to the corresponding value by setting css:({'top-margin':offset_value+'px'})