Dragging a div out from underneath multiple other divs - javascript

I have been trying to create a test case for a webapplication we are making and I am quite close to a solution, but the final part has me stumped...
We need to be able to drag divs around (no problem there), but some of the divs need to be locked in place (again, no problem).
The problem arises when a draggable div is stuck underneath a non draggable div. I have fixed this somewhat, but it only works if there is only ONE non draggable div on top of the draggable one.The moment it overlaps with another non draggable div, it won't work. Which is weird, as I am correctly accessing the draggable div.
my HTML:
<body>
<div id="div1" class="draggable"></div>
<div id="div2" class="locked"></div>
<div id="div3" class="locked"></div>
</body>
And here is my javascript:
<script>
$(document).ready(function () {
$(document).mousemove(function (e) {
window.mouseXPos = e.pageX;
window.mouseYPos = e.pageY;
});
});
$(function () {
$(".draggable").draggable();
});
$('.locked').ready(function () {
$('.locked').mousedown(function (e) {
var layer = e.target;
$("#data").html($("#data").html() + " " + layer.id);
$(layer).addClass("hide");
var lowerLayer = document.elementFromPoint(window.mouseXPos, window.mouseYPos);
if ($(lowerLayer).hasClass("locked")) {
$(lowerLayer).mousedown();
}
else if ($(lowerLayer).hasClass("draggable")) {
$("#data").html($("#data").html() + " " + lowerLayer.id);
$(lowerLayer).trigger(e);
}
$(layer).removeClass("hide");
});
});
</script>
Okay, so the idea is this, I use jquery to set everything with the class "draggable" to be able to be dragged. The body gets a mousemove event to store the current mouse position. While all the elements with the "locked" class, will fire a mousedown event. In this event, I hide the element I am clicking on by adding a class called "hide" (which contains only a display: none).
At this point, the element underneath the clicked element is visible. Using the elementFromPoint combined with my stored mouse positions, I grab the lower element.
By then checking if it is "locked" or "draggable" I can determine what this element should do. If it's locked, I force it to execute a mousedown event. If it's draggable, I use the .trigger(e) to start dragging.
I then remove the "hide" class again, so that the element does not stay hidden.
Using the div called data, I can see that the function does indeed reach the draggable div. However, if both locked divs are on top of it, it will not start dragging. If there is only one locked div on top, I can then start dragging the draggable div.
In my opinion, this should work without any problems. I mean, it works with only 1 overlapping div, and even with 2 (or more), I am still triggering the code in the else if statement.
What am I missing/overlooking?
-Ferdy

I rewrote your script slightly differently and it seems to work. The problem might be that you're using e.target, but i'm not really sure. Here's what i did:
$(".draggable").draggable();
$(".locked").mousedown(function(e) {
var layer = $(this);
layer.addClass("hide");
var lowerLayer = $(document.elementFromPoint(e.pageX, e.pageY));
if (lowerLayer.hasClass("draggable") || lowerLayer.hasClass("locked"))
lowerLayer.trigger(e);
layer.removeClass("hide");
});
http://jsfiddle.net/AXycq/

Related

Detecting .scroll on div with jQuery

EDIT: looks like I was approaching this from the wrong direction, will see if I can close this. Future reading:
Get mouse wheel events in jQuery?
I'm trying to create a scrollable <div> element (on a horizontal plane); not there with the maths yet but I'm having trouble with something more basic - the scroll event doesn't seem to be registering. Am I missing anything obvious?
I've taken out all the CSS associated with the <div> barring what you see in the samples below:
<div id="about-carousel" carousel-offset="0" style="height:20rem; background-color:red;">
</div>
$(document).ready(function() {
handleCarousels(['#about-carousel']);
});
function handleCarousels(idArray) {
for(var n in idArray) {
var target = idArray[n];
// this works
$(target).click(function() {
console.log('clicked');
});
// this doesn't
$(target).scroll(function(e) {
e.stopPropagation();
console.log('scrolls');
});
}
}
According to this answer the element needs to have overflow: scroll set, but that makes no difference. Even if I fill the <div> with images, it doesn't trigger the .scroll event at all.
Any ideas?
UPDATE BEFORE POSTING: the 'scrolls' message is being output to console if I set the div to overflow: scroll and fill it with images, but only when I drag the scroll bar. I thought it was meant to capture mouse events too?
I'm using jQuery 1.11.1.

