28

I'm looking to implement a web interface with a number of items which can be selected and dragged around to position them, either in groups or singly. Rather like the Windows Desktop, really.

We're using JQuery already, so additions to that would be first choice. JQuery UI Draggables and Selectables individually do much of what we want, but don't really work together to give the sort of effect we're looking for.

I am completely overwhelmed by the JQ plugin site (it's 'popular' algorithm doesn't seem very useful), and would welcome guidance as to the best way to avoid a lot of wheel-reinvention here, as I would guess that this metaphor has already been done.

Will Dean
  • 39,055
  • 11
  • 90
  • 118
  • Code in http://stackoverflow.com/questions/34698117/elements-became-randomly-non-resizable-after-dragging queston contains also answer to this question However code in answers cause random loss of resizable on drag – Andrus Jan 10 '16 at 08:53

4 Answers4

20

I also needed to do same thing, and i didn't want to use interface extension from eyecon.ro. After some research, I found Combining Selectables And Draggables Using jQuery UI. It is nicely told but to make the code snippets run you have to dig into it. I was able to make it work. I slightly changed it, this is my way to get it done. It needs modifications for using on production level, but i hope it helps.

// this creates the selected variable
// we are going to store the selected objects in here
var selected = $([]), offset = {top:0, left:0}; 

// initiate the selectable id to be recognized by UI
$("#selectable").selectable({
    filter: 'div',
});

// declare draggable UI and what we are going to be doing on start
$("#selectable div").draggable({
     start: function(ev, ui) {
        selected = $(".ui-selected").each(function() {
           var el = $(this);
            el.data("offset", el.offset());
        });

        if( !$(this).hasClass("ui-selected")) $(this).addClass("ui-selected");
        offset = $(this).offset();
    },
    drag: function(ev, ui) {
        var dt = ui.position.top - offset.top, dl = ui.position.left - offset.left;

        // take all the elements that are selected expect $("this"), which is the element being dragged and loop through each.
        selected.not(this).each(function() {
             // create the variable for we don't need to keep calling $("this")
             // el = current element we are on
             // off = what position was this element at when it was selected, before drag
             var el = $(this), off = el.data("offset");
             el.css({top: off.top + dt, left: off.left + dl});
        });
    }
});

CSS Styles to be able to see what's happening:

#selectable {   width: 100%; height: 100%;}
#selectable div {
    background: #ffc;
    line-height: 25px;
    height: 25px;
    width: 200px;
    border: 1px solid #fcc;
    }
#selectable div.ui-selected {
    background: #fcaf3e;
    }
#selectable div.ui-selecting {
    background: #8ae234;
    }

HTML Markup:

<div id="selectable">
    <div>item 1</div>
    <div>item 2</div>
    <div>item 3</div>
    <div>item 4</div>
</div>
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
Sinan
  • 11,443
  • 7
  • 37
  • 48
  • 2
    The main problem with this code it doesn't let you control click to add additional items. – Jeff Martin Jan 28 '10 at 23:12
  • 2
    This seems to only allow you to drag multiple selected items, I'm not able to select any selectable item with single click (let alone ctrl click) – digout Oct 03 '11 at 19:05
  • This code causes random loss of resizable on drag. This this is posted in http://stackoverflow.com/questions/34698117/elements-became-randomly-non-resizable-after-dragging How to fix ? – Andrus Jan 10 '16 at 08:52
10

This question is relevant, but is old; so are the answers. Here's an updated version of @idFlood's jsfiddle, that works with jQuery 1.9.1 + jQueryUI 1.10.3:

// store selected elements and the offset of the dragged element
var selected = $([]), offset = {top:0, left:0}; 

$( "#selectable > div" ).draggable({
    start: function (event, ui) {
        var $this = $(this);

        if ($this.hasClass("ui-selected")) {
            // if this is selected, attach current offset
            // of each selected element to that element
            selected = $(".ui-selected").each(function() {
                var el = $(this);
                el.data("offset", el.offset());
            });
        } else {
            // if this is not selected, clear current selection
            selected = $([]);
            $( "#selectable > div" ).removeClass("ui-selected");
        }
        offset = $this.offset();
    },

    drag: function (event, ui) {
        // drag all selected elements simultaneously
        var dt = ui.position.top - offset.top, dl = ui.position.left - offset.left;
        selected.not(this).each(function() {
            var $this = $(this);
            var elOffset = $this.data("offset");
            $this.css({top: elOffset.top + dt, left: elOffset.left + dl});
        });
    }
});

