hide popup by clicking outside of it - javascript

I read this article saying a popular way to close popups by clicking anywhere outside of the popup is a bad practice
https://css-tricks.com/dangers-stopping-event-propagation/
He gives an alternative solution in the article
$(document).on('click', function(event) {
if (!$(event.target).closest('#menucontainer').length) {
// Hide the menus.
}
});
Explanation of the above code: "The above handler listens for clicks on the document and checks to see if the event target is #menucontainer or has #menucontainer as a parent. If it doesn't, you know the click originated from outside of #menucontainer, and thus you can hide the menus if they're visible."
I don't use jQuery so am trying to implement something similar in vanilla.js.
One thing I tried is to stop pointer events with CSS, but there might be things inside the inner-popup, like a button, that I do want to be clickable
If it confuses matters - My popup is a black overlay with a lowered opacity with then another div inside to be the actual popup.
I tried this but it didn't work - I'm attaching the listener only after showing the popup:
function showPopup(popup) {
popup.style.display = "flex";
popup.addEventListener("click", function(event) {
if (!event.currentTarget.contains(target)) {
closePopup(event.currentTarget);
}
})
EDIT: this works
function clickOutsidePopup(e, popup) {
console.log(e.target)
if (e.target.querySelector(".inner")) {
closePopup(popup);
}
}

Dont stop the pointer events, write a function that works instead, something like this :
document.addEventListener('click', function(e) {
var el = e.target.closest('#menucontainer');
if (el) {
// click inside the popup
} else {
// click outside the popup
}
});
Remember to remove the event handler when the popup is closed, otherwise you'll have multiple event handlers cancelling each other out.
Note that element.closest() need a polyfill to work in IE.

Approach a simpler way:
Create an overlay which covers the whole body (positition absolute; top: 0; left: 0; width 100%; height: 100%; z-index: 2). Attach an onclick event which triggers the modal.close();
Put your modal over that overlay. When the overlay is clicked, just remove it and close the modal. (position: absolute; z-index: 3);

Related

Hide trigger (img) for popup, when I click on it

I have image in header, and when I click on the image a popup opens. I need to hide this image when popup is open and show when popup is closed.
Second Image: 2
Website:https://clavis-schule.de/home-dev/
If you're making use of event listeners you can swap out classes/styling of the element that is firing the event. Onclick works too but I personally only use it on button elements, choose what you prefer most
In case you're using jQuery you can use
$('img').on('click', (e) => {
e.toggle()
});
https://api.jquery.com/toggle
alternatively if you're not using jQuery you could use
document.addEventListener('click', function (event) {
event.target.classList.toggle("d-none")
}
// (more specifically)
document.getElementById("menu-img").classList.toggle("d-none")
where d-none is a bootstrap class. It could simply be a custom class as well
.d-none {
display: none
}
You will need to toggle the image back when clicking away the modal and since you've provided no code example I can only tell you to figure that part out on your own.

Preventing Change Page with Unsaved Change

Let me explain what I want to achieve: first of all, I'm building a React Application, but I think this is a JavaScript-related "problem".
Basically, what I want is to prevent the user to change page when he has made some changes on a form and he hasn't saved them. For this, let's just pretend we have a variable formIsEdited that is set to true if there are unsaved changes, false otherwise.
I know something similar to this can be achieve with the beforeUnload event, or with The <Prompt> component belonging To React Router, but both don't allow me to have a custom modal: what I want to show it's a div with my own style, my own button, etc, ...
So, I came up with an idea: I put a eventListener on the click event and, if the click is on a link and formIsEdited is true:
I prevent the default behavior;
I save in a variable lastClick the element clicked;
I show my modal;
If the user chooses to procede with the change page, i set formIsEdited to false, I pretend a click on the element saved in lastClick that will lead to the change page.
Then, it may happens that a whole div is inside an anchor element, so I also need to find the closest anchor element of the div on which the click has been made: if the research of the link doesn't return null, it means that the click would lead me to a link. So, I guessed that e.stopPropagation() would achieve what I wanted, but... it doesn't!
If you look at the snippet code.. I thought that, if you click on the blue box, $(e.target).closest('a'); would find the link to stackOverflow and then, since it's not null, would stop the propagation, therefore would avoid the change page.. But it doesn't work.
Any idea why? Or any idea to achieve what I wanted in the first place?
EDIT: I know there are several other question about preventing the changePage, but this question is more about the reason why stopPropagation does not avoid the click on the anchor element.
One more info: if I would take document.anchors and foreach element add a listener in which I do the action I itemize before, I can prevent the change page, with my own modal and my own logic!
But, unfortunately, since I'm using React, some anchor item are not in the DOM when I can call the function that adds the listener just mentioned.
EDIT2: I tried Luca's solution and, as I remembered, it doesn't work.. But the behavior of React is "strange".
Let me explain: if I use the following code:
let anchors = document.getElementsByTagName("a");
for (let i = 0; i < anchors.length; i++) {
anchors[i].addEventListener("click", (e) => {
clickListenerAnchorUnsavedChanges.call(this, e, anchors[i]);
}
}
function clickListenerAnchorUnsavedChanges(this: any, e: any, anchor: any) {
// if Edit has been made
console.log("Inside function1");
e.preventDefault();
}
The things works. Actually, let's pretend I click on the anchor which links to the /Account page: the write inside function1 is printed before the write I've put inside the constructor of the Component Account.
Instead, with the following function:
function test1(this: any) {
window.addEventListener("click", (e) => {
console.log("inside function 2");
// if edits have been made
let closestLink = $(e.target).closest('a');
if (closestLink != null) e.preventDefault();
}
}
It appears that the first thing to be printed is the console.log inside the constructor of Account component, and then inside function 2.
I don't know, it seems that, if I modify directly the behavior of an anchor, I can actually redirect/change the flow of the actions performed by a a click on a link. Instead, by modify the behavior on a click event, I cant.. But it seems that now this fact is more React-related, because in the snippet here the e.preventDefault() works.
window.addEventListener("click", (e) => {
var closestLink = $(e.target).closest('a');
if (closestLink!= null) {
e.preventDefault();
e.stopPropagation();
}
});
#span-1, #span-2 {
display: inline-block;
width: 200px;
height: 100px;
}
#span-1 {
background-color: blue;
}
#span-2 {
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<a href="https://stackoverflow.com/">
<span id="span-1" />
</a>
<span id="span-2">
</span>
</div>
I would suggest using preventDefault() instead of stopPropagation(), please read What's the difference between event.stopPropagation and event.preventDefault? for more information on the topic.
window.addEventListener("click", (e) => {
var closestLink = $(e.target).closest('a');
if (closestLink!= null) {
e.preventDefault();
}
});
#span-1, #span-2 {
display: inline-block;
width: 200px;
height: 100px;
}
#span-1 {
background-color: blue;
}
#span-2 {
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<a href="https://stackoverflow.com/">
<span id="span-1"></span>
</a>
<span id="span-2">
</span>
</div>
This still only works for links in the DOM, you can't control the user moving away by any other action, e.g. clicking on a link in the bookmarks-bar or an element that changes or replaces the location using a click handler.

