I have few components on my page which are draggable. They have the class
draggable. When this is dragged and stops I need to get the left and top positions
function init() {
var p = $('#groups-parent');
$('.draggable').draggable({
obstacle: ".obstacle",
preventCollision: true,
containment: [
p.offset().left, p.offset().top, p.offset().left + p.width() - 225, p.offset().top + p.height() - 50
],
start: function(event, ui) {
$(this).removeClass('obstacle');
},
stop: function (event, ui) {
console.log(ui);
$('#groups-parent').css({ 'min-height': Math.max(500, ui.position.top + 50) });
$(this).addClass('obstacle');
getTopLeftPosition();
}
});
}
Here is my function that should get the left and top positions.
function getTopLeftPosition(e) {
var coordX = e.pageX - $(this).offset().left,
coordY = e.pageY - $(this).offset().top;
alert(coordX + ' , ' + coordY);
}
This does not work. All I get in my browser is this error message
Cannot read properties of undefined (reading 'left')
How can I get this left and top positions? The alert should indicate it.
You probably just need to bind this to the function, by changing
getTopLeftPosition();
to either
getTopLeftPosition.call(this, event);
or
getTopLeftPosition.bind(this)(event)
Related
I'm using jQuery UI slider and drag and drop to create a way of specifying a rating out of 100 for each div.
The problem is when I drag my divs onto the slider, I do not know how to get the value for the position of each div on the slider. Here is a plnk + some code;
http://plnkr.co/edit/wSS2gZnSeJrvoBNDK6L3?p=preview
$(init);
function init() {
var range = 100;
var sliderDiv = $('#ratingBar');
sliderDiv.slider({
min: 0,
max: range,
slide: function(event, ui) {
$(".slider-value").html(ui.value);
}
});
var divs = '.itemContainer'
$(divs).draggable({
containment: '#content',
cursor: 'move',
snap: '#ratingBar',
revert: function(event, ui) {
$(this).data("uiDraggable").originalPosition = {
top: 0,
left: 0
};
return !event;
}
});
var position = sliderDiv.position(),
sliderWidth = sliderDiv.width(),
minX = position.left,
maxX = minX + sliderWidth,
tickSize = sliderWidth / range;
$('#ratingBar').droppable({
tolerance: 'touch',
drop: function(e, ui) {
var finalMidPosition = $(ui.draggable).position().left + Math.round($(divs).width() / 2);
if (finalMidPosition >= minX && finalMidPosition <= maxX) {
var val = Math.round((finalMidPosition - minX) / tickSize);
sliderDiv.slider("value", val);
$(".slider-value").html(ui.value);
}
}
});
$(".slider-value").html(sliderDiv.slider('value'));
}
Hope someone can offer some advice,
Cheers
(Also, if someone knows why I can drop the divs outside of the rating bar on either side, please let me know!)
Jquery UI has a function called stop and there is where you want to handle all the calculations as such:
stop: function(event, ui) {
// Object dragged
var e = ui.helper;
// Offset of that object
var eOffset = e.offset().left;
// Sliders offset
var sliderOffset = sliderDiv.offset().left;
// Subtract their offsets
var totalOffset = eOffset - sliderOffset;
// Width of box dragged
var boxWidth = ui.helper.width();
// Subtract their widths to account for overflow on end
var sliderW = sliderDiv.width() - boxWidth;
// Get percent moved
var percent = totalOffset / sliderW * 100;
// Find .slider-value and replace HTML
e.find(".slider-value").html(Math.round(percent));
}
This will be located here:
$(divs).draggable({
containment: '#content',
cursor: 'move',
snap: '#ratingBar',
revert: function(event, ui) {
$(this).data("uiDraggable").originalPosition = {
top: 0,
left: 0
};
return !event;
},
.... <-- HERE
});
I have provided commenting for every line so you understand what I did.
Above your draggable function you need to define a tighter containment area as such:
var left = sliderDiv.offset().left;
var right = left + sliderDiv.width();
var top = sliderDiv.offset().top;
var bottom = top + sliderDiv.height();
var height = $(".itemContainer").height();
var width = $(".itemContainer").width();
$(divs).draggable({
containment: [left, 0, right - width, bottom - height],
.....
});
This will prevent the draggable from being moved left, right, or below the slider.
Basically you grab the position of the object grabbed, adjust for some offset, adjust for some width, calculate the percentage, and replace the html (Could use text() as well).
Here is your plunker redone: http://plnkr.co/edit/3WSCo77c1cC5uFiYO8bV?p=preview
Documentation:
Jquery UI
Jquery .find
Update:
Here is the final fiddle. Thanks Twisty.
https://jsfiddle.net/natjkern/55n1Lg61/14/
Here is a mock up of my site
http://jsfiddle.net/natjkern/pjq9wqLy/
On the right I have a fixed list of elements called #userList. This element must be set to overflow: auto, as the number of user elements is variable. I then need to drag a copy into dropzone, which again can vary in size, so it's container must be also set to overflow auto. I need the container to auto scroll when dragged element is dragged to edge to allow user to place element anywhere in dropzone. So far my best idea is to append to body first to escape the original auto overflow container then append to #dropzone on droppable({over:}). But this isn't really working out. Any UI experts out there that can give me a hand?
Here is my code so far:
$(document).ready(function () {
$('.moveMe').draggable({
helper: "clone",
appendTo: 'body',
revert: 'invalid',
scroll: true,
});
$('#dropZone').droppable({
accept: '.moveMe',
//here is where my problem arrises
// I need #dropZoneCon to scroll to place
over: function (event, ui) {
ui.helper.draggable.detach().appendTo($(this));
$('#DropZone div').css('position','absolute');
ui.helper.draggable.draggable('option', 'containment', 'parent');
},
/* */
drop: function (event, ui) {
ui.helper.draggable.detach().appendTo($(this));
$('#DropZone div').css('position','absolute');
ui.helper.draggable.draggable('option', 'containment', 'parent');
},
});
});
Note: commenting out over: allow me to place element where I would like, but I really need the scrolling to work.
This is a little clunky, but it does what you're looking to do.
http://jsfiddle.net/Twisty/55n1Lg61/5/
JQuery
$(document).ready(function() {
var tEntered = false;
$('.moveMe').draggable({
helper: "clone",
appendTo: 'body',
revert: 'invalid',
drag: function(e, u) {
var curPos = u.offset;
var $target = $("#dropZoneCon");
var tVP = $target.offset();
var tW = Math.floor($target.width());
var tH = Math.floor($target.height());
var pad = 20;
var scrInc = 10;
var x0 = tVP.top;
var x1 = tVP.top + tH;
var y0 = tVP.left;
var y1 = tVP.left + tW;
$("#dragInfo").html("Over #dropZone: " + tEntered + ", top: " +
tVP.top + "/" + curPos.top + "/" + x1 + " left: " + tVP.left + "/" + curPos.left + "/" + y1);
if (tEntered) {
// Increase Scroll
if (curPos.left >= (y1 - pad) && curPos.left <= y1) {
$target.scrollLeft($target.scrollLeft() + scrInc);
}
if (curPos.top >= (x1 - pad) && curPos.top <= x1) {
$target.scrollTop($target.scrollTop() + scrInc);
}
// Decrease Scroll
if (curPos.left >= y0 && curPos.left <= (y0 + pad)) {
$target.scrollLeft($target.scrollLeft() - scrInc);
}
if (curPos.top >= x0 && curPos.top <= (x0 + pad)) {
$target.scrollTop($target.scrollTop() - scrInc);
}
}
}
});
$('#dropZone').droppable({
accept: '.moveMe',
over: function(e, u) {
tEntered = true;
},
out: function(e, u) {
tEntered = false;
},
drop: function(event, ui) {
ui.draggable.detach().appendTo($(this));
$('#DropZone div').css('position', 'absolute');
ui.draggable.draggable('option', 'containment', 'parent');
},
});
});
So we do a lot of the heavy lifting in the drag event since we can determine the position of the drag object from this event. In the drag event we define a number of variables:
curPos the current position of our draggable object in { top, left }
$target the dropZone container
tVP the Target View Port offset in { top, left }
tW & tH the Target's Width and Height
scrInc a value we will use to increase or decrease the scroll position
pad the amount of pixels to use as the edge detection of tVP
x0 & x1 are the tVP top edge and bottom edge
y0 & y1 are the tVP left edge and right edge
I defined tEntered outside of these functions, so I can access it from either draggable or droppable. Since droppable has over and out, we can use those to change this value.
Now, when tEntered is true, meaning we are dragging an object over the target, we look to see if the x and y might be within our padding. If they are we increase or decrease the scroll position.
I found this helpful: http://www.jqwidgets.com/community/topic/how-detect-area-dropped-inside-a-div-into-another-div/
This should allow you to do what you want unless I misunderstood. Comment if you have questions.
I've tried using jquery's built in draggable and I've tried using custom drag functions with no avail. Both have their respected issues and I will try to highlight both of them.
Basically, I am trying to allow the dragging of an element that is on a scaled div container. The following methods work okay on a scaled element that is less than around 2. But if you go any higher than that, we see some issues.
Any help would be appreciated. Thank you for your time.
HTML
<div id="container">
<div id="dragme">Hi</div>
</div>
Method 1 (Jquery draggable function)
I've tried the jquery draggable function as you can see in this jsfiddle example.
The problems I found in this example are the following:
Biggest concern: The droppable container does not change when it is scaled up. So if the element is being dragged over part of the scaled container that isn't a part of it's original size, it will fail.
When you click to drag a div, it teleports a little bit away from the mouse and is not a seamless drag.
JS
var percent = 2.5;
$("#dragme").draggable({
zIndex: 3000,
appendTo: 'body',
helper: function (e, ui) {
var draggable_element = $(this),
width = draggable_element.css('width'),
height = draggable_element.css('height'),
text = draggable_element.text(),
fontsize = draggable_element.css('font-size'),
textalign = draggable_element.css('font-size');
return $('<div id="' + draggable_element.id + '" name="' + draggable_element.attr('name') + '" class="text">' + text + '</div>').css({
'position': 'absolute',
'text-align': textalign,
'background-color': "red",
'font-size': fontsize,
'line-height': height,
'width': width,
'height': height,
'transform': 'scale(' + percent + ')',
'-moz-transform': 'scale(' + percent + ')',
'-webkit-transform': 'scale(' + percent + ')',
'-ms-transform': 'scale(' + percent + ')'
});
},
start: function (e, ui) {
$(this).hide();
},
stop: function (e, ui) {
$(this).show();
}
});
$("#container").droppable({
drop: function (event, ui) {
var formBg = $(this),
x = ui.offset.left,
y = ui.offset.top,
drag_type = ui.draggable.attr('id');
var element_top = (y - formBg.offset().top - $(ui.draggable).height() * (percent - 1) / 2) / percent,
element_left = (x - formBg.offset().left - $(ui.draggable).width() * (percent - 1) / 2) / percent;
$(ui.draggable).css({
'top': element_top,
'left': element_left
});
}
});
Method 2 - Custom drag function
I've tried using a custom drag function but it unusable after around a 2 scale.
jsfiddle on a scale(2) - Looks like the draggable div is having a seizure.
jsfiddle on a scale(2.5) - The draggable div flys away when you try to drag it.
JS
(function ($) {
$.fn.drags = function (opt) {
opt = $.extend({
handle: "",
cursor: "move"
}, opt);
if (opt.handle === "") {
var $el = this;
} else {
var $parent = this;
var $el = this.find(opt.handle);
}
return $el.css('cursor', opt.cursor).on("mousedown", function (e) {
if (opt.handle === "") {
var $drag = $(this).addClass('draggable');
} else {
$(this).addClass('active-handle')
var $drag = $parent.addClass('draggable');
}
var
drg_h = $drag.outerHeight(),
drg_w = $drag.outerWidth(),
pos_y = $drag.offset().top + drg_h - e.pageY,
pos_x = $drag.offset().left + drg_w - e.pageX;
follow = function (e) {
$drag.offset({
top: e.pageY + pos_y - drg_h,
left: e.pageX + pos_x - drg_w
})
};
$(window).on("mousemove", follow).on("mouseup", function () {
$drag.removeClass('draggable');
$(window).off("mousemove", follow);
});
e.preventDefault(); // disable selection
}).on("mouseup", function () {
if (opt.handle === "") {
$(this).removeClass('draggable');
} else {
$(this).removeClass('active-handle');
$parent.removeClass('draggable');
}
});
}
})(jQuery);
$("#dragme").drags({}, function (e) {});
Here are a few of my findings to make sure dragging on a scaled container works for method one. The only caveat is to make sure you have var percent as the scaled percentage declared before any of these actions happen.
First, use this code at the top of your javascript. This wil help making sure that the droppable area works with a sacled container.
$.ui.ddmanager.prepareOffsets = function( t, event ) { var i, j, m = $.ui.ddmanager.droppables[ t.options.scope ] || [], type = event ? event.type : null, list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack(); droppablesLoop: for ( i = 0; i < m.length; i++ ) { if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ], ( t.currentItem || t.element ) ) ) ) { continue; } for ( j = 0; j < list.length; j++ ) { if ( list[ j ] === m[ i ].element[ 0 ] ) { m[ i ].proportions().height = 0; continue droppablesLoop; } } m[ i ].visible = m[ i ].element.css( "display" ) !== "none"; if ( !m[ i ].visible ) { continue; } if ( type === "mousedown" ) { m[ i ]._activate.call( m[ i ], event ); } m[ i ].offset = m[ i ].element.offset(); m[ i ].proportions({ width: m[ i ].element[ 0 ].offsetWidth * percent, height: m[ i ].element[ 0 ].offsetHeight * percent }); } };
Here are a few functions that are necessary to fix the drag so it works on a scaled container.
function dragFix(event, ui) { var changeLeft = ui.position.left - ui.originalPosition.left, newLeft = ui.originalPosition.left + changeLeft / percent, changeTop = ui.position.top - ui.originalPosition.top, newTop = ui.originalPosition.top + changeTop / percent; ui.position.left = newLeft; ui.position.top = newTop; }
function startFix(event, ui) { ui.position.left = 0; ui.position.top = 0; var element = $(this); }
You will want this if you want to enable the element to be resizable on a scaled container.
function resizeFix(event, ui) { var changeWidth = ui.size.width - ui.originalSize.width, newWidth = ui.originalSize.width + changeWidth / percent, changeHeight = ui.size.height - ui.originalSize.height, newHeight = ui.originalSize.height + changeHeight / percent; ui.size.width = newWidth; ui.size.height = newHeight; }
To make an element draggable, I use the following function.
$("ELEMENT").resizable({ minWidth: - ($(this).width()) * 10, minHeight: - ($(this).height()) * 10, resize: resizeFix, start: startFix });
$("ELEMENT").draggable({ cursor: "move", start: startFix, drag: dragFix }); }
A similar problem is mentioned here: jquery - css "transform:scale" affects '.offset()' of jquery
It seems the problem arises from the fact that jQuery fails to return exact size for scaled elements and therefore failing setting right offset values to the element.
To solve this, he is suggesting first setting scale to 1 and setting offset and then again resetting scale value.
But this alone does not solve the problem here. Since mouse position is taken while it is scaled, position values should also be divided by scale value.
Here is an edited version of code:
var scl = 2.5;
var
drg_h = $drag.outerHeight(),
drg_w = $drag.outerWidth(),
pos_y = $drag.offset().top/scl + drg_h - e.pageY/scl,
pos_x = $drag.offset().left/scl + drg_w - e.pageX/scl;
follow = function(e) {
var size = {
top:e.pageY/scl + pos_y - drg_h+scl*2,
left:e.pageX/scl + pos_x - drg_w+scl*2
};
$drag.parent().css("transform","scale(1)");
$drag.offset(size);
$drag.parent().css("transform","scale("+scl+")");
};
Note: I only replaced scale value for transform tag, since I am using chrome. You can also replace all instances or instead you can use a different class with 1 scale value.
JSFiddle is also here.
Here is an example of simple drag with scaling, however, in prue dom.
<style>
#dragme {
position:absolute;
border:1px solid red;
background:pink;
left:10px;
top:20px;
width:100px;
height:200px;
}
#container {
transform: scale(2,2) translate(100px,100px);
position:relative;
border:1px solid green;
background:grey;
width:200px;
height:300px;
}
</style>
<body>
<div id="container">
<div id="dragme">Hi</div>
</div>
<script>
var dragme=document.getElementById("dragme");
var container=document.getElementById("container");
dragme.onmousedown=function Drag(e){
this.ini_X = this.offsetLeft-e.clientX/2;
this.ini_Y = this.offsetTop-e.clientY/2;
container.onmousemove = move;
container.onmouseup = release;
return false;
}
function move(e){
e.target.style.left = e.clientX/2 + e.target.ini_X + 'px';
e.target.style.top = e.clientY/2 + e.target.ini_Y + 'px';
}
function release(){
container.onmousemove=container.onmouseup=null;
}
</script>
</body>
I am using InteractJs and I almost have it working but when I put the star on a tree that was small before it goes to the top of the small tree.
So when you drag the star on a tree the other trees get smaller. When you drag it to another tree the trees get normal height again but the star goes to the top of the small tree height that it used to be.
interact('.draggable')
.draggable({
onmove: function (event) {
var target = event.target,
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
target.style.webkitTransform =
target.style.transform =
'translate(' + x + 'px, ' + y + 'px)';
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
},
onend: function (event) {
var textEl = event.target.querySelector('p');
textEl && (textEl.textContent =
'moved a distance of '
+ (Math.sqrt(event.dx * event.dx +
event.dy * event.dy)|0) + 'px');
}
})
.inertia(true)
.restrict({
drag: "parent",
endOnly: true,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
});
interact('.draggable').snap({
mode: 'anchor',
anchors: [],
range: Infinity,
elementOrigin: { x: 0.5, y: 2 },
endOnly: true
});
// enable draggables to be dropped into this
interact('.tree').dropzone({
// only accept elements matching this CSS selector
accept: '#star',
// Require a 75% element overlap for a drop to be possible
overlap: 0.75,
// listen for drop related events:
ondropactivate: function (event) {
// add active dropzone feedback
event.target.classList.add('drop-active');
},
ondragenter: function (event) {
var draggableElement = event.relatedTarget,
dropzoneElement = event.target;
// feedback the possibility of a drop
clearInterval(interval); // stop star rotation
dropzoneElement.classList.add('drop-target');
draggableElement.classList.add('can-drop');
$('.tree:not(.drop-target)').find('img').animate({
opacity: .5,
height: "160px"
});
var dropRect = interact.getElementRect(event.target),
dropCenter = {
x: dropRect.left + dropRect.width / 2,
y: dropRect.top + dropRect.height / 2
};
event.draggable.snap({
anchors: [ dropCenter ]
});
},
ondragleave: function (event) {
var draggableElement = event.relatedTarget,
dropzoneElement = event.target;
// remove the drop feedback style
event.target.classList.remove('drop-target');
event.relatedTarget.classList.remove('can-drop');
$('.tree:not(.drop-target)').find('img').animate({
opacity: 1,
height: "186px"
});
event.draggable.snap(false);
},
ondrop: function (event) {
//Dropped event
},
ondropdeactivate: function (event) {
// remove active dropzone feedback
event.target.classList.remove('drop-active');
event.target.classList.remove('drop-target');
}
})
//Start star rotation
var angle = 0;
var interval = setInterval(function(){
angle+=1;
$("#star img").rotate(angle);
},50)
jsfiddle: http://jsfiddle.net/hpq7rpnh/4/
Library: http://interactjs.io/
I am placing an image on the canvas using drap & drop but the problem is, after dropping the image on canvas, image is loosing it's original position where it is dropped.
Sometimes it comes down from the original position and sometimes moves to right. Here is the code:
$("#draggable1").draggable({
stop: function(event, ui)
{
var canv = document.getElementById("tools_sketch");
var rect = canv.getBoundingClientRect();
x = event.clientX - rect.left;
y = event.clientY - rect.top;
ans = confirm("Is it correct position?");
if (ans)
{
dropimg_over_ground("img1", x, y);
$('#draggable1').remove();
}
}
});
dropimg_over_ground = function(imgid, lefti, topi)
{
var c = document.getElementById("tools_sketch");
var ctx = c.getContext("2d");
var img = document.getElementById(imgid);
ctx.drawImage(img, lefti, topi);
}
I have searched forum alot and tried many solutions but it's not worked.
Just subtract the offset of the container:
$('#drag').draggable({
stop: function(e, ui){
console.log('drag');
console.log($(this).offset().top - $('#canvas').offset().top)
console.log($(this).offset().left - $('#canvas').offset().left)
}
});
$("#canvas").droppable({
accept: "#drag",
drop: function (event, ui) {
},
out: function (event, ui) {
console.log('dragged out');
}
});
Working example,
http://jsfiddle.net/kBM6u/6/
check the console for the results
Regards