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

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

Related

How to make sure clickable objects don't propagate to the wrong element?

Languages involved: HTML, CSS, JS
Context: I'm relatively new to web development. I have two elements overlapping each other. One is a slider, one is a div. The slider is on top of the div.
Code snippets:
<div id="myDiv">
<input id="mySlider" type="range" min=1 max=100 step=1>
</div>
and
initListeners() {
document.getElementById("myDiv").addEventListener("click", divFunction);
document.getElementById("mySlider").addEventListener("input", sliderFunction);
}
I need to make it that when you click the slider, it doesn't click the div. How would I go about doing that? I've tried z-index, but that doesn't seem to change anything.
Thanks in advance!
As I'm sure you've figured out by now, events in JavaScript by default bubble up from a child to a parent. You need to stop that from happening at the child level, also known as preventing propagation.
Using the stopPropagation function, you can handle this as follows:
function sliderFunction(e) {
e.stopPropagation();
}
Simple. That event will no longer reach the parent.
EDIT
While stop propagation is the correct method to use, event listeners must also match in type. Therefore, both the slider and the parent DIV must have click event listeners (instead of input and click). stopPropagation stops propagation of a specific type of event.
function divFunction() {
console.log('DIV clicked!');
}
function sliderFunction(event) {
event.stopPropagation();
console.log('Slider clicked!');
}
function initListeners() {
document.getElementById('myDiv').addEventListener('click', divFunction);
document.getElementById('mySlider').addEventListener('click', sliderFunction);
}
initListeners();
/* unnecessary visual aides */
body *:not(label) {
padding: 2rem;
outline: 1px solid red;
position: relative;
}
label {
display: inline-block;
position: absolute;
background: #222;
color: #fff;
top: 0; left: 0;
}
<div id="myDiv">
<label>#myDiv</label>
<div id="tools">
<label>#tools</label>
<input type="range" id="mySlider">
</div>
</div>
You can also check the target once you fire that click event. I've used this approach before:
JSFiddle: http://jsfiddle.net/L4ck7ygo/1/
function divFunction(e) {
if (e.target !== this) {
return;
} else {
console.log('hit');
}
}
When the fiddle first loads, click the slider and you'll see the console log out some text. To see it work, remove the line that is being pointed to and rerun the fiddle. Now when you click the slider, you won't see anything logged in the console, but if you click on the div and not the slider, it will log to the console.
function initListeners() {
document.getElementById("myDiv").addEventListener("click", divFunction);
document.getElementById("mySlider").addEventListener("input", sliderFunction);
}
initListeners();
function divFunction(e) {
console.log('Firing...') // <-- This will log on any click
if (e.target !== this) {
return;
} else {
console.log('hit'); // <-- This will NOT log except for div click
}
}
function sliderFunction() {
console.log('Doing stuffs...');
}
<div id="myDiv">
<input id="mySlider" type="range" min=1 max=100 step=1>
</div>
UPDATE: Stupidity on my part. I had the ordering wrong for the elements which caused propagation to not act as intended.

calling different functions from inside and outside of div

I am having a div inside a div. And I want to call a function on the click of outer div and another function on the click of inner div. Is it possible to do so?
<div onclick="function1()">
<div onclick=function2()></div>
</div>
Yes, this is very much possible. And the code you have will get the job done.
NOTE: You need to add event.stopPropagation() in case you want to prevent the bubbling of the event from the inner function.
Try this out:
function function1() {
console.log("From outer div");
}
function function2(event) {
console.log("From inner div");
event.stopPropagation();
}
#outer-div {
width: 100px;
height: 100px;
background: yellow;
}
#inner-div {
width: 50px;
height: 50px;
background: red;
position: relative;
top: 25px;
left: 25px;
}
<div id="outer-div" onclick="function1()">
<div id="inner-div" onclick="function2(event)"></div>
</div>
Yes, it is; one way to do it is the way you've done it in your question, except:
You need quotes around the inner onclick attribute value, just as you have around the outer onclick attribute value.
You probably want to pass event into at least the inner one:
<div onclick="function2(event)"></div>
and then have it call stopPropagation on that:
function function2(event) {
event.stopPropagation();
}
so that the click event isn't propagated to the parent (doesn't bubble any further). If the click bubbles, function1 will be called as well.
Example:
function function1() {
console.log("function1 called");
}
function function2(event) {
event.stopPropagation();
console.log("function2 called");
}
<div onclick="function1()">
<div onclick="function2(event)">this div fires function2</div>
clicking here will fire function1
</div>
You might also consider modern event handling rather than onxyz-attribute-style event handlers; search for examples of addEventListener for details; my answer here also has a useful workaround for obsolete browsers.

Click event not emitted when mousedown/mouseup do add/removeClass on element