Strange lag which disappears by clicking anywhere on the html body

I have some overlay elements which are display: none initially but turn to display: inline when I hover over specific items on the page, and disappear again when the mouse hovers over something else. Exactly same behavior as tool-tips with the difference that this overlay objects have clickable and interactive elements (such as a jquery accordion).
Everything works perfectly, until I interact with these overlay elements, i.e. click on one of the clickable items in the overlay element. Then, once that overlay item becomes display:none again, the page becomes extremely laggy in terms of how long it takes when I hover over an item to find its corresponding overlay element (they are selected by their id) and for it to appear and disappear.
The strange thing is that if I click anywhere on the html body, the lag disappears and everything becomes fast as in the beginning.
Out of despair, I have tried to programmatically call blur, focus, trigger('click') once the overlay element is set back to display:none but none has helped so far, and I have to manually click on the page for the lag to go away.
Any idea what causes such behavior and how I can fix it? thanks,
Edit: code
CSS part:
span.overlay {
z-index:10;
display:none;
position:absolute;
}
span.visible { display:inline; }
HTML part: lots of such span elements, each with their own unique id.
<span class='overlay ui-widget-content' id='xyz'>
<!-- lots of stuff here -->
</span>
javascript part:
/* displays overlay element when user hovers over the first td */
$('table.foo > tbody > tr > td:first-child').hover(
function(e) {
$(this).parent().tooltip('disable');
var elem = $('#' + $(this).parent().data('overlay-id'));
if (!elem.hasClass('visible')) {
elem.css('left', e.pageX + 20).css('top', e.pageY).addClass('visible');
elem.find('.accordion:first').accordion('refresh');
}
}, function() {
var elem = $('#' + $(this).parent().data('overlay-id'));
if (! elem.is(':hover') && ! elem.hasClass('pin')) {
$(elem).removeClass('visible');
}
$(this).parent().tooltip('enable');
});
/* if mouse leaves span.visible and it is not pinned it will hide the span */
$('body').on('mouseleave', 'span.visible',
function() {
if (!$(this).hasClass('pin')) {
$(this).removeClass('visible');
}
});
Edit: profiling the code, it seems that get offsetHeight and get offsetWidth take way longer than before. Yet I do not know why this should happen and why it should go away by clicking on the page.
previously, when I do not observe the problem, these two functions each take less than 3%.
try binding the mouseleave event upon opening the "tooltip". Replace your code with this (not tested):
/* displays overlay element when user hovers over the first td */
$('table.foo > tbody > tr > td:first-child').on('mouseenter',
function(e) {
$(this).parent().tooltip('disable');
var elem = $('#' + $(this).parent().data('overlay-id'));
if (!elem.hasClass('visible')) {
elem.css('left', e.pageX + 20).css('top', e.pageY).addClass('visible');
elem.find('.accordion:first').accordion('refresh');
// notice the "ONE" handler, it'll unbind the event after execution
elem.one('mouseleave', function() {
if (!$(this).hasClass('pin')) {
$(this).removeClass('visible');
}
$(this).parent().tooltip('enable');
});
}
}
);
Notice the one listener to unbind the event after it's first execution.
I can't guarantee that this will fix your issue but I experienced lots of performance hits when a page has A LOT of elements and browsers need to check hover events that change very quickly.
This way the browser only needs to check one mouseleave event. And if it happened, it's gone again. It seems you may have too many bound events and don't clean them up properly.
I'm not sure if I replicated your desired functionality correctly so please add code if I missed something. I was unsure why exactly you'd need to bind a mouseleave event via body AND via .hover().