// enable marquee selecting and deselect on outside click...
$( "#selectable" ).selectable();

// ...but manually implement selection to prevent interference from draggable()
$( "#selectable" ).on("click", "div", function (e) {
    if (!e.metaKey && !e.shiftKey) {
        // deselect other elements if meta/shift not held down
        // $( "#dc-modules .dc-module" ).removeClass("ui-selected");
        $( "#selectable > div" ).removeClass("ui-selected");
        $(this).addClass("ui-selected");
    } else {
        if ($(this).hasClass("ui-selected")) {
            $(this).removeClass("ui-selected");
        } else {
            $(this).addClass("ui-selected");
        }
    }
});

I had a problem with the _mouseStop() call throwing an error, so I removed it; this means that the ui-selecting state no longer happens on click, but all other functionality remains intact.

ericsoco
  • 24,913
  • 29
  • 97
  • 127
  • This solution works very well but I'm having trouble getting it to work with droppable. Is there anything intrinsic to the solution that you think would conflict with droppable or should I keep banging my head against this code? – Daniel Bernhard Jun 17 '15 at 21:29
  • @ericsoco This code causes random loff of resizable on drag. This is posted in http://stackoverflow.com/questions/34698117/elements-became-randomly-non-resizable-after-dragging How to fix ? – Andrus Jan 10 '16 at 08:49
4

I've made some modification to the answer given by Sinan Yasar. It's not perfect but it already behave much more like I would except.

One main addition is a click listener that calls the select.

// manually trigger the "select" of clicked elements
$( "#selectable > div" ).click( function(e){
    if (e.metaKey == false) {
        // if command key is pressed don't deselect existing elements
        $( "#selectable > div" ).removeClass("ui-selected");
        $(this).addClass("ui-selecting");
    }
    else {
        if ($(this).hasClass("ui-selected")) {
            // remove selected class from element if already selected
            $(this).removeClass("ui-selected");
        }
        else {
            // add selecting class if not
            $(this).addClass("ui-selecting");
        }
    }

    $( "#selectable" ).data("selectable")._mouseStop(null);
});

You can see a complete working example here: http://jsfiddle.net/DXrNn/4/

There is also a jquery-ui plugin available that does just that: http://code.google.com/p/jqdragdropmultiselect/ The probleme is that it doesn't look maintained.

edit: if you define the "filter" option of the draggable, you will need to call selectable.refresh() before the selectable._mouseStop(null).

$( "#selectable > div" ).click( function(e){
  ...
  var selectable = $("#container").data("selectable");
  selectable.refresh();
  selectable._mouseStop(null);
  ...
idFlood
  • 1,016
  • 10
  • 20
  • 2
    No matter how much I try your solution in jsfiddle, I can't get any elements to drag, only select. Chrome 19. – trusktr Jul 26 '12 at 17:40
  • 1
    @trusktr Try i with `jQuery 1.8.3` and `jQueryUI 1.9.2` – mraaroncruz Feb 03 '13 at 10:52
  • 1
    @trusktr because of this http://stackoverflow.com/questions/14366322/error-jquery-ui-draggable-cannot-read-property-msie seems that you would need jQueryUI 1.10 to use jQuery EDGE – mraaroncruz Feb 03 '13 at 11:11
  • any idea how to make draggable helper work? I don't want to move the selected siblings directly through `css()` – Henry Jun 30 '14 at 23:36
  • @pferdefleisch This code causes random loss of resizable during drag as posted in http://stackoverflow.com/questions/34698117/elements-became-randomly-non-resizable-after-dragging . How to fix ? – Andrus Jan 10 '16 at 08:50
  • fiddle haz fail – Eskim0 Nov 28 '16 at 20:25
0

There is also another way, probably a more concise way that I have used. The child element is the selectee and the parent element will have the "ui-selectable" class.

$("#parentContainer").selectable({
  selected: function(event, ui) {
    let selected = ui.unselected.id;
   document.getElementById(selected).style.border = '1px solid blue'
  }
  unselected: function(event, ui) {
    let unselected = ui.unselected.id;
    document.getElementById(unselected).style.border = ''
    $('#'+unselected).draggable('destroy')
  }
})

document.getElementbyId('childElement').addEventListener('mousedown', function() {
  $(this).draggable({
    cursor: crosshair
  })
})
analytical_prat
  • 842
  • 12
  • 14