File input triggers change event twice (or more) - javascript

I have a basic input[file] element which I hide. When you click on the #holder a file explorer pops up. But selecting a file triggers the console.log() line to be executed twice (on my computer).
Beware: the following code below crashes my Chrome tab.
You should better run it as a separate file. Cannot provide a "working" demo, but this is the closest I can get to MCVE.
var element = document.getElementById('holder');
element.onclick = function(e) {
var input = document.getElementById('file-input');
input.click();
input.addEventListener("change", function(evt) {
console.log(evt);
Phimij.addFiles(input.files);
}, false);
};
#holder {
border: 10px dashed #ccc;
width: 300px;
height: 300px;
margin: 20px auto;
}
#holder.hover {
border: 10px dashed #333;
}
#file-input {
display: none;
}
<div id="holder">
<input type="file" multiple id="file-input" />
</div>

click events bubble up the ancestry tree. That means a click on your input will bubble up to your #holder element and fire your click handler on it. In your click handler on #holder, you fire the click event on the input. That's why your browser crashes: You've triggered an infinite loop.
The solution is to hook click on the input and tell it not to bubble (propagate); see flagged lines (but keep reading, further notes below):
var element = document.getElementById('holder');
// **** Added vvvv
document.getElementById('file-input').addEventListener("click", function(evt) {
evt.stopPropagation();
}, false);
// *** Added ^^^^
element.onclick = function(e) {
var input = document.getElementById('file-input');
input.click();
input.addEventListener("change", function(evt) {
console.log(evt);
// Phimij.addFiles(input.files);
}, false);
};
#holder {
border: 10px dashed #ccc;
width: 300px;
height: 300px;
margin: 20px auto;
}
#holder.hover {
border: 10px dashed #333;
}
#file-input {
display: none;
}
<div id="holder">
<input type="file" multiple id="file-input" />
</div>
There are a few other things I'd change. You're adding a change handler to the input every time there's a click on #holder; you really only want to do that once. I'd also add that handler before triggering the click.
So for what it's worth, some changes I'd make:
var element = document.getElementById('holder');
var input = document.getElementById('file-input');
element.addEventListener("click", function() {
input.click();
}, false);
input.addEventListener("click", function(evt) {
evt.stopPropagation();
}, false);
input.addEventListener("change", function(evt) {
console.log(evt);
// Phimij.addFiles(input.files);
}, false);
#holder {
border: 10px dashed #ccc;
width: 300px;
height: 300px;
margin: 20px auto;
}
#holder.hover {
border: 10px dashed #333;
}
#file-input {
display: none;
}
<div id="holder">
<input type="file" multiple id="file-input" />
</div>

Related

Javascript: I added an event listener to all button in every class but some button won't work

