Javascript drag and drop - do you have to use dragover? - javascript

I'm trying to figure out how to properly do drag and drop, and from what I read on MDN, it sounds like i should be able to indicate via dragenter that a drop is possible on that element. I simply haven't been able to get it to work, but when I switch to drag over, it seems to start working. I don't really want to register a dragover event handler tho, because I don't need that to be called every time the mouse moves inside an area. Is there a way to completely avoid registering a dragover handler for drag and drop?
This is what I have working with dragover:
<div id="main">
<div id="a" draggable="true">a</div>
<div id="c">b</div>
</div>
<style>
div {
display: inline-block;
border: 1px solid black;
}
#c {
height: 100px;
width: 100px;
}
</style>
<script type='text/javascript'>
var a = document.getElementById('a')
var c = document.getElementById('c')
a.addEventListener('dragstart', function(event) {
var dt = event.dataTransfer;
dt.setData("object/jsObject", {test:1});
dt.setData("text/plain", "test one");
})
c.addEventListener('dragover', function(event) {
if(event.preventDefault){
event.preventDefault();
}
console.log(event)
})
c.addEventListener('drop', function(event) {
console.log(event)
c.innerHTML = 'dropped '+event.dataTransfer.getData(event.dataTransfer.types[0])
event.dataTransfer.dropEffect = 'move';
})
</script>
Is there any way to replace dragover with dragenter?
UPDATE:
Oh here's something interesting, apparently the HTML5 drag and drop api is absolute shite. I was beginning to come to that conclusion myself.

Related

How to get "Pointermove" delegation to work in Safari iOS on parent/child?

I've run into a problem and haven't been able to find a workaround yet. I'm trying to use an event delegate with "pointermove" on a parent container and I want to know when the event crosses from a child to the parent container and vice versa.
This works well on desktop browsers, but when I try in Safari iOS it seems like the event target gets "stuck" on whatever first started the pointermove. When the pointermove crosses to the parent/child boundary the target doesn't change. Any ideas?
Example code:
const outer = document.getElementById("outer");
outer.addEventListener("pointermove", (e) => console.log(e.target.id))
body {
touch-action: none;
}
#outer {
height: 500px;
width: 500px;
background-color: #AAAAFF;
}
#inner {
height: 200px;
width: 200px;
background-color: #AAFFAA;
}
<div id="outer">
<div id="inner">
</div>
</div>
Looks like this has been an issue for a long time. Touchmove works the same way as Pointermove which is why I wasn't seeing results for this question. Here's another stack overflow post with the workaround which is to use document.elementFromPoint like e.g.:
const outer = document.getElementById("outer");
outer.addEventListener("pointermove", (e) => {
actualTarget = document.elementFromPoint(e.clientX, e.clientY);
console.log(actualTarget.id);
})

Can't preventDefault() on drop event on a div with Svelte