Javascript click function not responding

I have simple script which make layer on background when popup div is shown.
jQuery(".openForm").click(function() {
jQuery("#popup").show();
jQuery("body").append('<div id="layer"></div>');
});
This works fine but, when I click somewhere it should close popup with this script
jQuery("#layer").click(function() {
jQuery("#popup").hide();
jQuery("#layer").remove();
});
anyway nothink happens, there is no error even.
I'm guessing #layer doesn't exist when you attempt to bind the handler. Use event delegation instead:
jQuery(document).on('click', "#layer", function() {
jQuery("#popup").hide();
jQuery("#layer").remove();
});
Alternatively, you could hide/show the #layer div (like the #popup div) instead of adding and removing it each time.

HammerJS triggers click after dragend

I have a perfectly working hammerjs drag listener that captures drags just like I'd expect. But when you let go, right after the dragend is triggered, it then triggers a click event as well. So if you were clicking to drag (move) the target, and the cursor happened to be over a link, when you let go (drop), it triggers a click on the link, which I don't want.
Here's my code:
$('.draggable').hammer({
drag_min_distance: 0,
drag_max_touches: 2,
drag_lock_to_axis: true,
drag_lock_min_distance: 30
}).on('drag', handleMiddleEvent)
.on('dragstart', handleStartEvent)
.on('dragend', handleEndEvent);
If I console.log the event on all three handlers and console.log click events on all links, I get something like this in my console:
dragstart
dragmiddle
dragmiddle
dragmiddle
dragmiddle
dragend
click
I want to avoid the click at the end. I tried adding event.stopPropagation() to all of the handle methods, but to no avail.
Try using Hammer.js tap events instead of the click events on the links.
I had a pretty similar problem, and found some kind of workaround - I know it's dirty but the links are not being clicked by a drag. I want to drag a wrapper-div, which is my wrapper element seen in the js below. It has several links in it, wich are being clicked when the dragging stops above them, and i want to prevent that from happening.
First, i define a div that is on top of everything but set hidden in my html:
<div id="wrapper">
<div id="linklist">
<-- Links etc in here-->
<div id="preventClick" style="visibility: hidden; position: absolute; width: 5000px; height: 6000px; "></div>
</div>
</div>
Then, in the hammer functions, the div is set to visible at a dragstart (in this case, a dragdown) - you just can't see it because it has no background-color or any content.
JS:
var wrapper = document.getElementById("wrapper");
Hammer(wrapper).on("dragdown", function() {
handleDragDown(); //call function that handles movement
var preventClick = document.getElementById("preventClick");
preventClick.style.visibility = 'visible';
});
Hammer(wrapper).on("dragend", function() {
var preventClick = document.getElementById("preventClick");
preventClick.style.visibility = 'hidden';
});
I know this is far from perfect and your post is from several months ago, but it worked for me. Hope i could help :)

Categories

Resources