59

I have a design where I have a list of "available boxes", users take boxes by dragging them from the "available boxes" list to their "My Boxes" list.

Users more often than not take multiple boxes at a time (max 20), once they have finished with the boxes they drag them back to the "available boxes" list to return them.

jQuery sortable allows me to drag one box at a time which from a user perspective is undesirable. I've been unable to come up with a simple solution to the issue.

I may have to come up with a different UI method entirely, but first does anyone have any suggestions on how this might be accomplished?

Thanks!

danspants
  • 3,099
  • 3
  • 27
  • 33
  • 1
    [See my answer for a working solution](http://stackoverflow.com/a/15301704/873263). The accepted answer and the answer by @Shanimal are good starts, but they had some bugs that needed to be worked out. – Aaron Blenkush Mar 08 '13 at 19:34

5 Answers5

77

Working Solution

tl;dr: Refer to this Fiddle for a working answer.


I looked everywhere for a solution to the issue of dragging multiple selected items from a sortable into a connected sortable, and these answers were the best I could find.

However...

The accepted answer is buggy, and @Shanimal's answer is close, but not quite complete. I took @Shanimal's code and built on it.

I fixed:

I added:

  • Proper Ctrl + click (or Cmd + click if on a mac) support for selecting multiple items. Clicking without the Ctrl key held down will cause that item selected, and other items in the same list to be deselected. This is the same click behavior as the jQuery UI Selectable() widget, the difference being that Selectable() has a marquee on mousedrag.

Fiddle

HTML:

<ul>
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
</ul>
<ul>
    <li>Four</li>
    <li>Five</li>
    <li>Six</li>
</ul>

JavaScript (with jQuery and jQuery UI):

$("ul").on('click', 'li', function (e) {
    if (e.ctrlKey || e.metaKey) {
        $(this).toggleClass("selected");
    } else {
        $(this).addClass("selected").siblings().removeClass('selected');
    }
}).sortable({
    connectWith: "ul",
    delay: 150, //Needed to prevent accidental drag when trying to select
    revert: 0,
    helper: function (e, item) {
        var helper = $('<li/>');
        if (!item.hasClass('selected')) {
            item.addClass('selected').siblings().removeClass('selected');
        }
        var elements = item.parent().children('.selected').clone();
        item.data('multidrag', elements).siblings('.selected').remove();
        return helper.append(elements);
    },
    stop: function (e, info) {
        info.item.after(info.item.data('multidrag')).remove();
    }

});

NOTE:

Since I posted this, I implemented something simmilar - connecting draggable list items to a sortable, with multi-select capability. It is set up almost exactly the same, since jQuery UI widgets are so similar. One UI tip is to make sure you have the delay parameter set for the draggables or selectables, so you can click to select multiple items without initiating a drag. Then you construct a helper that looks like all the selected elements put together (make a new element, clone the selected items, and append them), but make sure to leave the original item intact (otherwise it screws up the functionality - I cannot say exactly why, but it involves a lot of frustrating DOM Exceptions).

I also added Shift + Click functionality, so that it functions more like native desktop applications. I might have to start a blog so I can expound on this in greater detail :-)

Community
  • 1
  • 1
