JavaScript hide on click outside triggers twice from the same click - javascript

I have this code:
Full Code:
https://codepen.io/anon/pen/wmLBxW
JavaScript code:
function HideOnClickOutside(aContentElement, aFullScreenElement) {
const outsideClickListener = event => {
event.preventDefault();
let isClickInside = aContentElement.contains(event.target);
if (!isClickInside) {
aFullScreenElement.classList.toggle("hidden", true);
removeClickListener();
}
};
const removeClickListener = () => {
document.removeEventListener("click", outsideClickListener);
};
document.addEventListener("click", outsideClickListener);
}
CSS
#fullscreen {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow: auto;
background: black;
opacity: 0.5;
}
.hidden {
display: none;
}
#journal-show-entry {
display: flex;
justify-content: center;
width: 40%;
height: 60%;
border: 1px solid #9b6400;
background-color: #ffffff;
}
HTML
Link
<div id="fullscreen" class="hidden">
<div id="journal-show-entry">
</div>
</div>
Which I found in this thread:
How do I detect a click outside an element?
However just like in my CodePen, it triggers the outsideClickListener on the same click that adds the EventListener making the div hide itself again on the same click and therefore never shows.
Why is this happening?

What is happening is that the event is propagating onto the next layer (the hidden div)
Adding this will fix your problem:
function ShowFullScreenDiv(event) {
event.stopPropagation(); // <-- add this
divFullScreen.classList.toggle("hidden", false);
HideOnClickOutside(divEntry, divFullScreen);
}
And ofcourse add the event to the html:
Link

Related

Why I need 2 requestAnimationFrame invokes to wait the dom rendered

I'm trying to locate which element newly rendered is under mouse pointer. (*)
Here is my code:
btn.addEventListener('click', function () {
btn.remove();
for (let i = 0; i < 10; i++) {
lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
}
requestAnimationFrame(function () { requestAnimationFrame(function () {
const chosen = document.querySelector('li:hover');
alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
}); });
});
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }
<button id=btn>Click Me</button>
<ul id=lst><ul>
I'm confused that I need 2 requestAnimationFrame to make my code execute correctly. Removing one raf, the alert will show null instead.
The code also seems ugly to me. How to implement it more elegantly?
In case anyone care about: I'm running my code on Firefox. And the code, as a part of my Firefox extension, only need to target to Firefox 60+.
(*): The story behind may be more complex. But to keep it simple...
That's quite an interesting behavior you found here, browsers seem to not update the :hover before that second frame, even if we force a reflow or what else.
Even worse, in Chrome if you hide the <button> element using display:none, it will stay the :hover element until the mouse moves (while normally display:none elements are not accessible to :hover).
The specs don't go into much details about how :hover should be calculated, so it's a bit hard to tell it's a "bug" per se.
Anyway, for what you want, the best is to find that element through the document.elementsFromPoints method, which will work synchronously.
btn.addEventListener('click', function ( evt ) {
btn.remove();
for (let i = 0; i < 10; i++) {
lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
}
const chosen = document.elementsFromPoint( evt.clientX, evt.clientY )
.filter( (elem) => elem.matches( "li" ) )[ 0 ];
alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
});
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }
<button id=btn>Click Me</button>
<ul id=lst><ul>
I cannot exactly answer the question why you need 2 rafs.
But i can provide you an more elegant way with async / await. Create a small function called nextTick that returns an promise. So you await for the next frame.
So you can first wait till the button is gone, create your elemens, then await again for the next painting cycle to be sure the elements are accessible
btn.addEventListener('click', async function () {
btn.remove();
await nextTick();
for (let i = 0; i < 10; i++) {
lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
}
await nextTick()
const chosen = document.querySelector('li:hover');
alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
});
function nextTick() {
return new Promise(requestAnimationFrame)
}
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }
<button id=btn>Click Me</button>
<ul id=lst><ul>

How to make text selection visible in child, while mouse over draggable parent?