I just started studying JS and I'm currently simulating something that will apply to my project
Basically, I'm trying to generate new Divs with Button on it in order to do something.
And I applied the for loop on the button from the guide here
Turns out it works! but there's a bug where some buttons wont work whenever I generate more divs+button and I don't know why?
const btnCreate = document.querySelector(".myButton");
const changeBtn = document.getElementsByClassName("changeBtnStyle");
const newNewDiv = document.getElementsByClassName("newCDiv");
const createFunc = function() {
const parentDiv = document.querySelector(".parentDiv");
const newDiv = document.createElement("div");
const newChangeBtn = document.createElement("button");
parentDiv.appendChild(newDiv);
newDiv.appendChild(newChangeBtn);
newDiv.classList.add("newCDiv");
newChangeBtn.classList.add("changeBtnStyle")
newChangeBtn.innerHTML = "change"
for (let i = 0; i < changeBtn.length; i++) {
changeBtn[i].addEventListener("click", function() {
newNewDiv[i].classList.toggle("changeColor");
}, false);
}
};
btnCreate.addEventListener("click", createFunc);
.parentDiv {
margin: auto;
width: 300px;
height: 300px;
background-color: gray;
position: relative;
}
.newCDiv {
background: green;
width: 50px;
height: 50px;
position: relative;
}
.changeBtnStyle {
position: absolute;
}
.changeColor {
background: red;
}
.myButton {
margin: auto;
}
<button class="myButton">
Create Div w/ Button
</button>
<div class="parentDiv">
</div>
Here's the JSFiddle one
Every time you click on a generated button the for loop will add an event listener for each button. Even the ones that already have an event listener attached to them. So by doing that and then toggling the class, you call the classList.toggle() function more than once.
For example with 1 event listener, the toggle works fine. But with 2 event listeners you toggle and toggle again, resulting in an immediate on / off switch. 3 event listeners will toggle 3 times, on / off / on, having the right resulting but not working correctly.
So instead of looping each button again, just add the event listener only to the element that you've created in the createFunc function.
const btnCreate = document.querySelector(".myButton");
const changeBtn = document.getElementsByClassName("changeBtnStyle");
const newNewDiv = document.getElementsByClassName("newCDiv");
const createFunc = function() {
const parentDiv = document.querySelector(".parentDiv");
const newDiv = document.createElement("div");
const newChangeBtn = document.createElement("button");
parentDiv.appendChild(newDiv);
newDiv.appendChild(newChangeBtn);
newDiv.classList.add("newCDiv");
newChangeBtn.classList.add("changeBtnStyle")
newChangeBtn.innerHTML = "change"
newChangeBtn.addEventListener("click", function() {
newDiv.classList.toggle("changeColor");
}, false);
};
btnCreate.addEventListener("click", createFunc);
.parentDiv {
margin: auto;
width: 300px;
height: 300px;
background-color: gray;
position: relative;
}
.newCDiv {
background: green;
width: 50px;
height: 50px;
position: relative;
}
.changeBtnStyle {
position: absolute;
}
.changeColor {
background: red;
}
.myButton {
margin: auto;
}
<button class="myButton">Create Div w/ Button</button>
<div class="parentDiv"></div>

Why does a mouse right click fire the "pointerdown" event when pressed down but not the "pointerup" event when released?

I'm using pointerevents rather than mouse events to be able to have a universal pointer solution, rather than having to separately consider touch vs mouse events etc.
The problem is that when I press the right mouse button, the pointerdown event is fired normally (as expected), but when releasing it, the pointerup event is not fired (unexpectedly).
I've created a minimum reproducible case here:
https://codesandbox.io/s/proud-smoke-1x2w5?file=/src/index.js
And I've created a video of the issue here:
https://app.usebubbles.com/6a21646e-13d2-4a7f-a598-dfad35a9c0d3
Why does a mouse right click fire the "pointerdown" event when pressed down but not the "pointerup" event when released?
Note that this is in Chrome 81 (https://www.whatsmybrowser.org/b/VJUHP)
It appears to have to do with the "contextmenu", check the snippet below, if you prevent the default behaviour then the "pointerup" event is triggered on the right click.
const app = document.getElementById("app");
const count = document.getElementById("count");
const writeCount = (n) => (count.innerHTML = n);
noContextMenu.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
let n = 0;
writeCount(0);
app.addEventListener("pointerdown", (e) => {
writeCount(++n);
eventType.innerHTML = "pointerdown";
});
app.addEventListener("pointerup", (e) => {
writeCount(--n);
eventType.innerHTML = "pointerup";
});
body {
font-family: sans-serif;
}
#contextmenu {
margin: 10px;
padding: 20px;
width: 100px;
float: left;
border: 1px solid blue;
}
#noContextMenu {
margin: 10px;
padding: 20px;
width: 100px;
float: left;
border: 1px solid darkviolet;
}
<div id="app">
<div>
Event type: (<span id="count"></span>) <span id="eventType"></span>
</div>
<div id="contextmenu">context menu</div>
<div id="noContextMenu">noContextMenu</div>
</div>

How can I enable draggable when mouse is already down on element and already moved?