I'm trying to implement a file dropper on a <div> as a Svelte component. I've tried every combination of preventDefault but the browser still loads the dropped file instead of passing it to the component.
<script>
function handleDrop(event) {
event.preventDefault();
console.log("onDrop");
}
function handleDragover(event) {
console.log("dragOver");
}
</script>
<style>
.dropzone {
display: block;
width: 100vw;
height: 300px;
background-color: #555;
}
</style>
<div class="dropzone" on:drop|preventDefault={handleDrop}
on:dragover|once|preventDefault={handleDragover}></div>
I've tried with and without event.preventDefault(); in handler functions. Also tried with on:dragenter event and different combinations of modifiers, i.e. with stopPropagation. The browser still opens the dropped file. What am I doing wrong? Thanks!
(UPDATE) FIX:
Okay, the culprit was the |once modifier. Once removed from the on:dragover in <div> everything works great, except that dragover event fires continuously while dragging across the div. event.preventDefault(); inside handler functions is not needed as the |preventDefault modifier works correctly. Here is the code (omitting <style> for brevity):
<script>
function handleDrop(event) {
console.log("onDrop");
}
function handleDragover(event) {
console.log("onDragOver");
}
</script>
<div class="dropzone" on:drop|preventDefault={handleDrop}
on:dragover|preventDefault={handleDragover}></div>
Not submitting this as an answer yet, because I would like to find out why I can't use |once modifier for dragover event, which would be useful for my app. Thanks!
Problem:
This is a common gotcha rooted in HTML drag-and-drop (not Svelte's fault), where the last dragover event must be canceled in order to cancel drop. Looking at Svelte's once directive, it's just a closure that runs your handler one time. However, dragover will fire multiple times before being dropped, so the immediately preceding dragover is not prevented.
Solution:
Just include the directive without a handler:
<div
on:dragover|preventDefault
on:drop|preventDefault={handler}
>
<style>
.dropzone {
display: block;
width: 100vw;
height: 300px;
background-color: #555;
}
</style>
<div class="dropzone" on:drop={event => handleDrop(event)}
on:dragover={handleDragover}>
</div>
<script>
export function handleDragover (ev) {
ev.preventDefault();
console.log("dragOver");
}
export function handleDrop (ev) {
ev.preventDefault();
console.log("onDrop");
}
</script>
Look here: https://svelte.dev/repl/3721cbc9490a4c51b07068944a36a40d?version=3.4.2
https://v2.svelte.dev/repl?version=2.9.10&gist=8a9b145a738530b20d0c3ba138512289

Why are my events on button fired after events on parent elements?

Intro
I am extending photoswipe with my own button & modal dialog, similar to built in share dialog.
I already made code that worked, but then followed these modifications to photoswipe:
https://github.com/dimsemenov/PhotoSwipe/issues/1209
Now it doesn't work anymore. Issue is that photoswipe's event handlers get called before mine, so it appears as if user clicked on photoswipe controls and photoswipe hides image, controls & everything and only my modal is visible.
Diagnostics
I have modified onControlsTap and onGlobalTap and my button click to log to console and I see they are fired in this order:
onControlsTap
onGlobalTap
Settings button click
Html on the other hand looks like this:
<div id="globalTapContainer">
<div id="controlTapContainer">
<button id="myButton"></button>
</div>
</div>
And events are registered using addEventListener(..., false)
Code
This is my code which binds to click event
$("#pswp__settings__dropdown_background, .pswp__button--settings")
.click(function(ev) {
console.log('Settings button click');
ev.stopPropagation();
toggleSettings();
});
This is photoswipe code that binds events.
_controls = framework.getChildByClass(pswp.scrollWrap, 'pswp__ui');
// ...
framework.bind(_controls, 'pswpTap click', _onControlsTap);
framework.bind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
var framework = {
// ...
bind: function(target, type, listener, unbind) {
var methodName = (unbind ? 'remove' : 'add') + 'EventListener';
type = type.split(' ');
for(var i = 0; i < type.length; i++) {
if(type[i]) {
target[methodName]( type[i], listener, false);
}
}
}
}
My button and modal are one of child nodes of pswp__ui.
Question
How is it possible that their events are called before mine when I have registered click event to a specific button?
What to do to make photoswipe events not fire when you click on my controls?
I'm not familiar with photoswipe, but its events use a custom event called pswpTap, not click. Presumably this fires when an element is tapped or when the mouse button is pressed. click events don't fire until the mouse button is released, so that would explain why their events are firing before yours.
Example:
$('#outerdiv').on('mousedown', function() {
console.log('outer mousedown');
});
$('#innerdiv').on('click', function() {
console.log('inner click');
});
#outerdiv {
width: 100px;
height: 100px;
background-color: blue;
}
#innerdiv {
width: 40px;
height: 40px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outerdiv">
<div id="innerdiv"></div>
</div>
You should presumably be able to prevent this by having your element handle and cancel the mousedown event. You may also need to add an event handler for tap events if they work differently from mousedown (I'm not sure whether they are).
$('#outerdiv').on('mousedown', function() {
console.log('outer mousedown');
});
$('#innerdiv').on('mousedown', function(event) {
console.log('inner mousedown');
event.stopPropagation();
});
$('#innerdiv').on('click', function() {
console.log('inner click');
});
#outerdiv {
width: 100px;
height: 100px;
background-color: blue;
}
#innerdiv {
width: 40px;
height: 40px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outerdiv">
<div id="innerdiv"></div>
</div>

How can I make the parent div draggable