I have a box, that is draggable, inside this box I have another box, that contains text. I want to be able to be able to select the text but also to drag/drop parent. The problem I have is, how can I show selection, when leaving box with text? Right now selection highlight is disappearing when I mouse is out, and appear again, when mouse is over again. Here's the code. Select a text and leave green square.
const innerBox = document.getElementById('inner');
const outerBox = document.getElementById('outer');
innerBox.addEventListener('mouseover', () => {
outerBox.setAttribute('draggable', false);
});
innerBox.addEventListener('mouseout', () => {
outerBox.setAttribute('draggable', true);
});
* {
box-sizing: border-box;
}
#outer {
padding: 1rem;
width: 10rem;
height: 10rem;
background-color: red;
}
#inner {
width: 100%;
height: 100%;
background-color: green;
}
<div id="outer" draggable="true">
<div id="inner">Select me!</div>
</div>
Try adding user-select:auto to the inner box!
const innerBox = document.getElementById('inner');
const outerBox = document.getElementById('outer');
innerBox.addEventListener('mouseover', () => {
outerBox.setAttribute('draggable', false);
});
innerBox.addEventListener('mouseout', () => {
outerBox.setAttribute('draggable', true);
});
* {
box-sizing: border-box;
}
#outer {
padding: 1rem;
width: 10rem;
height: 10rem;
background-color: red;
}
#inner {
width: 100%;
height: 100%;
background-color: green;
user-select:auto
}
<div id="outer" draggable="true">
<div id="inner">Select me!</div>
</div>

How to make a popup disappear only when clicked anywhere outside of it

