I have two jquery.ui draggables. I am constraining their movement to the y-axis. If one is dragged to a certain y-position, I want the other to automatically move to the same y-position and vice versa. Has anyone ever linked two of these together before?
Updated: Script and demo updated so it is no longer restricted to the y-axis while dragging.
This script looks for the class "group" followed by a number to drag/drop these combined objects. I posted a demo here.
HTML
<div class="demo">
<div id="draggable">
<p>Drag from here</p>
<div class="dragme group1"><img src="image1.jpg"><br>Group 1</div>
<div class="dragme group1"><img src="image2.jpg"><br>Group 1</div>
<div class="dragme group2"><img src="image3.jpg"><br>Group 2</div>
<div class="dragme group2"><img src="image4.jpg"><br>Group 2</div>
</div>
<div id="droppable">
<p>Drop here</p>
</div>
</div>
Script
$(document).ready(function() {
// function to get matching groups (change '.group' and /group.../ inside the match to whatever class you want to use
var getAll = function(t) {
return $('.group' + t.helper.attr('class').match(/group([0-9]+)/)[1]).not(t);
};
// add drag functionality
$(".dragme").draggable({
revert: true,
revertDuration: 10,
// grouped items animate separately, so leave this number low
containment: '.demo',
stop: function(e, ui) {
getAll(ui).css({
'top': ui.helper.css('top'),
'left': 0
});
},
drag: function(e, ui) {
getAll(ui).css({
'top': ui.helper.css('top'),
'left': ui.helper.css('left')
});
}
});
$("#droppable").droppable({
drop: function(e, ui) {
ui.draggable.appendTo($(this));
getAll(ui).appendTo($(this));
}
});
});
I haven't done this before, but I suggest using the drag event to adjust the position of the respective other element:
$('.selector').draggable({
...,
drag: function(event, ui) {
},
...
});
The ui object will contain information (in the property ui.offset) you can use to manually reposition the other element.
https://forum.jquery.com/topic/dragging-a-group-of-items-alsodrag-like-alsoresize
I found an extension for UI draggable(). It works in the same way as 'alsoResize' for UI resizable(), i.e.
$('.selector').draggable({ alsoDrag: '.selected' });
Add the plugin code after main jquery-ui library.
$.ui.plugin.add( 'draggable', 'alsoDrag', {
start: function() {
var that = $(this).data("ui-draggable"),
o = that.options,
_store = function (exp) {
$(exp).each(function() {
var el = $(this);
el.data("ui-draggable-alsoDrag", {
top: parseInt(el.css("top"), 10),
left: parseInt(el.css("left"), 10)
});
});
};
if (typeof(o.alsoDrag) === "object" && !o.alsoDrag.parentNode) {
if (o.alsoDrag.length) { o.alsoDrag = o.alsoDrag[0]; _store(o.alsoDrag); }
else { $.each(o.alsoDrag, function (exp) { _store(exp); }); }
}else{
_store(o.alsoDrag);
}
},
drag: function () {
var that = $(this).data("ui-draggable"),
o = that.options,
os = that.originalSize,
op = that.originalPosition,
delta = {
top: (that.position.top - op.top) || 0,
left: (that.position.left - op.left) || 0
},
_alsoDrag = function (exp, c) {
$(exp).each(function() {
var el = $(this), start = $(this).data("ui-draggable-alsoDrag"), style = {},
css = ["top", "left"];
$.each(css, function (i, prop) {
var sum = (start[prop]||0) + (delta[prop]||0);
style[prop] = sum || null;
});
el.css(style);
});
};
if (typeof(o.alsoDrag) === "object" && !o.alsoDrag.nodeType) {
$.each(o.alsoDrag, function (exp, c) { _alsoDrag(exp, c); });
}else{
_alsoDrag(o.alsoDrag);
}
},
stop: function() {
$(this).removeData("draggable-alsoDrag");
}
});
You would have to explicitly set the Y-Axis of both the elements.
For this either in the event handler functions of both the elements you will have to set Y-Axis or bind both the elements with same function.
Happy coding.
Here is a simplified version of the code mentioned in the marked answer(for dummies like me):
In HTML
<div id="div1" class="group">...</div>
<div id="div2"class="group">...</div>
In Script tag
$(document).ready(function(){
//to get the group
var getGroup = function() {
return $(".group");
}; // add drag functionality
$(".group").draggable({
revertDuration: 10,
// grouped items animate separately, so leave this number low
containment: "window",
scroll: false,
stop: function(e, ui) {
getGroup(ui).css({
'top': ui.helper.css('top'),
});
},
drag: function(e, ui) {
getGroup(ui).css({
'top': ui.helper.css('top'),
'left': ui.helper.css('left')
});
}
});
});
Related
I'm trying to make an exam application which has drag & drop question type that the user might dragging correct elements to correct droppable boxes. Totally, I need get dropped elements/items id's as array or another and insert to mysql with php. I did something are below:
Javascript
$(".dragging-items .draggable-item").draggable({
helper: "clone",
opacity: 0.5,
scroll:true,
refreshPositions: true,
start: function( event, ui ) {
(this).addClass("dragging");
},
stop: function( event, ui ) {
$(this).removeClass("dragging");
},
});
$(".dragging-items").droppable({
hoverClass: "drop-hover",
drop:function(event, ui) {
ui.draggable.detach().appendTo($(this));
}
});
$(".droppable-list").droppable({
drop:function(event, ui) {
ui.draggable.detach().appendTo($(this));
var drag_id = $(ui.draggable).attr("id");
var drop_id = event.target.id;
var $tbox = $("input[name=droppable-list-"+ drop_id +"]");
var current_data = $tbox.val();
var new_data = current_data + drag_id + ", ";
$tbox.val(new_data);
},
});
HTML
<ul class="dragging-items">
<li class="draggable-item" id="1"> Item-1 </li>
<li class="draggable-item" id="2"> Item-2 </li>
<li class="draggable-item" id="3"> Item-</li>
</ul>
<div class="drag-drop">
<div class="droppable-title"> Box 1 </div>
<div class="droppable-item">
<div class="droppable-list" id="1"></div>
</div>
<input type="text" class="droppable-input" name="droppable-list-1">
</div>
<div class="droppable-title"> Box 2 </div>
<div class="droppable-item">
<div class="droppable-list" id="2"></div>
</div>
<input type="text" class="droppable-input" name="droppable-list-2">
</div>
</div>
JsFiddle
https://jsfiddle.net/7e568v2y/
I found a number of oddities in CSS that may want to be corrected. A lot of missed ;, but that's not the main focus. Also, you have a number of repeated ID attributes and these must be unique in HTML. Consider the following code.
$(function() {
function dropToArray(dropObj) {
var results = [];
$(".draggable-item", dropObj).each(function(i, el) {
results.push($(el).data("drag-id"));
});
return results;
}
function updateDropList(dropObj) {
var dropped = dropToArray(dropObj);
var $tbox = $("input[name=droppable-list-" + dropObj.attr("id") + "]");
$tbox.val(dropped.join(","));
}
$(".dragging-items .draggable-item").draggable({
helper: "clone",
opacity: 0.5,
scroll: true,
refreshPositions: true,
start: function(event, ui) {
$(this).addClass("dragging");
},
stop: function(event, ui) {
$(this).removeClass("dragging");
},
});
$(".dragging-items").droppable({
hoverClass: "drop-hover",
drop: function(event, ui) {
ui.draggable.detach().appendTo($(this));
}
});
$(".droppable-list").droppable({
drop: function(event, ui) {
var $this = $(this);
if ($this.find(".droppable-list").length >= 2) {
ui.draggable.draggable("option", "revert", true);
return;
} else {
ui.draggable.detach().appendTo($(this));
setInterval(function() {
$(".droppable-list").each(function(i, el) {
updateDropList($(el));
});
}, 100);
}
},
});
});
Always best to create a few simple functions if you're going to be doing something often. I created two, one to iterate the container and build an array of the specific ID. To help ensure the same data comes back, I added the data-drag-id attribute. Now we can have unique IDs and still retain the ID Number for each item.
Working example: https://jsfiddle.net/Twisty/t1a40Lsn/22/
As you can see, when an item is dragged and dropped, it updates the field for all the lists. This addresses an issue if the user moves it from one box to another. Since the data is in an Array, we can simple use .join() to make a nice well formatted string of data.
I am building custom calendar and I have the following issue: since I have to create different events on that calendar, that start and end on certain dates, and I need to resize them (left and right), I would need to know if I am starting that resize from the left side or from the right side, so I know how to set those dates.
Basically, it doesn't select the proper dates when I resize from start and when I resize from the end.
EDIT: My draggable elements also get some sort of top/left values that mess up everything, and I cannot resize it to left more than their start width (to the right side it does what it should).. It is reducing width (- 47px) instead of adding that width.
This is the code I have for resizable now:
$(".draggable").resizable({
classes: {
"ui-resizable": "highlight"
},
containment: '.calendar-row',
grid: [47, 10],
handles: 'e, w',
resize: function (event, ui) {
lastEvent = event;
},
start: function (event, ui) {
uiEvent = true;
$( event.originalEvent.target ).one('click', function(e){ e.stopImmediatePropagation(); } );
},
stop: function (event, ui) {
setTimeout(function(){
uiEvent = false;
}, 300);
$( event.originalEvent.target ).one('click', function(e){ e.stopImmediatePropagation(); } );
if(isChrome || isEdge)
{
var resizedDate = new Date($(lastEvent.toElement).attr('data-date'));
// left to right
if(ui.size.width > ui.originalSize.width)
{
console.log($(lastEvent.toElement).attr('id'));
console.log($(lastEvent.toElement).attr('data-date'));
}
else{
var elemFromPoint = document.elementFromPoint(lastEvent.pageX, lastEvent.pageY).previousElementSibling;
console.log($(elemFromPoint).attr('id'));
console.log($(elemFromPoint).attr('data-date'));
}
}
if(isFirefox)
{
var resizedDate = new Date($(lastEvent.target).attr('data-date'));
$(lastEvent.target).hide();
// left to right
if(ui.size.width > ui.originalSize.width)
{
var elemFromPoint = document.elementFromPoint(lastEvent.pageX, lastEvent.pageY);
console.log($(elemFromPoint).attr('id'));
console.log($(elemFromPoint).attr('data-date'));
}
else{
var elemFromPoint = document.elementFromPoint(lastEvent.pageX, lastEvent.pageY).previousElementSibling;
console.log($(elemFromPoint).attr('id'));
console.log($(elemFromPoint).attr('data-date'));
}
$(lastEvent.target).show();
}
}
});
https://jsfiddle.net/ianderso222/y9ynouxs/36/
I hope this makes sense, If I need to elaborate I will do so.
Right now the code is working pretty well, doing everything I need it to except this one issue. I just need to alert the order that the squares have been dropped in, after all 4 have been placed.
So, if square4 is placed in the large container, that would show up first in the alert. If square2 is in the last, smallest box it would be last in the list, and so on.
I would use sortable but I am afraid it would not work with the current setup. The resizing to different sized containers would not work, or at least I was not able to get it to work. If there is a way to keep the current structure of resizing to fill container and sliding into place I would say do that, but from everything I have seen I feel I would have to essentially start from scratch.
Here is the JavaScript, pardon the messy code:
$('.holderList li').droppable({
drop: function(event, ui) {
var droppable = $(this);
var draggable = ui.draggable;
console.log(draggable.attr('id') + ' is ' + droppable.attr('id'));
var $this = $(this);
ui.draggable.position({
my: "center",
at: "center",
of: $this,
using: function(pos) {
$(this).animate(pos, 200, "linear");
}
});
ui.draggable.addClass('dropped');
ui.draggable.data('droppedin', $(this));
$(this).droppable('disable');
setTimeout(function() {
var dragID = ui.draggable;
if (!$(".ui-droppable").not(".ui-droppable-disabled").length) {
alert(draggable.attr('id'));
}
}, 400);
},
});
$(".square").draggable({
stack: ".square",
revert: function(event, ui) {
//overwrite original position
$(this).data("ui-draggable").originalPosition = {
width: 50,
height: 50
}
//return boolean
return !event;
},
drag: function(event, ui) {
var draggable = $(this).data("ui-draggable");
$.each(draggable.snapElements, function(index, element) {
ui = $.extend({}, ui, {
snapElement: $(element.item),
snapping: element.snapping
});
if (element.snapping) {
if (!element.snappingKnown) {
element.snappingKnown = true;
draggable._trigger("snapped", event, ui);
}
} else if (element.snappingKnown) {
element.snappingKnown = false;
draggable._trigger("snapped", event, ui);
}
});
if ($(this).data('droppedin')) {
$(this).data('droppedin').droppable('enable');
$(this).data('droppedin', null)
$(this).removeClass('dropped')
}
},
snap: ".holder",
snapMode: "inner",
snapTolerance: 8,
snapped: function(event, ui) {
var squareWidth = ui.snapElement.width();
var squareHeight = ui.snapElement.height();
ui.helper.css({
width: squareWidth,
height: squareHeight
});
}
});
Take a look at my solution:
Demo
First it assigns a data-current attribute to the droppable holder on every drop and sets it to the id of the draggable.
Then it itterates trough all the .holder elements and prints their data-current
Simple but works.
// On single drop
drop: function(event,ui){
...
droppable.attr('data-current', draggable.attr('id') );
}
//On all dropped
$('.holder').each(function(el){
console.log($(this).attr('data-current'));
});
I'm trying to implement something very close to what the 'Sortable Widget' would do, though I can't use it because of other things that doesn't work with the premade widget.
So I'm trying to recreate it's functionality with draggable and droppable elements:
$(".Element").draggable({
helper: 'original',
drag: function(event, ui) {
ElementWidth = $(this).outerWidth(true);
if($(this).prev().length){
LeftElementWidth = $(this).prev().outerWidth(true);
LeftElementLeftOffset = $(this).prev().offset().left;
if(parseFloat(ui.offset.left+(ElementWidth/2)) < parseFloat(LeftElementLeftOffset+(LeftElementWidth/2))){
$(this).prev().before($(this));
}
}
if($(this).next().length){
RightElementWidth = $(this).next().outerWidth(true);
RightElementLeftOffset = $(this).next().offset().left;
if(parseFloat(ui.offset.left+(ElementWidth/2)) > parseFloat(RightElementLeftOffset+(RightElementWidth/2))){
$(this).next().after($(this));
}
}
}
});
$("#Container").droppable({ accept: '.Element' });
It works fine, except for that the draggable-helper doesn't stay underneath the mouse-cursor when I move it's element to the next position.
Check out this fiddle:
http://jsfiddle.net/5qFhg/15/
You'll see what happens when you try to sort the green boxes. How can I keep the helper in position?
http://jsfiddle.net/margaret_/XM6f8/1/
Is this what you are looking for? Are you okay with using knockout? I can't add comments 'cause I don't have 50 reputation.
<input data-bind="value: name, visibleAndSelect: $data === viewModel.selectedTask(), event: { blur: function() { viewModel.selectTask(''); } }" />
Use parent and previous location to mimic the function.
ko.bindingHandlers.sortableList = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
$(element).data("sortList", valueAccessor()); //attach meta-data
$(element).sortable({
start: function(event, ui) {
//track the original position of the element
var parent = ui.item.parent();
var prev = ui.item.prev();
//create a function to move it back (if it has a prev sibling, insert after it, otherwise put it at the beginning)
ui.item.moveItemBack = prev.length ? function() { ui.item.insertAfter(prev); } : function() { parent.prepend(ui.item); };
},
update: function(event, ui) {
var item = ui.item.data("sortItem");
if (item) {
//identify parents
var originalParent = ui.item.data("parentList");
var newParent = ui.item.parent().data("sortList");
//figure out its new position
var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
if (position >= 0) {
//move the element back to its original position and let KO handle adding it to the new parent
if (originalParent !== newParent) {
ui.item.moveItemBack();
}
//place item in the proper position
newParent.remove(item);
newParent.splice(position, 0, item);
}
}
},
connectWith: '.container'
});
}
Do you want the divs to appear side by side?
I'm trying to select more than one item in a jQuery sortable set and then move the selected items around together.
Here's my weak beginning of an attempt to make it work. And here's the code:
HTML:
<div class="container">
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
<div>five</div>
</div>
JS:
$('.container div').draggable({
connectToSortable: '.container',
//How do I drag all selected items?
helper: function(e, ui) {
return $('.selected');
}
});
$('.container').sortable({
axis: 'y',
//How do I sort all selected items?
helper: function(e, ui) {
return $('.selected');
}
});
$('.container div').live('click', function(e) {
$(this).toggleClass('selected');
});
CSS:
body{background-color:#012;font-family:sans-serif;text-align:center;}
div{margin:5px 0;padding:1em;}
.container{width:52%;margin:1in auto;background-color:#555;border-radius:.5em;box-shadow:0 0 20px black;}
.container div{background-color:#333;color:#aaa;border:1px solid #777;background-color:#333;color:#aaa;border-radius:.25em;cursor:default;height:1em;}
.container div:hover{background-color:#383838;color:#ccc;}
.selected{background-color:#36a !important;border-color:#036 !important;color:#fff !important;font-weight:bolder;}
I don't know if I'm headed in the right direction or not. I can't find an example of this anywhere online. Just lots of related questions. Does anyone know how?
For example, if I've selected items 4 and 5 out of a list of 6. I want to be able to drag 4 and 5 to the top of the set to get this order - 4 5 1 2 3 6 - Or if I selected 5 and 1 and drag them to the bottom - 2 3 4 6 1 5
This seems to work with the multisortable plugin. Code below. Or see jsFiddle.
// ctrl + click to select multiple
$('.container').multisortable({
stop: function(e, ui) {
var $group = $('.ui-multisort-grouped').not(ui.item);
$group.clone().insertAfter($(ui.item));
$group.each(function() {
$(this).removeClass('ui-multisort-grouped');
});
$group.remove();
}
});
But what if multisortable breaks with future jQuery versions?
Modifying my answer here (according to your HTML and CSS) :
Select items to sort
Create a custom helper
Hide the selected items until sort is done
Resize the helper and placeholder according to the selection
Manually detach selected items from the current position and attach them to the new position after sort
Show the hidden items (undo step 3) after step5
$(function () {
$('.container').on('click', 'div', function () {
$(this).toggleClass('selected');
});
$(".container").sortable({
revert: true,
helper: function (e, item) {
if (!item.hasClass('selected')) item.addClass('selected');
var elements = $('.selected').not('.ui-sortable-placeholder').clone();
var helper = $('<div/>');
item.siblings('.selected').addClass('hidden');
return helper.append(elements);
},
start: function (e, ui) {
var elements = ui.item.siblings('.selected.hidden').not('.ui-sortable-placeholder');
ui.item.data('items', elements);
var len = ui.helper.children().length;
var currentHeight = ui.helper.height()
var itemHeight = ui.item.height() + 32; // 32 = 16x2 padding
ui.helper.height(currentHeight + (len * itemHeight))
ui.placeholder.height((len * itemHeight))
},
receive: function (e, ui) {
ui.item.before(ui.item.data('items'));
},
stop: function (e, ui) {
ui.item.siblings('.selected').removeClass('hidden');
$('.selected').removeClass('selected');
}
});
});
Updated Fiddle