Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make draggable element sortable within droppable using jQuery UI

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.

like image 470
Bobe Avatar asked Jan 08 '14 02:01

Bobe


People also ask

How does jQuery ui draggable work?

jQueryUI provides draggable() method to make any DOM element draggable. Once the element is draggable, you can move that element by clicking on it with the mouse and dragging it anywhere within the viewport.

How do you make a draggable element?

Making any HTML5 elements including images draggable is quite easy. We take the help of the 'draggable' attribute. It takes true, false, or auto as arguments. The default value is auto.

Why is draggable not working?

You have one of these problems: Your jQuery or jQuery UI Javascript path files are wrong. Your jQuery UI does not include draggable. Your jQuery or jQuery UI Javascript files are corrupted.


1 Answers

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.

like image 137
acarlon Avatar answered Oct 19 '22 17:10

acarlon