Is there a JQuery plugin which combines Draggable and Selectable
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.
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>
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.
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);
...