How to prevent dragenter event fires on the draggable element itself? - html

I have a list of element. They are all have draggable="true" attribute and can fire ondragenter and ondragover event. In other words, they are all draggable and droppable.
The problem is, when I drag an element, its ondragenter event fires immediately. This is not what I expected.
How can I prevent the these events which I am dragging an element?
var div = document.querySelectorAll('.div');
var i = 0;
for(i = 0; i < div.length; i++) {
(function(j) {
div[j].addEventListener('dragstart', handlDragStart);
div[j].addEventListener('dragenter', handleDragEnter);
div[j].addEventListener('dragover', handleDragOver);
})(i)
}
function handlDragStart(ev) {
ev.dataTransfer.effectAllowed = 'move';
ev.dataTransfer.dropEffect = 'move';
}
function handleDragEnter (ev) {
ev.preventDefault();
ev.target.classList.add('enter');
}
function handleDragOver (ev) {
ev.preventDefault();
}
.div {
width:100px;
height:100px;
border: 1px solid #333;
background-color: #ddd;
display: inline-block;
}
.enter {
border: 3px dotted red;
}
<div>
<div class="div" draggable="true">1</div>
<div class="div" draggable="true">2</div>
<div class="div" draggable="true">3</div>
<div class="div" draggable="true">4</div>
<div class="div" draggable="true">5</div>
</div>
I made this example.I expect that when I drag a rec into another rec, the latter get a red border. You can see when I start drag, the first rec get the red border immediately which means the dragenter() fires. How can I prevent it?

Related

dragleave event is firing on inner childs

