RESOLVED
So I studied sortable()
with the connectWith
attribute a bit more and found the solution was simpler than I expected, but I was thinking about it sort of backwards because of draggable()
and droppable()
.
Here's the fiddle: http://jsfiddle.net/DKBU9/12/
Original question follows:
This is an extension of a previous SO question that I felt should be kept separate.
The original question is here: jQuery UI - Clone droppable/sortable list after drop event. It regards reinitializing the droppable handler on cloned elements.
On top of that though, I'm trying to allow the elements that have been dragged from the initial list to be sorted within the droppable lists.
Here is a fiddle essentially illustrating the current behaviour as a result of the original question: http://jsfiddle.net/DKBU9/7/
And the required code because SO likes to complain and bloat these posts:
HTML
<ul id="draggables">
<li>foo1</li>
<li>foo2</li>
<li>foo3</li>
</ul>
<ul class="droppable new">
</ul>
JS
$(function() {
$('#draggables > li').draggable({
appendTo: 'document',
revert: 'invalid'
});
$('.droppable > li').draggable({
appendTo: 'document',
revert: 'invalid'
});
$('#draggables').droppable({
accept: '.droppable > li',
drop: function(event, ui) {
ui.draggable.detach().css({top: 0,left: 0}).appendTo($(this));
cleanUp();
}
});
initDrop($('.droppable'));
});
function initDrop($element) {
$element.droppable({
accept: function(event, ui) {
return true;
},
drop: function(event, ui) {
if($(this).hasClass('new')) {
var clone = $(this).clone();
$(this).after(clone);
$(this).removeClass('new');
initDrop( clone );
}
ui.draggable.detach().css({top: 0,left: 0}).appendTo($(this));
cleanUp();
}
}).sortable({
items: 'li',
revert: false,
update: function() {
cleanUp();
}
});
}
function cleanUp() {
$('.droppable').not('.new').each(function() {
if($(this).find('li').length == 0) {
$(this).remove();
}
});
}
Reference question: Jquery UI combine sortable and draggable
I've been trying to use this SO question to resolve my issue as the result is exactly what I'm trying to achieve, specifically the last fiddle provided in the comments of the accepted answer, but I can't seem to figure out how to adapt it to my code considering the minor differences. For example, the fiddle in that question clones the draggable rather than dragging the actual element and unlike mine doesn't allow elements to be dragged back into the initial list.
So any help to try to get that result for my code would be greatly appreciated.
Also, in that example, the accept
attribute is set as a function to return true. What is the reason for this? Doesn't that just mean it will accept anything, or any draggable element?
EDIT: I read a couple answers that just used sortable() with the connectWith attribute, but for some reason I didn't think it did what I needed to, partly because I didn't want the initial list to be sortable as well. However, using sortable() alone seems to get the functionality I'm after but I just haven't yet adapted all the event handlers.
See this fiddle.
It will make the items droppable and sortable in the new lists, but not in the initial (as per your edit).
There are a few tricks:
1) Starting with the draggable, I added a new function because we need to leave a placeholder (use a clone) so that the containers don't jump about during drag and we need to initialise the new element as draggable.
function initDraggable($element)
{
$($element).draggable({
connectToSortable: '.droppable',
revert: function(droppableObj)
{
if(droppableObj === false)
{
$(this).removeClass('dragging');
return true;
}
else
{
return false;
}
},
helper: 'clone',
start: function(e, ui)
{
$draggingParent = $(this).parent();
$(this).addClass('dragging');
}
});
}
Note the connectToSortable:
with .droppable
(As a cleanup, I think you should change droppable to sortable);
Note also revert:
- this gives us an opportunity to remove the 'dragging' class (which makes the original (cloned) element invisible) on revert.
Finally, there is start
which adds the 'dragging' class which makes the original (cloned) element invisible during drag. It also sets the $draggingParent which is used to check whether the item is being dragged from #draggables
or .droppables
.
2) Then there is the droppable()
for the #draggables
:
$("#draggables").droppable({
drop: function(ev, ui) {
if($draggingParent[0] === $(ui.helper).parent()[0])
{
//dragged from draggables to draggables
$(ui.draggable).removeClass('dragging');
}
else
{
if(ui.draggable.parent())
var $clone = $(ui.draggable).clone();
$(ui.draggable).remove();
$clone.removeClass();
$clone.removeAttr('style');
initDraggable($clone);
$(this).append($clone);
}
}
Note that the drop
handler checks whether this element is dropped from #draggables
or .droppables
and acts accordingly.
3) Finally, as you already mentioned, we really want the droppable to be sortable:
function initDroppableSort($element) {
$element.sortable({
items: 'li',
connectWith: ".droppable,#draggables",
revert: true,
stop: function(event, ui) {
$(ui.item).removeClass('dragging');
$('.dragging').remove();
if($(this).hasClass('new')) {
var clone = $(this).clone();
clone.empty();
$(this).after(clone);
$(this).removeClass('new');
initDroppableSort( clone );
}
cleanUp();
}
});
}
Note the connectWith
and specifically that it refers to both .droppable
and #draggables
.