Aaron Blenkush
  • 3,034
  • 2
  • 28
  • 54
  • It is better... but bug that Ryan mentioned is still reproducable... i dragged 2 elements out... then back in becuase I decided i didnt want to move them and unless I place them in the same postion as they were the DOM 3 exception occurs – Roopak Venkatakrishnan Mar 08 '13 at 19:35
  • 1
    @RoopakVenkatakrishnan: After a revamp it's fixed now. – Aaron Blenkush Mar 08 '13 at 22:53
  • Hi Aaron,Your solution works perfectly.But in some senarios i need to only allow user to select all the the list(all the
  • ) and put into other
      .
  • – Gautam May 16 '13 at 16:55
  • @Gautam: I'm not sure what you mean; you can Ctrl+click to select all the list items at the same time. I've since used this in an application and I added Ctrl + A for select all and Shift+click to select a range of items. The Shift+click is a bit trickier, but Ctrl+A works as long as you know which list has "focus". Just listen for Ctrl+A, then add the `selected` class to each item. – Aaron Blenkush May 16 '13 at 18:12
  • @aaron Awesome stuff; but I have a function that handles some sync on other DOM area after the drag completion i.e. thru stop event. And at that time I need the recently dragged item's objects. I haven't been able to get that dragged stack. Can u help me on this? – Mrigesh Raj Shrestha Jun 13 '13 at 08:10
  • @Mrigesh: Have you checked out the API documentation for jQuery UI for [Sortable](http://api.jqueryui.com/sortable/) and [Draggable](http://api.jqueryui.com/draggable/)? In particular, look at `sortable`'s event callbacks. I'd recommend creating a [Fiddle](http://jsfiddle.net/) and exploring the values of the `event` and `ui` objects by printing to the Console (`console.dir(event);`, for example). I rarely memorize this stuff - whenever I need to "remember" I just take a couple minutes to what I described above and see exactly what's happening, if the API docs don't tell me right away. – Aaron Blenkush Jun 13 '13 at 17:28
  • @AaronBlenkush that app segment where this scenario exists is real bizzare. Fiddling it is real hard stuff. Abt api, i checked all but couldnt get a collection of multiple items just dragged in. – Mrigesh Raj Shrestha Jun 17 '13 at 04:35
  • 2
    @Mrigesh, I looked at the Fiddle for this answer and remembered how I access the dragged elements in the `stop` callback. Rather than explain it here, I made an update to the Fiddle that has comments so you can see how to do it. (Hint: it uses the [`jQuery.data()`](http://api.jquery.com/data/) method to attach the elements to the dragged `item` that Sortable passes between its callbacks. You have to attach the selected items to `item` when the sort is initiated. I did it on the `helper` callback.) http://jsfiddle.net/hQnWG/614/ – Aaron Blenkush Jun 17 '13 at 16:29
  • 1
    @AaronBlenkush thanks for such an effort. Hats off for making it happen. I will try this solution you gave and let you know :) – Mrigesh Raj Shrestha Jun 18 '13 at 11:48
  • This solution leads me to a few errors (because some items are removed from the list I guess). I have found it was more safe to hide elements, and remove them in the stop function – slaur4 Sep 06 '13 at 11:24
  • @slaur4 That makes sense. It's all smoke and mirrors anyway; whatever achieves the end result of a (reliable) user experience! If you feel so inclined, post your improvement as separate answer for posterity :-) – Aaron Blenkush Sep 24 '13 at 21:20
  • If we have selected multiple items and then decide not drop them (reason could be incorrect selection),then one should be able to do press ecaspe during drag to revert items back to tehir original position. any ideas how to do it ? – django Oct 30 '13 at 17:58
  • @django It's outside of the scope of this question. You should research it and open a new question if you can't figure it out. Be sure to include what you have tried. http://forum.jquery.com/topic/how-to-cancel-drag-while-dragging – Aaron Blenkush Oct 30 '13 at 18:47
  • @AaronBlenkush your solution works perfect but i want one modification to have clone elements rather to remove them from source list. can u help me how i can achieve that ? your code remove elements from source list. – Salman Nov 12 '13 at 20:49
  • One problem with this is that if i simply drag non-consecutive items and leave them in same container , upon reverting back the order of items will be changed... – T J Jun 09 '14 at 10:05
  • @TJ: This is Stack Overflow, not open source code repo. The answer is meant to be a starting point, not a final solution ;-) – Aaron Blenkush Jun 09 '14 at 14:52
  • @AaronBlenkush well, so are the other answers.. you were saying accepted answer is buggy, other answers are not quite complete etc, hence i pointed out - this one too. – T J Jun 10 '14 at 05:43
  • Nice work, Aaron. I was wondering what a clean entry point may be, without having to muck with the internals too much, and it seems you've found it. I'm implementing your code for a connected **draggable**. As you may have encountered, `connectToSortable` takes the `draggable` `helper` and passes it along to the `sortable`. Therefore, the `elements` that are stored in the `helper` of the `draggable` are then lost by the time we get to the `sortable` `drop` function. How did you get around this? – seebiscuit Oct 06 '14 at 19:47
  • 1
    Definitely the best solution for users who have elements like – Reuben Ward Aug 05 '16 at 01:26