I am trying to use HTML5 drag and drop and make the dropable container to change its style when the draggable element is over it.
the problem is if the dropable container contain inner elements a dragleave events getting fired making the container to lose its style.
as you can see when the draggable element is getting inside the small green box. we lose the red border of the outside div.
<html>
<head>
<style>
.droptarget {
width: 100px;
height: 100px;
margin: 15px;
margin-right: 100px;
padding: 10px;
border: 1px solid #aaaaaa;
}
.inner-droptarget {
margin-left: 20px;
margin-top: 20px;
width: 50px;
height: 50px;
border: 1px solid green
}
</style>
</head>
<body>
<p ondragstart="dragStart(event)" draggable="true" id="dragtarget">Drag me!</p>
<div class="droptarget" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" ondrop="drop(event)" ondragover="allowDrop(event)">
<div class="inner-droptarget">
</div>
</div>
<script>
function dragStart(event) {
event.dataTransfer.setData("Text", event.target.id);
}
function dragEnter(event) {
if ( event.target.className == "droptarget" ) {
event.target.style.border = "3px dotted red";
}
}
function dragLeave(event) {
if ( event.target.className == "droptarget" ) {
event.target.style.border = "";
}
}
</script>
<p>
The border of the outside div should remain red even if dragging into the green div!
</p>
</body>
</html>
You have to ignore "dragleave" events that are fired on elements that are contained in "droptarget" (for example "inner-droptarget").
To do so, you can detect if the event was fired from a descendant of the drop area. Your handler will look like this:
function dragLeave(event) {
if ( event.target.className == "droptarget" ) && !($('.droptarget').contains(event.fromElement) {
event.target.style.border = "";
}
}
When you enter the inner div during dragging, the event drag enter and then drag leave event is firing. We need a way to mark that you are leaving the outer div and entering the inner div so the border stays red on the outer div.
I can get both borders to be red, but when you leave its a similar problem, both borders go back to green.
<body>
<p ondragstart="dragStart(event)" draggable="true" id="dragtarget">Drag me!</p>
<div id="myOuterDiv" class="droptarget" ondragenter="dragEnter1(event)"
ondragleave="dragLeave1(event)"
ondrop="drop(event)"
ondragover="allowDrop(event)">
<div id="myInnerDiv" class="inner-droptarget" ondragenter="dragEnter2(event)"
ondragleave="dragLeave2(event)">
</div>
</div>
<script>
var inOuter = false;
var inInner = false;
function dragStart(event) {
event.dataTransfer.setData("Text", event.target.id);
}
function dragEnter1(event) {
inOuter = true;
document.getElementById("myInnerDiv").addEventListener("ondragenter", dragEnter2);
console.log("entered outer");
return highlightBorder();
}
function dragLeave1(event) {
console.log("left outer");
inOuter = false;
highlightBorder();
event.preventDefault();
}
function dragEnter2(event) {
console.log("entered inner");
inInner = true;
inOuter = true;
return highlightBorder();
}
function dragLeave2(event) {
console.log("left inner");
inInner = false;
inOuter = true;
return highlightBorder();
}
function allowDrop(ev) {
return false;
}
function drop(event) {
var inOuter = false;
var inInner = false;
document.getElementById("myInnerDiv").style.border ="3px solid green";
document.getElementById("myOuterDiv").style.border ="3px solid green";
return false;
}
function highlightBorder() {
if( inInner)
{
document.getElementById("myInnerDiv").style.border ="3px dotted red";
document.getElementById("myOuterDiv").style.border ="3px dotted red";
return false;
}
if(!inInner && inOuter)
{
document.getElementById("myInnerDiv").style.border ="";
document.getElementById("myOuterDiv").style.border ="3px dotted red";
return false;
}
if(!inInner && !inOuter)
{
document.getElementById("myInnerDiv").style.border ="";
document.getElementById("myOuterDiv").style.border ="";
return false;
}
}
</script>
<p>
The border of the outside div should remain red even if dragging into the green div!
</p>
</body>
</html>
You can use a counter to solve the problem.When dragenter counter++,when dragleave counter-- and check counter === 0 then do something.
// div has an child element
divEl.addEventListen("dragenter",dragEnter)
divEl.addEventListen("dragleave",dragLeave)
counter = 0
function dragEnter(){
counter++
}
function dragLeave(){
counter--
if(counter === 0){
// then do something
}
}

Allow select text on a HTML 5 draggable child element

Having a table with draggable rows where each row is draggable=true, how can the user still be able to select text from a column?
<table>
<thead>..</thead>
<tbody>
..
<tr draggable="true">
<td>..</td>
<td>Cool text but you can't select me</td>
<td>..</td>
</tr>
..
</tbody>
</table>
Another simple example (https://codepen.io/anon/pen/qjoBXV)
div {
padding: 20px;
margin: 20px;
background: #eee;
}
.all-copy p {
-webkit-user-select: all; /* Chrome all / Safari all */
-moz-user-select: all; /* Firefox all */
-ms-user-select: all; /* IE 10+ */
user-select: all; /* Likely future */
}
<div class="all-copy" draggable="true">
<p>Select me as text</p>
</div>
There are two things we need to do.
One thing is limitting the drag event only trigger on specified area, for example, the drag handle.
The other thing is that we only set the text on the div with content class can be selected. The reason why we do so is that the element that has been set to draggable, on which browser will add a default rule user-select: none.
const itemEl = document.querySelector('.item');
const handleEl = document.querySelector('.handle');
let mouseDownEl;
itemEl.onmousedown = function(evt) {
mouseDownEl = evt.target;
}
itemEl.ondragstart = function(evt) {
// only the handle div can be picked up to trigger the drag event
if (mouseDownEl.matches('.handle')) {
// ...code
} else {
evt.preventDefault();
}
}
.item {
width: 70px;
border: 1px solid black;
text-align: center;
}
.content {
border-top: 1px solid gray;
user-select: text;
}
<div class="item" draggable="true">
<div class='handle'>handle</div>
<div class='content'>content</div>
</div>
One way to make that work, is to actually check which element fired the event, e.target, against the element that has the listener attach to itself, #draggable (in this case using this).
if (e.target === this) {...}
This will allow default behavior on element positioned inside the draggable element, such as selecting a text and so on.
Note, since Firefox has issue with draggable="true", I used a different drag method.
Stack snippet
(function (elem2drag) {
var x_pos = 0, y_pos = 0, x_elem = 0, y_elem = 0;
document.querySelector('#draggable').addEventListener('mousemove', function(e) {
x_pos = e.pageX;
y_pos = e.pageY;
if (elem2drag !== null) {
elem2drag.style.left = (x_pos - x_elem) + 'px';
elem2drag.style.top = (y_pos - y_elem) + 'px';
}
})
document.querySelector('#draggable').addEventListener('mousedown', function(e) {
if (e.target === this) {
elem2drag = this;
x_elem = x_pos - elem2drag.offsetLeft;
y_elem = y_pos - elem2drag.offsetTop;
return false;
}
})
document.querySelector('#draggable').addEventListener('mouseup', function(e) {
elem2drag = null;
})
})(null);
#draggable {
display: inline-block;
background: lightgray;
padding:15px;
cursor:move;
position:relative;
}
span {
background: white;
line-height: 25px;
cursor:auto;
}
<div id="draggable">
<span>Select me as text will work<br>when the mouse is over the text</span>
</div>

how to disable dragend animation in html5

I created a draggable element by setting its draggable attribute. When I drop the element, there is an animation of the element snapping back to its origin position:
How can the snap-back animation be disabled? I tried calling preventDefault() on the dragend event, but it had no effect on the animation.
The following snippet shows the basics:
document.getElementById('test').addEventListener(
'dragend', evt => {
evt.preventDefault();
}
);
#container {
border: 1px solid black;
min-width: 300px;
min-height: 200px;
position: relative;
}
#test {
width: 50px;
height: 50px;
background-color: red;
position: absolute;
top: 25px;
left: 40px;
}
<div id="container">
<div id="test" draggable='true'></div>
</div>
Not every browser will show the dragged #test jumping back to the original position.
In order to prevent the animation, you need the drop event to fire. For the drop event to fire, you need to call preventDefault() in the handler for dragover.
document.addEventListener('dragover', function(e) { e.preventDefault() })
Example in MDN docs shows the same thing: https://developer.mozilla.org/en-US/docs/Web/Events/drop#Example
An old blog post describing the quirks of HTML5 Drag and Drop API: https://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
As was said earlier, you need to explicitly describe onDragOver handler on the parent's container (where you will drop your draggable element) and put .preventDefault() on event to prevent this animation.
Here is a simple React code example for better understanding of this mechanic (you can position the box inside the container by dragging it):
App.jsx
import './App.css'
const App = () => {
function handleDragOver(e) {
e.preventDefault()
}
function handleDrop(e) {
let box = document.getElementById('box')
if (box) {
box.style.top = e.clientY + 'px'
box.style.left = e.clientX + 'px'
}
}
return (
<div className="container" onDragOver={handleDragOver} onDrop={handleDrop}>
<div id="box" draggable></div>
</div>
)
}
export default App
App.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100vw;
height: 100vh;
position: relative;
}
#box {
width: 100px;
height: 100px;
background-color: lightgreen;
position: absolute;
}

