views:

267

answers:

2

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?

+2  A: 

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();
});
fudgey
Hi Fudgey, I'm almost embarrassed that you put in so much effort on my question. Thanks! Your code is extremely elegant.I think I also have a solution for the problem that drops into a task are only accepted when dragging in from above. This is the slightly changed fiddle code. http://jsfiddle.net/Jyjtp/
Thinking and tinkering a little longer, I see two problems with sortable even with your workaround:1. there is no 'over' event allowing tasks to signal that they are droppable, 2. (more seriously) subtasks can only be created after the sortable is initialized, i.e. they must be top-level tasks before they can be dragged onto another task.
Hi, there is an 'over' event associated with the sortable script, but unfortunately it triggers when you are over the entire group (the `ul`) and not individual elements. As for your second problem, I'm not sure how to solve that one for you. What exactly are you trying to accomplish?
fudgey
I've fixed the 'over' issue by adding a class to list items with a particular index based on the relative positions of placeholder and helper, using your code as the base. If a task has that 'hoverTarget' class when sorting stops, my code makes sure the dragged task is dropped on the target.The second problem turns out ot be not a problem at all, it actually makes the user experience much more clear when they can only either inspect a task's subtasks, or turn main tasks into subtasks, but not both in the same view. Thanks again!
+1  A: 

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