I'm attaching mousedown, mouseup and click handlers to an element. On mousedown I add a class to the element, on mouseup I remove the class, and on click I do some work. (This is a simplification of the context. In my project the click event is handled by a 3rd party component.)
The problem I'm having is that the click event is never emitted in Safari and Firefox, but it works just fine in Chrome. (I don't know what IE does. I don't have access to it, and don't care about it.)
The code is as follows:
HTML:
<div id="clickme">
<div class="normal"></div>
<div class="highlight"></div>
</div>
<input type="text" id="textinput"/>
CSS:
#clickme:not(.active) > .highlight {
display: none;
}
#clickme.active > .normal {
display: none;
}
.normal, .highlight {
width: 100px;
height: 100px;
}
.normal {
background: blue;
}
.highlight {
background: red;
}
JS:
var clickme = $('#clickme');
var textinput = $('#textinput');
clickme.on('mousedown', function(e) {
clickme.addClass('active');
// ^-- comment this out and the click event starts working
});
clickme.on('mouseup', function(e) {
clickme.removeClass('active');
// ^-- comment this out and the click event starts working after the second click
});
clickme.on('click', function(e) {
e.preventDefault();
textinput.val(Date.now());
});
JSFiddle: https://jsfiddle.net/xLskk3po/14/
JSFiddle without JQuery: https://jsfiddle.net/xLskk3po/15/ It shows that it's not a JQuery problem.
I stumbled upon this SO question: When a mousedown and mouseup event don't equal a click and it looks like my issue is similar to that. So I did something silly: I put a transparent, absolutely positioned element on top.
HTML:
<div id="clickme">
<div class="normal"></div>
<div class="highlight"></div>
<div class="abs"></div> <!-- this is the absolute element, covering #clickme -->
</div>
<input type="text" id="textinput"/>
That fixed it.

Prevent custom event from executing

I want to prevent custom event on parent when child is clicked. Note that I don't have access to the code of parent event. I've tried doing e.preventDefault() on the button itself but it doesn't help.
Is there any way of ignoring all parent events when something inside of it is clicked?
$(function(){
// Note that this is just an example, I don't have access to this code
// This is some custom event inside custom plugin
$('.container').on('click', function() {
alert('This should be alerted only if you click on green box');
});
$('.btn').on('click', function() {
// Here I want to make sure that *parent* events are not triggered.
alert('Button is triggered, green box should be not triggered');
});
});
.container {
width: 300px;
height: 200px;
background: green;
padding-top: 100px;
}
.btn {
width: 100px;
height: 100px;
display: block;
margin: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<button class="btn">Click Me</button>
</div>
Since you're using jQuery, you can use the event.stopPropagation() method. The event.stopPropagation() method stops the bubbling of an event to parent elements, preventing any parent event handlers from being executed. You can see it in action here
$(document).ready(function () {
$("#button").click(function (event) {
alert("This is the button.");
// Comment the following to see the difference
event.stopPropagation();
});
$("#outerdiv").click(function (event) {
alert("This is the outer div.");
});
});
In this simple example, if you click on the button, the event is handled by its own handler and it won't bubble up the DOM hierarchy. You can add a very simple handler calling event.stopPropagation() on the button and it won't bubble up. No need to mess with the parent's JS.

Javascript addClass removeClass not working as desired

When I click on the icon, I want it to be switched on/off.
In my html file I have:
<div class="startSharing"></div>
And in my js:
$('.startSharing').click(function() {
$(this).removeClass('startSharing');
$(this).addClass('stopSharing');
});
$('.stopSharing').click(function() {
$(this).removeClass('stopSharing');
$(this).addClass('startSharing');
});
It works fine when switching from on to off - but I don't understand why it still goes into "startSharing" section when it is turned off.
Thank you for help.
In this case, you're setting the handlers to the particular elements. Once they're set - they're set, regardless of the selector changing.
You could just define it on the .startSharing element, if they all start like that. Then, you could use .toggleClass() to easily switch them on/off:
$('.startSharing').click(function() {
$(this).toggleClass('startSharing stopSharing');
});
You may also delegate the click event like this:
$('body').on('click', '.startSharing', function() {
$(this).removeClass('startSharing').addClass('stopSharing');
});
$('body').on('click', '.stopSharing', function() {
$(this).removeClass('stopSharing').addClass('startSharing');
});
Doing it this way will also allow dynamically-generated to trigger events. In your case, .stopSharing was generated dynamically.
use event delegation
$('#container').on('click',".startSharing,.stopSharing",function(){
$(this).toggleClass('startSharing').toggleClass('stopSharing');
});
#container div {
width: 250px;
height: 100px;
}
.startSharing {
background-color: green;
}
.startSharing::after {
content: "start sharing";
}
.stopSharing {
background-color: red;
}
.stopSharing::after {
content: "stop sharing";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<div class="startSharing"></div>
</div>

Categories