Nested ng-click's in IE9

I need to have a ng-click-event nested into another ng-click-event.
This doesn't seem to be a problem in the Chrome client I am able to use here at work, but the standard browser is IE9.
The problem is that clicking on the inner control does not trigger the function corresponding to the inner control, but rather the function of the parent control.
The code looks a little like this:
angular.module('App', [])
.controller('Controller', function() {
var self = this;
self.outer_function = function($event) {
alert('Outer function called');
}
self.inner_function = function($event) {
$event.stopPropagation();
alert('Inner function called');
}
});
.outer {
border: 1px solid black;
padding: 10px;
display: inline-block;
}
.inner {
margin: 0 10px;
padding: 5px;
border: 1px solid red;
display: inline-block;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="App">
<div ng-controller="Controller as ctrl">
<button class="outer" data-ng-click="ctrl.outer_function($event)">
This is the div
<div class="inner" data-ng-click="ctrl.inner_function($event)">
Inner div
</div>
</button>
</div>
</div>
Is there anything I am forgetting? Or a workaround to make this work in IE9?
Thanks in advance!

Get away with built-in dragging html elements

I have a problem with html-objects that moving when I´m dragging them, you know the transparent "ghost" -copy of the element that appears when you holding down the mouse and drag it. My problem is that it interrupt my mouse event. I have a image that should be able to move inside a div when you dragging it with the mouse. I have to events for that, first one for mousedown that trigger mousemove-event, mousemove handles the movement of the image. mousedown is no problem but when I´m moving the mouse with the button down the transparent "ghost" - copy of the elements appear and interrupt my mousemove-event. Is that any one how knows how to get around or fix this thing?
Could you provide a sample?
From what it sounds like, the first thing to check is that the events are actually being hit. Writing messages out to console whenever each event fires should help prove or disprove that the events are occurring as-expected.
Then you start your mousemove event, set a timeout for the appended class 'hide' in 0 ms. Its work.
const dragAndDrop = () => {
const card = document.querySelector('.dragDrop');
const cells = document.querySelectorAll('.block');
const dragStart = function () {
// i added timeout for appended class
setTimeout(() => {
this.classList.add('hide');
}, 0);
};
const dragEnd = function () {
// i added timeout for appended class
setTimeout(() => {
this.classList.remove('hide');
}, 0);
};
const dragOver = function (event) {
event.preventDefault();
}
const dragEnter = function (event) {
event.preventDefault()
this.classList.add('hovered');
}
const dragLeave = function () {
this.classList.remove('hovered');
}
const dragDrop = function () {
this.append(card);
this.classList.remove('hovered')
}
cells.forEach((cell) => {
cell.addEventListener('dragover', dragOver);
cell.addEventListener('dragenter', dragEnter);
cell.addEventListener('dragleave', dragLeave);
cell.addEventListener('drop', dragDrop);
})
card.addEventListener('dragstart', dragStart);
card.addEventListener('dragend', dragEnd);
}
dragAndDrop()
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
width: 100%;
height: 100vh;
display: flex;
justify-content: space-around;
padding-top: 20px;
}
.block{
width: 100px;
height: 100px;
background-color: rgb(99, 99, 99);
display: flex;
justify-content: center;
align-items: center;
}
.card{
width: 80px;
height: 60px;
background-color: black;
cursor: grab;
}
.hide {
display: none;
}
.hovered {
background-color: rgb(254, 164, 164);
}
<div class="block">
<div draggable="true" class="drag dragDrop">
<div class="card"></div>
<div class="text">Hello, world</div>
</div>
</div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
I wrote this 12 years ago but if I remember right the solution was something like,
imageElement.addEventListener('dragover', function(e) {
e.preventDefault()
});
This should prevent the user from clicking and dragging on the image which means the browser won't add any visual effects.