I have a div containing three buttons. The div needs to be draggable, so that you can drag all three buttons around the screen together. That works fine, but the problem is that when I click on of the individual buttons it inherits the draggable id and it is draggable on it's own. I do not want that to happen. So my question is: how do I make my buttons draggable, but make them always stay together and keep them clickable. I added the code below, but here is a JSFiddle: http://jsfiddle.net/2ga50vvt/
So to be clear: the div also needs to be draggable through dragging one of the individual buttons, but then the rest of the div needs to stick with it. Now dragging an individual button only moves the button.
P.S. I do not want to use JQuery UI
HTML:
<div id="draggable" class="ui-widget-content">
<button ng-click="menu.shown = !menu.shown">MENU</button>
<br>
<button ng-click="disconnect()">CLOSE</button>
<br>
<button ng-click="">KEYS</button>
</div>
JS
$(document).ready(function() {
var $dragging = null;
$('body').on("mousedown", "#draggable", function(e) {
$(this).attr('unselectable', 'on').addClass('dragged');
var el_w = $('.dragged').outerWidth(),
el_h = $('.dragged').outerHeight();
$('body').on("mousemove", function(e) {
if ($dragging) {
$dragging.offset({
top: e.pageY - el_h / 2,
left: e.pageX - el_w / 2
});
}
});
$dragging = $(e.target);
}).on("mouseup", ".dragged", function(e) {
$dragging = null;
$(this).removeAttr('unselectable').removeClass('dragged');
});
});
CSS:
body {
padding: 50px;
}
.dragged {
background-color: yellow;
}
#draggable {
position: fixed;
width: 150px;
height 150px;
padding: 0.5em;
background: red;
background-color: black;
z-index: 1000;
cursor: move;
float: left;
}
Update 1
This is a working solution: http://jsfiddle.net/2ga50vvt/3/
However when I click on the div and start dragging the center of the div jumps to my cursor. It works great, but it looks a bit wonky. Is there a way to prevent the div from moving to my cursor?
Your help is most welcome.
You can read the target property of the event and return false to avoid all not #draggable to be draggable.
if(e.target.id !== "draggable") {
return false;
}
The edited fiddle:
http://jsfiddle.net/2ga50vvt/1/
It works perfectly, but one suggestion: don't target with ids because with this code you can't drag more of one element (ids must be unique), so the workaround is to write an attribute or a classname and play with it.
Good luck.
Use $dragging = $('#draggable'); instead of $dragging = $('e.target');
It will drag div if you try to drag using cursor on button. It will drag #draggable instead of target.
Working Fiddle
Presuming you're opposed to JQueryUI for it's file size, I'd still recommend a prebuilt solution because why reinvent the wheel?
Draggabilly is a really nifty library that I've used when resource size has been an issue. It's 20k minified (obviously even smaller gzipped) and available on a CDN - which in itself has lots of benefits e.g. caching.
$(function() {
$( "#draggable" ).draggabilly();
});
There's a few CSS hooks, different options, events etc.
JSFiddle here

How to change image side of cursor on drag event using JQuery

When you drag an file on browser screen, an image appear side of mouse cursor that is windows default image. This images is various like Copy, Move and Forbide. See its at bottom.
How can i change image side of mouse cursor to this images using javascript or JQuery? For example when i drag a file and move mouse in undragable area, forbiden image display side of cursor.
You can use the dataTransfer.dropEffect property of the dragover event to set the small image besides the cursor:
$(".targetDiv").on("dragover", function (event) {
event.preventDefault();
event.originalEvent.dataTransfer.dropEffect = "none"; // Shows the "forbidden" image
});
The values for that property are copy, move, link and none. You can test these values in the code snippet below. Please note that the originalEvent must be used. According to my tests, it works in Firefox and Chrome but not in IE.
$(function () {
$(".targetDiv").on("dragover", function (event) {
event.preventDefault();
event.originalEvent.dataTransfer.dropEffect = event.target.getAttribute("data-effect");
});
});
.targetDiv
{
display: inline-block;
border: solid 1px black;
width: 80px;
height: 50px;
margin: 4px;
text-align: center;
line-height: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Drag a file over each block </p>
<div>
<div data-effect="link" class="targetDiv">Link</div>
<div data-effect="move" class="targetDiv">Move</div>
</div>
<div>
<div data-effect="copy" class="targetDiv">Copy</div>
<div data-effect="none" class="targetDiv">None</div>
</div>
You can change the cursor image by changing the property of the cursor by css using jquery.
function ondrag(event) {
$('body').css('cursor', 'wait');
}
You can check the various cursor property here.
http://www.w3schools.com/cssref/pr_class_cursor.asp
If you want to replace the cursor with a custom image you can use this:
https://github.com/Webbrother/jquery.change-cursor
If you want to limit draggables to a certain area,
Try using "containment" option:
http://docs.jquery.com/UI/Draggable#option-containment
You can do it with jquery draggable
Here is the preview what i have done
$( ".your_image" ).draggable({
drag: function() {
$(".your_image").css("cursor","url(https://cdn1.iconfinder.com/data/icons/CrystalClear/16x16/actions/move.png), auto");
},
stop: function() {
$(".your_image").css("cursor","url(https://cdn4.iconfinder.com/data/icons/32x32-free-design-icons/32/Copy.png), auto");
}
});
.your_image{
height:100px;
width:100px;
background-color:red;
cursor:url(https://cdn4.iconfinder.com/data/icons/32x32-free-design-icons/32/Copy.png), auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div>
<div class="your_image">
</div>
</div>
<div class="log">
</div>

Categories