I'm trying to toggle a popup when click on a specific link and then remove class ".open" from it when clicked anywhere other than the popup box.
Using below methods I was able to get the popup disappear when clicked outside of it but it's now also getting disappear when clicked inside the popup area.
$(".onclick-dropdown-link, .user-message-center-link").on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var $this = $(this);
var id = $this.attr('href');
$('.onclick-dropdown').not(id).removeClass('open');
$(id).toggleClass('open');
});
$('body:not(.onclick-dropdown.open)').click(function(e) {
$("#alert-center, #message-center, #user-message-center").removeClass('open');
});
.onclick-dropdown {
opacity: 0;
visibility: hidden;
background: #f3f3f3;
width: 390px;
height: 390px;
position: absolute;
top: 128px;
right: 28px;
height: auto;
padding: 0;
z-index: 999;
}
.onclick-dropdown.open {
opacity: 1;
visibility: visible;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li class="alert-center-link">
<a class="onclick-dropdown-link" href="#alert-center">The Link</a>
</li>
</ul>
<div id="alert-center" class="onclick-dropdown">
<p>Lorem Ipusm</p>
</div>
Change your .click() function to the following:
$(document).click(function(e) {
if( !$('.onclick-dropdown').is(e.target) && !$('.onclick-dropdown').children().is(e.target)) {
if( $('.onclick-dropdown').hasClass('open') ) {
$("#alert-center, #message-center, #user-message-center").removeClass('open');
}
}
});
Change your Popup HTML to :
<div id="alert-center-outer" class="onclick-dropdown">
<div id="alert-center">
//CONTENT HERE...
<p>Lorem Ipusm</p>
</div>
</div>
#alert-center-outer{
position: fixed;
left; 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0.5;
background-color: black;
}
#alert-center{
width: 200px;
height: 300px;
margin: 0 auto;/*This will center align it.*/
}
Clicking the #alert-center-outer should hide the popup.
Clicking the #alert-center should stopPropagation so that it doesn't bubble to its parent.
try below code
$(document).ready(function(){
$(".onclick-dropdown-link, .user-message-center-link").on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var $this = $(this);
var id = $this.attr('href');
$('.onclick-dropdown').not(id).removeClass('open');
$(id).toggleClass('open');
});
$(document).click(function(e) {
if (!$(e.target).closest('.onclick-dropdown').length)
$("#alert-center, #message-center, #user-message-center").removeClass('open');
});
})
.onclick-dropdown {
opacity: 0;
visibility: hidden;
background: #f3f3f3;
width: 390px;
height: 390px;
position: absolute;
top: 128px;
right: 28px;
height: auto;
padding: 0;
z-index: 999;
}
.onclick-dropdown.open {
opacity: 1;
visibility: visible;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="alert-center" class="onclick-dropdown">
<p>Lorem Ipusm</p>
</div>
<li class="alert-center-link"><a class="onclick-dropdown-link" href="#alert-center">asdfsdfsdfds</a></li>
// only trigger once
$('body').one('click', function(e) {
var $target = $(e.target);
// the $target which trigger click event is not the popup dom itself
// and also is not the children dom of the popup
if (!$target.is('#alert-center') && $target.parents('#alert-center').length === 0) {
$("#alert-center, #message-center, #user-message-center").removeClass('open');
}
})
Is that what you want?
Now you can add any tags in the popup, and click inside the popup area where not hide it.

Dropdown Pure Javascript ES6 Issue with Toggle

I'm trying to trigger a dropdown with pure javascript that would have the following functionalities:
Clicking anyhwere outside the dropdown will close the dropdown
The dropdown can be toggled.
When clicking a link inside the dropdown, the ability to control
whether the menu should close or stay open
Now I have an issue. If I try to accomplish 1 (above) I am unable to accomplish 2 (toggle the dropdown) and vice-versa.
Regarding the 3 (above), the fiddle will clearly show you that when clicking a link on the first dropdown, the dropdown doesn't disappear. But when clicking a link from the second dropdown makes the dropdown disappear. How do I control this?
The Fiddle: https://jsfiddle.net/TheEarlyMan/0u6nnakm/
My JS:
class dropdown {
constructor() {
this.trigger = document.querySelectorAll('.dropdown [data-trigger]');
this.target = document.querySelectorAll('.dropdown [data-dropdown]');
this.init();
}
init() {
for (let i = 0; i < this.trigger.length; i++) {
this.handleClick(this.trigger[i]);
}
}
handleClick(el) {
el.addEventListener('click', (event) => {
event.preventDefault();
el.nextElementSibling.classList.toggle('active');
});
for (let i = 0; i < this.target.length; i++) {
this.handleClose(this.target[i]);
}
}
handleClose(el) {
window.addEventListener('mouseup', (event) => {
if (event.target != el && event.target.parentNode != el) {
if (el.classList.contains('active')) {
el.classList.remove('active');
}
}
});
}
}
window.addEventListener('load', () => {
new dropdown();
}, false)
The CSS:
.dropdown {
position: relative;
}
.dropdown-content {
position: absolute;
display: none;
z-index: 1;
min-width: 10vr;
padding: 0;
lost-column: 1/3;
padding: calc(1vr - 1px);
background: #fff;
border: 1px solid #ccc;
}
.dropdown-menu {
display: none;
position: absolute;
z-index: 1;
background: #fff;
border: 1px solid #ccc;
width: 8vr;
li {
margin: 0;
display: block;
}
a {
padding: 0.5vr 0.5vr;
&:hover {
background: #eee;
}
}
}
.dropdown-menu, .dropdown-content {
&.active {
display: block;
}
}

Close 2-Div Modal With Pure JS

I have a simple modal which has two divs. I want to have it "close" when either a) hitting esc e.g. keypress key code 27, or b) clicking outside.
I've searched so many posts but so many are js framework specific and/or don't work in the specific situation.
I want to be able to close it on esc or any click outside of the #emod div.
Here is what I have:
<div id="emod-bg"></div><div id="emod"></div>
CSS
#emod-bg
{
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #d0d0d0;
opacity: .50;
-webkit-opacity: .5;
-moz-opacity: .5;
filter: alpha(opacity=50);
z-index: 1000;
}
#emod
{
background-color: white;
display: none;
height: 480px;
left: 50%;
margin: -250px 0 0 -160px;
padding: 10px;
position: absolute;
top: 50%;
width: 320px;
z-index: 1000;
}
...and it is activated as follows:
document.getElementById('emod').style.display = "block";
document.getElementById('emod-bg').style.display = "block";
To close the modal when hitting the escape key, just add a keyup event to the document, and then check the keyCode property of the event object.
If you want to close the modal when clicking on the backdrop, just attach a click event and compare event.target to #emod-bg:
Example Here
var modal = document.getElementById('emod-bg');
modal.style.display = "block";
document.addEventListener('keyup', function (e) {
if (e.keyCode === 27) {
modal.style.display = 'none';
}
});
modal.addEventListener('click', function (e) {
if (e.target === e.currentTarget) {
modal.style.display = 'none';
}
});

Categories