Prevent parent page from scrolling when mouse is over embedded iframe in Firefox

...without limiting the scroll inside the iframe or the need to specifically name/tag all scrollable elements.
Imagine google maps widget embedded in parent page. When you zoom in the widget you don't want the parent page to scroll, obviously.
I thought an answer to my previous question solved the problem:
While scrolling inside an iframe, the body doesn't know anything about
what happens there. But when iframe scroller reach the bottom or the
top, it pass scrolling to body.
Cancel the event that propagates from the iframe.
But the solution does not work in Firefox because Firefox will not - by design - propagate events captured by iframe to the parent page, yet strangely it will scroll the parent page. See jsfiddle here.
$('body').bind('mousewheel DOMMouseScroll', onWheel);
function onWheel (e){
if (e.target === iframe)
e.preventDefault();
console.log(e);
}
So, how do I prevent page from scrolling when user zooms content in embedded iframe, in Firefox?
Since it is a bug in Firefox, the workaround is to work directly with the scroll event, instead of the mousewheel / DOMMouseScroll ones.
The way I did: When user enters the mouse over the iframe, I set a flag to true, and when he leaves the mouse out there, I set it back to false.
Then, when user tries to scroll, but the mouse arrow is inside the iframe, I prevent the parent window scrolling. But, unfortunately, you can't prevent the window scrolling with the usual e.preventDefault() method, so we still need another workaround here, forcing the window to scroll exactly to the X and Y positions it was already before.
The full code:
(function(w) {
var s = { insideIframe: false }
$(iframe).mouseenter(function() {
s.insideIframe = true;
s.scrollX = w.scrollX;
s.scrollY = w.scrollY;
}).mouseleave(function() {
s.insideIframe = false;
});
$(document).scroll(function() {
if (s.insideIframe)
w.scrollTo(s.scrollX, s.scrollY);
});
})(window);
I've created an immediately executed function to prevent defining the s variable in the global scope.
Fiddle working: http://jsfiddle.net/qznujqjs/16/
Edit
Since your question was not tagged with jQuery (although inside it, you've showed a code using the library), the solution with vanilla JS is as simple as the above one:
(function(w) {
var s = { insideIframe: false }
iframe.addEventListener('mouseenter', function() {
s.insideIframe = true;
s.scrollX = w.scrollX;
s.scrollY = w.scrollY;
});
iframe.addEventListener('mouseleave', function() {
s.insideIframe = false;
});
document.addEventListener('scroll', function() {
if (s.insideIframe)
w.scrollTo(s.scrollX, s.scrollY);
});
})(window);
Given all the prerequisites, I think the following is the sanest way to make this work in Firefox.
Wrap your iframe with a div which is a little bit shorter to enable vertical scrolling in it:
<div id="wrapper" style="height:190px; width:200px; overflow-y: auto; overflow-x: hidden;">
<iframe id="iframeid" height="200px" width="200px" src="about:blank">
</iframe>
</div>
Now you can center the iframe vertically and re-position it every time
the wrapper receives a scroll event (it will occur when a user tries to scroll away at frame edges):
var topOffset = 3;
wrapper.scrollTop(topOffset);
wrapper.on("scroll", function(e) {
wrapper.scrollTop(topOffset);
});
Combine this with your previous fix for Chrome, and it should cover all major browsers. Here is a working example - http://jsfiddle.net/o2tk05ab/5/
The only outstanding issue will be the visible vertical scrollbar on a wrapper div. There are several ways to go about it, for instance - Hide scroll bar, but still being able to scroll
I think that will solve your problem
it solved mine
var myElem=function(event){
return $(event.toElement).closest('.slimScrollDiv')
}
$(document).mouseover(function(e){
window.isOnSub=myElem(e).length>0
})
$(document).on('mousewheel',function(e){
if(window.isOnSub){
console.log(e.originalEvent.wheelDelta);
if( myElem(e).prop('scrollHeight')-myElem(e).scrollTop()<=myElem(e).height()&&(e.originalEvent.wheelDelta<0)){
e.preventDefault()
}
}
})
replace '.slimScrollDiv' with the element selector you want to
prevent parent scroll while your mouse is on it
http://jsbin.com/cutube/1/edit?html,js,output

interact.js dragged item move on top

it's my first time to use this plugin.
I want to add a stacking of elements whenever it's dragged. I use z-index and position relative.
onstart: function (event) {
//get max z-index on page
var maxZ = Math.max.apply(null,
$.map($('body > *'), function(e,n) {
if ($(e).css('position') != 'static')
return parseInt($(e).css('z-index')) || 1;
}));
event.target.style.background = 'red';
event.target.style.zIndex = maxZ + 1;
event.target.style.position = 'relative';
},
Is there more efficient way to do this?
original drag and drop demo
my edited drag and drop demo with stacking
Your solution works well if the elements are not also used as dropzones. When dropzones overlap, interact.js chooses the element that is deepest in the DOM and so should appear above the other dropzones ulness some CSS properties change the drawing order (eg. position, transform, z-index, etc.) If overlapping dropzones are re-ordered with z-index then it might happen that the drop targets look incorrect.
If the elements that you're targeting are all siblings and all have absolute or fixed position (before any drag happens), then appending one element to it's parent should bring it in front of the others without using z-index so drop checks should work fine.
onstart: function (event) {
var target = event.target;
// Bring element in front of its siblings
target.parentNode.appendChild(target);
...
}

Drag Leave and Drag Enter

When I drag a file over to my window I wish to show an overlay, and when the file is dragged off the window I wish to remove the overlay.
$(window).on('dragleave', this.onDragLeave);
$(window).on('dragenter', this.onDragEnter);
p.onDragEnter = function(e) {
console.log('ENTER');
};
p.onDragLeave = function(e) {
console.log('LEAVE');
};
The above works fine, when I enter and leave the window it logs correctly.
The problem starts when I start fading in and out my overlay:
p.onDragEnter = function(e) {
console.log('ENTER');
$('#drag-overlay').fadeIn();
};
p.onDragLeave = function(e) {
console.log('LEAVE');
$('#drag-overlay').fadeOut();
};
With the above, it just fades in and out again and again. I'm not sure whats going on, it's as if when the overlay fades in it fires a drag leave, i'm not sure why?
The overlay is just an absolute div, width and height 100%.
The problem is that by showing an overlay, you are causing the dragged item to leave the parent and drag into the overlay. Then hiding the overlay causes drag to trigger in the parent.
Fortunately, the solution is simple and can be done in css:
#drag-overlay
{
pointer-events: none;
...
}
See this jsfiddle for a working solution.
If you remove pointer-events:none you get the same behaviour. pointer-events:none just means that the parent ondragleave method isn't fired when dragging over the overlay.

Fast moving mouse never triggers mouseleave event

When mousing over certain buttons on my site, I'd like for tooltips to appear that instruct users. Basically, whenever a button with the 'has_tooltip' class is moused over, a tooltip is attached.
$('.has_tooltip').live({
mouseenter : function(e) {
if($('#tooltip_container').length > 0){
$('#tooltip_container').remove();
}
var $t = $(this), text = $t.attr('rel'), left = e.pageX-25, top = e.pageY-25;
if($t.attr('rev') === '1') {
text += ' <span class="tooltip_warning">You must be logged in to make use of this.</span>'
}
$tooltip = $('<div id="tooltip_container">'+text+'</div>');
$('body').prepend($tooltip);
$tooltip.css({
left: left+'px',
top: top+'px'
});
},
});
And when a user's cursor leaves the newly created tooltip box, it should disappear
$('#tooltip_container').live({
mouseleave : function(e){
$(this).remove();
}
});
However, a fast moving mouse over a button with the 'has_tooltip' class adds the tooltip, but moves too quickly for the mouseleave event to trigger.
Anyone have some tips on how I can fix this?
'If the mouse does not enter the tooltip (the tooltip appears below the mouse), the browser may not trigger the mouseleave event. You may want to add an additional selector. Try this:
$('#tooltip_container','.has_tooltip').live({
mouseleave : function(e){
$('#tooltip_container').remove();
}
});
I would highly recommend removing the HTML from your tooltip method though... try creating an empty div and add the tooltip text and positioning when you go to show it -- try to add as little to the DOM as possible (create a hidden div for most of the tooltip HTML and only change the actual text content of it as necessary).
Ideally, your mouseenter should simply replace the tooltip text and show the div with correct positioning. The mouseleave event should just hide() the tooltip div (not remove it from the DOM just to be created and added again later).

Categories