I have code written to allow an HTML element to be dragged after the mouse has been down over that element for a certain period of time.
The problem is that when I am using native HTML drag and drop, and I enable the draggable property when this timeout is up (the mouse has been down on that element for that period of time), if the mouse had been moved while it was down before that timeout was up, HTML will not trigger a dragstart event or even start dragging the element.
There is an example below.
var t;
function startDelayedDrag() {
clearTimeout(t);
document.getElementById('dragtarget').draggable = false;
console.log('mousedown')
t = setTimeout(function() {
console.log('dragging enabled')
document.getElementById('dragtarget').draggable = true;
}, 1000);
}
.droptarget {
float: left;
width: 100px;
height: 35px;
margin: 15px;
padding: 10px;
border: 1px solid #aaaaaa;
user-select: none;
}
<div class="droptarget">
<p onmousedown="startDelayedDrag()" id="dragtarget">Drag me!</p>
</div>
<div class="droptarget"></div>
This one is tricky and it might be different from what you had in mind, but here is goes an idea how to solve your issue:
Start the drag event
Hide the drag object by setting an image using setDragImage
Clone the drag element node, hide the clone and add it to the document (since it's not possible to change the image set by setDragImage)
Start the timeout to change the visibility of the ghost element
This could be yet improved in many ways, but I think you can get the mechanics of how it works as it is. As a reference see the following snippet:
const [$drag] = document.getElementsByClassName('drag')
const [$pixel] = document.getElementsByClassName('pixel')
let $ghost = null
$drag.addEventListener("dragstart", e => {
// set the current draged element invisible
e.dataTransfer.setDragImage($pixel, 0, 0)
// create a ghost element
$ghost = $drag.cloneNode(true)
$ghost.style.position = "absolute"
$ghost.style.display = "none"
document.body.appendChild($ghost)
setTimeout(() => {
$ghost.style.display = 'block'
}, 1000)
})
$drag.addEventListener("drag", e => {
// keep the ghost position to follow the mouse while dragging
$ghost.style.left = `${e.clientX}px`
$ghost.style.top = `${e.clientY}px`
}, false);
$drag.addEventListener("dragend", e => {
// remove the ghost
if ($ghost.parentNode) $ghost.parentNode.removeChild($ghost)
}, false)
.content {
display: flex;
}
.box {
width: 100px;
height: 35px;
padding: 10px;
margin: 10px;
border: 1px solid #aaaaaa;
}
.drop {
user-select: none;
}
.drag {
text-align: center;
}
.pixel {
width: 1px;
height: 1px;
background-color: white;
}
<div class="content">
<div draggable="true" class="drag box">Drag</div>
<div class="drop box"></div>
<div class="pixel"></div>
</div>

Fire mouseup event only for outer div

I have an HTML element (form element - textbox) inside another series of divs.
I created listener for mouseup event on the outer div, but it fires when I click on the textbox.
I thought it would only fire when mouseup on the outer div since I attached the listener to that element - not to the textbox
Is there a way to prevent it from firing when 'mouseup' fires on the textbox?
require([
"dijit/form/TextBox",
"dojo/on",
"dojo/dom"
], function (
TextBox,
on,
dom) {
var box = new TextBox({
name: "any",
value: "",
placeholder: "Type anything here"
}, "textBox");
var canvas = dom.byId("outerDiv");
on(canvas, "mouseup", function (e) {
alert();
});
});
dojo.require("dijit.form.anyText");
var textBox = dijit.byId("textBox");
console.log( "--- >> "+textBox.get("value"));
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.12.1/dojo/dojo.js"></script>
<Div id="outerDiv" style="width: 3in; height: 3in; border: 1px solid;border-color:black; cursor: pointer;">
<Div id="innerDiv" style="height: auto; border: 1px solid;border-color:blue;">
<div id="textBox" readonly></div>
</Div>
</Div>
When you click the text box, the event "bubbles up" to the outer elements.
JavaScript's Event.stopPropagation() "prevents further propagation of the current event in the capturing and bubbling phases".
Below is an example in pure JavaScript, but see also dojo.stopEvent.
var outerDiv = document.getElementById('outerDiv');
var textBox = document.getElementById('textBox');
outerDiv.addEventListener('mouseup', function(event) {
console.log('mouse up on outer div');
});
textBox.addEventListener('mouseup', function(event) {
event.stopPropagation();
console.log('mouse up on text box');
});
#outerDiv {
width: 3in;
height: 3in;
border: 1px solid;
border-color: black;
cursor: pointer;
}
#innerDiv {
height: auto;
border: 1px solid;
border-color: blue;
}
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.12.1/dojo/dojo.js"></script>
<Div id="outerDiv">
<Div id="innerDiv">
<div id="textBox" readonly>textbox</div>
</Div>
</Div>
Edit
Here's a Dojo example:
require([
"dijit/form/TextBox",
"dojo/on",
"dojo/dom"
], function(
TextBox,
on,
dom) {
var box = new TextBox({
name: "any",
value: "",
placeholder: "Type anything here"
}, "textBox");
var canvas = dom.byId("outerDiv");
on(box, "mouseup", function(e) {
console.log('mouse up on text box');
dojo.stopEvent(e);
});
on(canvas, "mouseup", function(e) {
console.log('mouse up on outer div');
});
});
#outerDiv {
width: 3in;
height: 3in;
border: 1px solid;
border-color: black;
cursor: pointer;
}
#innerDiv {
height: auto;
border: 1px solid;
border-color: blue;
}
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.10.4/dojo/dojo.js"></script>
<Div id="outerDiv">
<Div id="innerDiv">
<div id="textBox" readonly>textbox</div>
</Div>
</Div>
You can check the id before creating the alert
on(canvas, "mouseup", function (e) {
if (e.target.id == "outerDiv") {
alert();
}
});
JS Fiddle

