jQuery UI droppable - tolerance/greedy not working as expected - jquery-ui

When I drag the draggable, the element containing the droppables keeps getting triggered (because it's also a droppable), even though it's behind the droppables and they're stacked right on each other. It's behaving like there's a gap between the droppables, and there isn't one.
I've made an example on jsFiddle and here's the screenshot of the offending behavior.
If I add padding to .parent (for example padding: 0.2em 0.2em 0em 0.2em the behavior is even worse.

First of all, I hope you found out a solution in these months, I'm answering this because I'm working with jQueryUI in this period and I thought it would be a good exercise to try to find an answer. Also, I hate unanswered questions ^_^
Unfortunately, it really looks like the browser reacts as if between those .child elements there's some space, enough to trigger the *over events for the .parent. The only idea I came up with is to try to detect if, when the dropover events triggers on the parent, the mouse position is actually inside a child element. If so, you should give the accepting_drops class to the child element instead of the parent.
Here's the code (I made a jsFiddle to show my solution in action):
HTML and CSS are unaltered, so I won't copy them again
Javascript
$('.dragme').draggable({
helper: 'clone'
});
$('.parent,.child').droppable({
greedy: true,
tolerance: 'pointer',
});
$(".parent").on("dropover", function(event, ui) {
var first = $(".child:first");
var last = $(".child:last");
if((event.originalEvent.pageX > first.offset().left) &&
(event.originalEvent.pageY > first.offset().top) &&
(event.originalEvent.pageX <= (last.offset().left + last.width())) &&
(event.originalEvent.pageY <= (last.offset().top + last.height()))) {
$(this).removeClass("accepting_drops");
}
else {
$(this).addClass("accepting_drops");
$(".child").removeClass("accepting_drops");
}
}).on("dropout", function() {
$(this).removeClass("accepting_drops");
});
$(".child").on("dropover", function() {
$(".parent").removeClass("accepting_drops");
$(".child").removeClass("accepting_drops");
$(this).addClass("accepting_drops");
}).on("dropout", function() {
$(this).removeClass("accepting_drops");
});
I removed the hoverClass: 'accepting_drops' line because we're overriding the default behaviour of the draggable component. For the parent div, if when a dropover event triggers I'm also inside a child element, I remove the accepting_drops class from it, otherwise I remove it from any child who could have it and add it to the parent instead. When a dropout event triggers on it, I remove the accepting_drops class.
For the child, the behaviour is almost standard, on a dropover event I remove the accepting_drops class from everything else and add it to the child, on a dropout event I remove it.
The behaviour is still a bit of a mistery, but this workaround should do the trick.

Related

Draggable element hidden outside container

Using jQuery UI, I am trying to create an interface with two scrollable containers, each containing many draggable elements. The user can drag an element from one container to the other.
The dropping feature is not an issue. When dropped, the element is detached and recreated in the right place under its new parent.
My problem is that the draggable element cannot be displayed outside its container when the container has position:relative; applied, so while dragging, the element will disappear when it is moved outside the container boundaries.
This default behaviour makes sense, as normally the user would want to drag an element inside its container. As a workaround I had assumed the solution would be to use the draggable property 'appendTo', which I thought would move the element outside its container, but unfortunately this hasn't seemed to have had any effect.
DOM: (each view is scrollable and each container has position:relative and is as large as it needs to be to hold all elements)
BODY
VIEW 1
CONTAINER
DRAGGABLE ELEMENTS
VIEW 2
CONTAINER
DRAGGABLE ELEMENTS
Javascript:
$('div.card').draggable({
appendTo: 'body',
scroll: false //stops scrolling container when moved outside boundaries
});
Please see JSFiddle for a simplified explanation of the problem. I didn't want to bloat the example with droppable code, so this just illustrates the dragging issue. http://jsfiddle.net/Em7Ak/
Many thanks in advance.
I'm not sure if this will fix your exact problem, but I came across this question looking for the same answer and this is what I found.
In the options for .draggable(), pass in helper:'clone' to make a clone of the item automatically so that it can be dragged out of the container. And use appendTo:'body' to put it at the end of the <body> tag. So in my case, my options look somewhat like this, adding in revert:'invalid' to cause it to spring back if it isn't dropped somewhere valid:
jQuery(".myselector").draggable({
helper: 'clone',
revert: 'invalid',
appendTo: 'body'
});
use the "clone" helper and hide the item while dragging it and show it again on stop.
$(".removalbe-recom").draggable({
appendTo: "body",
helper: "clone",
revert: "invalid",
cursor: "move",
containment: "document",
zIndex: 10000,
scroll:false,
start: function (event, ui) {
$(this).hide();
},
stop: function (event, ui) {
$(this).show();
}
});
I had similar problem some months ago.
My need was to be able to use the auto scrolling of one big container from others
Here is my question for more details, JqueryUI, drag elements into cells of a scrolling dropable div containing large table
I found a workaround. The idea is to append the element clone to the scrollable container during the helper construction callback, then append the helper to the body using a setTimeout function after 1ms. The helper position must be mapped on the mouse position to avoid offset problem.
Here is my solution (JSFiddle seems to be down now, try it later if no code is displaying in the windows) : http://jsfiddle.net/QvRjL/134/
$('[id^="drag-"]').each(function() {
$(this).draggable({
opacity: 0.7,
cursorAt: { top: 15, left: 50 },
appendTo: 'body',
containment: 'body',
scroll: true,
helper: function(){
//Hack to append the cartridge to the body (visible above others divs),
//but still belonging to the scrollable container
$('#container').append('<div id="clone" class="cartridge">' + $(this).html() + '</div>');
$("#clone").hide();
setTimeout(function(){
$('#clone').appendTo('body');
$("#clone").show();
},1);
return $("#clone");
}
});
});​
Add position:absolute to card style:
div.card {
position:absolute;
width:100px; height:50px;
border:1px black solid;
background-color:orange;
text-align:center; vertical-align:middle;
}

jQuery UI: combining Sortable with Draggable while cloning the Sortable

I'm trying to build an interface tool which essentially allows users to build a grid out of common UI elements.
Here's a jsFiddle: http://jsfiddle.net/FX4Fw/
Essentially, the idea is that you drag content elements (picture, headline, standfirst, etc) into the grey placeholder at the bottom. Once they're in there, they should no longer be Draggables (because this breaks the CSS grid system they inherit) and they can then be resized. The original items in the UI should stay where they are, so the user is essentially cloning them into the box to be positioned.
This almost works in my demo, but when the user grabs a UI element and drags it into the placeholder, I then remove the ui-draggable class from the cloned element that ends up inside the placeholder. This also removes it from the original source element (I want this to stay where it is) so it's no longer usable.
Is there a way to combine these things so they work in tandem? Hopefully it's clear what I'm trying to do.
Never mind - found the answer here: https://stackoverflow.com/a/3041887/176615
(basically this code)
stop: function(event, ui) {
//check it wasn't here previously
if(!ui.item.data('tag') && !ui.item.data('handle')) {
ui.item.data('tag', true); //tag new draggable drops
ui.item.removeClass('ui-draggable'); // dirty hack
}
},

jQuery droppable - how to react on hover after a short period of time?

I am using jQuery draggable/droppable to drag (divs) from one list to another. Both lists have a fixed number of list items, and the list items themselves and can either be empty or already contain a div.
I am dragging a div to a list item. If the list item already contains a div then I would like that div to be pushed down to the list item below it - so as to make way for the div I am dragging. I can do this with the "over" event in droppable. But I would like this to happen only after the user has paused there for a cetain period of time, say 0.5 second. How can I do this?
Also, if the user decides not to drop then I would like the displaced item to come back to its original position. Again, what would be the easiest of accomplishing that?
Thanks for your help
William
I just happen to be doing something similar. In my case, I am using the treeTable plugin. I want to expand a collapsed tree if it is hovered for some amount of time. Here is what I have so far. I just happen to be working on this today. Note the use of a timeout to delay the operation.
var timeout = null;
var clear = function() {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
}
$(".selector").droppable({
over: function(e, ui) {
if (! $(this).is("expanded")) {
clear();
timeout = setTimeout(function() {
$(this).expand();
timeout = null;
}, 1000);
}
}
, out: clear
, drop: function(e, ui) {
clear();
...
}
});
I'm still having a problem with this. After one branch gets expanded, the rows below it get pushed down to make room for the expanded branch. I haven't dropped yet and so I go hover over the first new element in the expanded branch. The problem is that this element does not get the hoverClass, the first element that got pushed down below the new branch gets it. It is like jquery doesn't figure out that it got moved down. I imagine that you are likely to encounter this problem given what you've described.
I was on jquery-1.4.2 but then I updated to jquery-1.6.4 to see if that would fix the problem. Both versions exhibit the same behavior. Also, I tested this in Chrome 14.0.835.202, Opera 11.52, Firefox 3.6.23 and IE7.0.5730.13. It happens the same way in all of them. So, either jquery has a bug or I've done something to cause this.
About your second question. I'm not sure I completely understand what you're going after. Are you looking for the

How to decide whether to accept or reject a jQuery draggable into a droppable

I'm using jQuery and I have the following problem:
In my site I have a chessboard with pieces. Every square is a simple div with the background property to show white or black. Over these squares (inside the divs) I've put an img tag referencing the piece that must be over that square. Something like:
<div id="a8" class="square" style="background-image: url('/images/background_white.png')">
<img id="piece_a8" class="piece" src="/images/rook_black.png" />
</div>
I can control the movement of the pieces using jQuery. Every piece-class img is a draggable and every square-class div is a droppable. I already have a server-side function that, given a set of coordinates, returns "VALID" if the movement is valid, and "INVALID" if otherwise. My idea is, if the server returns "INVALID", the piece must return to its origin square, and if the server returns "VALID", the piece must stay in its new square, deleting every other piece inside the target square.
My problem is, I don't know how can I enforce this return value in my jQuery code. I've tried putting functions in the revert property of the draggable, and in the accept and drop functions of the droppable, but I haven't found how to make $.get return false or true.
Any help would be really appreciated.
Thanks in advance,
Léster
Nevermind, answered.
In case someone needs to know, the trick is in 2 parts:
First: In the draggable definition, under the start event, add a function that saves the original position. Like this:
$('item_to_drag').draggable({
start: function(){
$(this).data("origPosition",$(this).position());
}
});
Second: In the droppable definition, under the drop event, do your .get and use a function to process the answer; in case your conditions are not met, animate the draggable back to its original position. Like this:
drop: function (event, ui) {
$.get(url,function(data) {
if (data == '"INVALIDO"')
{
ui.draggable.animate(ui.draggable.data("origPosition"),"slow");
}
else
{
//store new positions, whatever;
}
}
);
}
That'll do the trick.
Part of the answer came from here: In jQuery, how to revert a draggable on ajax call failure? .

Make jQuery sortable items droppable to their peers

I have a sortable list (of tasks). It helps prioritize tasks, so I want to keep this functionality. Now I want to add subtasks to the functionality: I want to enable users to drag one task over to another task and drop it there to turn it into a subtask.
Applying .draggable() and .droppable() to items that are already sortable has no effect. What could I do?
I put together a demo of how to do this... but it may not be the best method. Here are some problems I've discovered:
Because this code uses the placeholder to figure out where you are moving the list, you can only drop an item inside another item if you approach it from the top. I did get a working version where you could drop an item anywhere inside the base item, but the code was just too messy and cumbersome.
Sometimes when an item from the other list is dropped in an item, it becomes stuck. I'm not sure why, but it becomes unstuck when the list group is moved to the other list.
I'm sure there is a better method, one that calculates the intersection of list items (just like the sortable script does). But this is a quick and dirty method.
$(function() {
var phTop, container, indx;
$("#sortable1, #sortable2").sortable({
connectWith: '.connectedSortable',
beforeStop: function(e,ui){
phTop = ui.placeholder.position().top;
container = ui.placeholder.parent();
indx = ui.placeholder.index();
},
stop: function(e,ui){
var list = container.find('> li').eq(indx);
// 15 is a pixel tolerance between the two items (dragging in from the top)
if ( Math.abs( phTop - ui.position.top ) < 15 ) {
// prevent list depth > 1
if (ui.item.find('li').length > 0) { return; }
// add ul inside of li to make subgroup
if (!list.find('ul').length) { list.append('<ul></ul>'); }
ui.item.appendTo( list.find('> ul') );
}
container.find('li > ul:empty').remove(); // remove empty subgroups
}
}).disableSelection();
});
After I posted my question I had no patience, and I decided to ignore UI.sortable altogether, building the required functionality from draggable and droppable and using special divs as spacers that would swell up on dragover to facilitate dropping in between tasks.
That worked to some degree, except it's all much more code and it's a lot more jittery and bugprone than sortable, even with the refreshPositions option set to true. Still, there might be other valid reasons to want to circumvent UI.sortable.
In very brief faux code: $(.taskitem).draggable
revert: invalid
start: animate height of spacers from 0 to 5
$(.spacer).droppable
over: animate height from 5 to 50
out: animate height back to 5
drop: insert draggable after spacer
find spacer with same index as draggable and move it along

Resources