mouseenter and mouseleave in javascript not working

I am having issue with the mouseenter and the mouseleave event in javascript. The strange thing is that the code works if you substitute these 2 events with click or dblclick events. Hope you can help me here.
PS: I'm using chrome.
don't know how to make js work on fiddle... for now
here's the code:
https://jsfiddle.net/frempong69/t7du0kte/
(function() {
window.onload = function() {
var box = document.getElementsByClassName("box")[0];
var change = function() {
box.style.backgroundColor = "green";
};
var normal = function() {
box.style.backgroundColor = "blue";
}
addEventListener("click", change, false);
addEventListener("mouseleave", normal, false);
};
}());
You are adding the mouseleave/mouseenter handlers to the window object. The click handler works because it bubbles to the window object, but the mouseenter and mouseleave events doesn't bubble so the listeners attached to the window object won't get triggered
You need add the listerns to the box element
(function() {
window.onload = function() {
var box = document.getElementsByClassName("box")[0];
var change = function() {
box.style.backgroundColor = "green";
};
var normal = function() {
box.style.backgroundColor = "blue";
}
box.addEventListener("mouseenter", change, false);
box.addEventListener("mouseleave", normal, false);
};
}());
.box {
background-color: red;
width: 400px;
height: 200px;
margin: 50px auto;
position: relative;
}
.box:after {
content: " ";
width: 0px;
height: 0px;
border-top: 100px solid transparent;
border-right: 100px solid transparent;
border-bottom: 100px solid transparent;
border-left: 100px solid red;
position: absolute;
left: 100%;
top: 50%;
margin-top: -100px
}
<div class="box">
</div>
You can simply do like this
box.onmouseenter = change;
box.mouseleave = normal;
You must change
addEventListener("click", change, false);
addEventListener("mouseleave", normal, false);
with this
box.addEventListener("click", change, false);
box.addEventListener("mouseout", normal, false);
you just use this
<div class="box" onmouseover="style.background='green'" onmouseout="style.background='red'">
</div>
its work

Categories

Resources