div loses focus, when clicked on a child which has focus - javascript

So, I am making a modal which has focus on it when it is shown.
And if the user clicks outside, I want to hide it.
I have implemented this with the help of focus events.
In cases, where the modal itself has a child which is focusable, modal loses the focus, I have also handled it in the below code. These cases are correctly handled by onFocusLoss but not by onFocusLoss2.
function onClickButton() {
var modal = document.getElementById('modal');
modal.style.visibility = 'visible';
modal.focus();
}
function onFocusLoss() {
var modal = document.getElementById('modal');
setTimeout(function() {
var activeEl = document.activeElement;
if (modal !== activeEl && !modal.contains(activeEl)) {
modal.style.visibility = 'hidden';
}
}, 0);
}
function onFocusLoss2() {
var modal = document.getElementById('modal');
var activeEl = document.activeElement;
if (modal !== activeEl && !modal.contains(activeEl)) {
modal.style.visibility = 'hidden';
}
}
#modal {
position: fixed;
visibility: hidden;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 1px solid red;
}
#modal > div {
padding: 16px;
}
<button onclick='onClickButton()'>Show Modal</button>
<div id='modal' tabindex='-1' onfocusout='onFocusLoss()'>
<div>Focus Test</div>
<div>
<input>
</div>
</div>
This is how I assume focus events are going, when the modal is focused and then I click inside input,
then first the modal loses focus, the body is the active element, and then the input element becomes the active element, and body loses focus.
Thus, the reason maybe that onFocusLoss2 is not handling the case because activeEl at that time is the body, while in onFocusLoss, the activeEl is the input.
My question is, why one approach is working and the other is not? I want a good technical reason. If your answer is that it gives enough time so that input element gains focus, I want to know how can you say that the time is enough and will always work?
My solution is based on this answer, though.

May wish to change your mind. When the modal is focused, add a click event to the element below it to close the modal.
<body>
<button onclick='onClickButton()'>Show Modal</button>
<div id='modal' tabindex='-1'>
<div>Focus Test</div>
<div>
<input>
</div>
</div>
</body>
function onClickButton() {
const modal = document.getElementById('modal');
modal.style.visibility = 'visible';
modal.focus();
document.body.addEventListener('click', ()=>{
modal.style.visiable = 'hidden'
})
}
Don't forget to adjust modal's z-index to make sure it's above the body.

Just like #RiverTwilight, I'm removing the focus need and simply adding an onClick event.
I placed a <div> inside your #modal and made #modal fill the whole screen, this way you can use different animations and other techniques to also animate your content as long as #modal doesn't have padding.
I hope you get your answer. ✌️
function onClickButton() {
var modal = document.getElementById('modal');
modal.style.visibility = 'visible';
modal.focus();
}
function onModalBGClick(e) {
var modal = document.getElementById('modal');
var activeEl = e.target;
if (activeEl === modal) {
modal.style.visibility = 'hidden';
}
}
#modal {
position: fixed;
visibility: hidden;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
.modal-inner {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 1px solid red;
}
#modal > div {
padding: 16px;
}
<button onclick='onClickButton()'>Show Modal</button>
<div id='modal' onclick='onModalBGClick(event)'>
<div class="modal-inner">
<div>Focus Test</div>
<div>
<input>
</div>
</div>
</div>

Related

Why do i have to click twice to open a modal?

I am trying to make a button on which if we click a popup opens(modal) and it is working but only problem is i need to click it twice to open it and i don't see how can i solve it.
// Get the modal
function modal1() {
var modal = document.getElementById("myModal");
// Get the button that opens the modal
var btn = document.getElementById("myBtn");
// Get the <span> element that closes the modal
var span = document.getElementsByClassName("close")[0];
// When the user clicks the button, open the modal
btn.onclick = function() {
modal.style.display = "block";
}
// When the user clicks on <span> (x), close the modal
span.onclick = function() {
modal.style.display = "none";
}
// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
}
/* The Modal (background) */
.modal {
display: none;
/* Hidden by default */
position: fixed;
/* Stay in place */
z-index: 1;
/* Sit on top */
padding-top: 100px;
/* Location of the box */
left: 0;
top: 0;
width: 100%;
/* Full width */
height: 100%;
/* Full height */
overflow: auto;
/* Enable scroll if needed */
background-color: rgb(0, 0, 0);
/* Fallback color */
background-color: rgba(0, 0, 0, 0.4);
/* Black w/ opacity */
}
/* Modal Content */
.modal-content {
background-color: #fefefe;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}
/* The Close Button */
.close {
color: #aaaaaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
<!-- Trigger/Open The Modal -->
<button id="myBtn" onclick="modal1()">Open Modal</button>
<!-- The Modal -->
<div id="myModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<span class="close">×</span>
<p>Some text in the Modal..</p>
</div>
</div>
Thank you in advance :)
ps: I am leaning in very early stage js (beginner)
It's fairly straightforward if you read your code in order.
When you click the button, it runs the modal1() function.
This function, among other things, adds a btn.onclick listener, waiting for a click.
That's it.
So now, you have to click a second time in order to trigger the listener you just created.
The new onclick listener you create, for that matter, overrides the current onclick on the button, and I'm not sure why you do this.
The simple solution is to put modal.style.display = "block" directly in modal1(). Why override the onclick function?
function modal1() {
modal.style.display = "block";
}

Can't close popup with touch without a specific button in ios

I am learning now how to create popups in mobile devices, and I created a popup that closes when I touch any part of the screen, but it doesn't work on ios (maybe on all touch devices, haven't checked), only on the computer.
Now based on the information I read here:
jQuery click events not working in iOS
Make onclick work on iphone
How to bind 'touchstart' and 'click' events but not respond to both?
Cannot close popup() window in iPhone Safari
The popup opens fine because you press a link attribute <a> to open it up, however upon closing I can press any part of the screen.
Here is the code:
var modal = document.getElementById('myModal'); // Get the popup
var btn = document.getElementById("myBtn"); // Get the button that opens the popup
btn.onclick = function() { // Open the popup
modal.style.display = "block";
}
window.onclick = function(event) { // Closes the popup
if (event.target == modal) {
modal.style.display = "none";
}
}
I tried adding cursor: pointer; in CSS, changing the script in javascript to onmouseover instead of click, changing the script, changing window.click to div.click and adding the following code to the javascript script:
let touchEvent = 'ontouchstart' in window ? 'touchstart' : 'click';
window.on(touchEvent, function(event){...});
Nothing worked...
I guess I don't quite get how to use the touch event listener, if it's connected.
You need to add event listeners for touchstart and/or touchend for this to work on mobile devices, check out the snippet below:
// JavaScript
var modal = document.getElementById('myModal'); // Get the popup
var btn = document.getElementById("myBtn"); // Get the button that opens the popup
btn.onclick = function() { // Open the popup
modal.style.display = "block";
}
let eventsArray = ["click","touchstart", "touchend"];
eventsArray.forEach( function(event) {
window.addEventListener(event, function(e) {
if (e.target === modal) {
modal.style.display = "none";
}
}, false);
});
/*CSS*/
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#myModal {
display: none;
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.25);
width: 100%;
height: 100%;
text-align: center;
}
#myModal span {
position: fixed;
top: 50%;
left: 50%;
padding: 10px;
background-color: #a600a6;
color: #fff;
transform: translate(-50%, -50%);
}
<!--HTML-->
<body class="overlay">
<div >
<button id="myBtn">
Open Modal
</button>
</div>
<div id="myModal">
<span>Hello World!</span>
</div>
</body>
Tested on jsfiddle with an iPad and it works.

Closing div when clicking outside of div not working on iPhone. Works fine on Mac/PC and Android

Here is the div(s). Depending on what button is clicked, one of them will appear on the screen.
var box;
var value;
var wrapper = $('.wrapper');
$('body').on('click', '#login, #register', function() {
if ($(this).attr('class')) {
value = $(this).attr('class')
} else {
value = $(this).attr('id')
}
switch (value) {
case 'login':
box = $('.loginBox');
break;
case 'register':
box = $('.registerBox');
break;
}
box.closest(wrapper).toggleClass('open');
$('input:text', box).first().focus();
});
// this successfully closes the div when I click outside of it (on Mac/PC & Android, however it does not close the div on iOS)
$('body').on('click', '.wrapper', function() {
box.closest(wrapper).toggleClass('open');
});
$('body').on('click', '.wrapper div', function(e) {
e.stopPropagation();
});
.wrapper {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: rgba(0, 0, 0, 0.1);
display: none;
z-index: 300;
}
.open {
display: block;
}
.blackBox {
position: absolute;
left: 50%;
top: 41%;
height: 340px;
width: 500px;
z-index: 997;
margin: 0 auto;
transform: translate(-50%, -50%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="blackBox">
<div class="loginBox">
...
</div>
</div>
</div>
<div class="wrapper">
<div class="blackBox">
<div class="registerBox">
...
</div>
</div>
</div>
The 2nd function, (the toggle when clicking outside the div) successfully closes the div when I click outside of it (on Mac/PC & Android, however it does not close the div on iOS)
Is there another method I can perform for iOS?:
$('body').on('click', '.wrapper', function(){
if (/iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) {
// another method
}
box.closest(wrapper).toggleClass('open');
});
iStuff will only acknowledge a click event if the element is clickable. One of the conditions to make it clickable is to give it
cursor:pointer;
If that's not an option, you could use tap:
$(containerSelector).on('click tap', targetSelector, function(event) {
// have you finished that webpage already?
})
Yet another option is to use touchstart. But I personally think that's a mistake. touchstart is triggered whenever a touch event is started, whether it's a swipe, a multi-touch zoom or whatever other fancy gestures your device might recognize.
Most times, when you want to swipe, pan, zoom, you don't want click functionality to trigger. tap is the most click-like event in the world of touch gestures.
In the particular case of a dropdown, you don't want it closing on gestures aimed at better positioning it on the screen or zooming in on the area in which it opened.

How to close a side bar when i pressed anywhere

What I want to do is to dismiss the sidebar when I press anywhere on the screen.
How can it be done? How can I make it so that when I hover over my image, my text would appear instead of it being visible the whole time?
function openNav() {
document.getElementById("mySidenav").style.width = "300px";
document.getElementById("toggle").style.position = "static";
}
function closeNav() {
document.getElementById("mySidenav").style.width = "0";
document.getElementById("toggle").style.marginRight = "0";
document.getElementById("toggle").style.position = "relative";
}
#toggle {
position: relative;
left: 400px;
font-size: 1.2em;
visibility: visible;
}
#toggle:hover {
color: white;
transition: 0.5s;
}
.sidenav {
height: 100%;
width: 0;
position: fixed;
z-index: 1;
top: 0;
right: 0;
background-color: transparent;
overflow-x: hidden;
transition: 0.5s;
padding-top: 50px;
}
.sidenav a {
text-decoration: none;
font-size: 25px;
color: #818181;
display: block;
transition: 0.3s;
}
.sidenav a:hover,
.offcanvas a:focus {
color: #f1f1f1;
}
.sidenav .closebtn {
position: absolute;
top: 0;
right: 25px;
font-size: 36px;
margin-left: 50px;
}
<div>
<ul id="topBar">
<li id="ixora">Ixora</li>
<li class="lists">2014</li>
<li class="lists">2015</li>
<li id="toggle" onclick="openNav() ">☰</li>
</ul>
</div>
<div id="mySidenav" class="sidenav">
×
<span class="textImage">Outing</span>
<img src="outing.jpg">
<span class="textImage">Prom Night</span>
<img src="prom.jpg">
<span class="textImage">PortDickson Trip</span>
<img src="pd.jpg">
<span class="textImage">Merdeka</span>
<img src="merdeka%20(2).jpg">
</div>
I honestly think you should be using jQuery here as it's fantastic for DOM manipulation. If for some reason you have to use vanilla js then it's going to be a little trickier.
Adding jQuery to your project is easy and well explained on the jQuery site. Here;s a comparison of selecting an element in js/jquery:
Vanilla JS:
document.getElementById("mySidenav")
jQuery:
$('#mySidenav')
To get things to happen when events happen (i.e. button press, or clicking away from a menu) you set up event handlers.
It's hard to picture from your code as there seems to be CSS missing for #topBar so I can't see your site very well (here's your code running in jsfiddle).
But lets say you have a button with ID of #openToggle. You would set up your sideNav in css with the correct width, height, etc, and leave it as display: none. Then we create an event handler to do something when you click that button:
//event handlers go at the bottom of your js file or script tag
$('#openMenu').on('click', openMenu);
That example is basically saying when the element with ID 'openMenu' is 'clicked' the run the 'openMenu' function - simple! :)
The openMenu function would look something like (basic example):
function openMenu() {
var $menu = $('#sideNav');
$menu.show();
};
A better way would be to have a toggle function that toggles the menu based on whether it's already open or closed:
function toggleMenu() {
var $menu = $('#sideNav');
if (($menu).is(':visible')) { //if it's visible
$menu.hide(); //hide the menu
} else { //else it's hidden
$menu.show(); //so show it
}
};
// event handler for menu toggle button
$('#menuToggle').on('click', toggleMenu);
With regards to closing the menu when you click away from it, you could bind an event handler to the body of the page and use jQuery's .one() function (runs only once) which will detect if the body is clicked and then run the menuToggle funtion - you'd end up with 2 handlers for this:
// event handler for menu toggle button
$('#menuToggle').on('click', toggleMenu);
$('body').one('click', toggleMenu);
Alternatively you could have the menu close when your mouse pointer leaves the menu?:
//event handlers
$('#menuToggle').on('click', toggleMenu);
$('#sideNav').mouseleave(toggleMenu);
The mouseleave handler is basically saying, once the mouse pointer leaves the element with ID of 'sideNav' then run the toggleMenu function.
I'm a newb too so my examples may not be great, but I hope I helped at least a little. Hopefully some real javascript devs will be along shortly to add to this or give better examples.
Cheers,
Dave
JQuery
$( document ).onclick(function() {
$("#mySidenav").hide();
}
Reference documentation:
JQuery .on
JQuery .hide
With javascript, you can do the following:
document.onclick = function(e){
if(e.target.id == "mySidenav"){
return false;
}
closeNav();
}
I see that you already have functions to open and close your sidebar,
clicking "anywhere" is the same as "clicking on the body", But I guess that you don't want your sidebar to close when you're clicking on it.
So, here's what you can do :
var myNav = document.getElementById("mySidenav");
myNav.onclick = function(e) {
e.stopPropagation();
}
document.body.onclick = function(e) {
closeNav();
}
So, when you click on your sidebar, you won't click on the body because the event propagation is stopped, and when you click elsewhere, it will close your sidebar.
Hope it helps,
best regards

css+js: div click (and elements inside it)

I'm trying to build a (semi-transparent) overlay that covers all on a web page.
Similar to this: http://www.jankoatwarpspeed.com/use-jquery-to-turn-off-the-lights-while-watching-videos
<div id="overlaySplash" onclick="clickHandler(this)">
<div id="insideBox">
[ label elements here with onclick="dosomething()" ]
</div>
</div>
css
#overlaySplash {
position: absolute;
top: 0;
left: 0;
bottom:0;
right:0;
background: rgb(50, 50, 50);
background: rgba(0, 0, 0, .50);
z-index: 50;
}
#insidebox {
position: absolute;
z-index: 100;
left: 15%;
right: 15%;
bottom: 5%;
line-height: 50px;
text-align: left;
}
The problem is that I'm not using jquery and the overlay div will have some clicable contents on int. I have this function below but when I click on elements inside the main overlay div JS will return e.id as being the overlay div itself, not the clicked element. This way I can't tell where the user really clicked.
function clickHandler(e){ //hide the div overlaySplash
e = e || event;
alert(e.id);
}
THE BOTTOM LINE:
user clicked an label inside the div: dosomething();
user clicked the background (the DIV itself): closeOverlaySplash();
I don't think you need to completely stop propagation as it may serve some purpose later on. It might be easiest to create a separate js & css files that encompass this functionality for ease of use.
The issue you have is basically the event is bubbling up to the parent when it isn't currently needed. You can easily check the event.target.id to see if the parent was clicked or not. With this you can make sure the overlay was clicked vs the content.
eg:
if (event.target.id == "overlay") {
//hide the overlay
}
JSFiddler
Like this:
HTML
<div id="overlaySplash" onclick="clickHandler(event);">
<div id="insideBox" onclick="clickHandlerInner(event);">Inner</div>
</div>
JS
function clickHandler(event) {
alert("no");
}
function clickHandlerInner(event) {
if (!event) var event = window.event;
event.cancelBubble = true; //IE
if (event.stopPropagation) event.stopPropagation()
alert("yes")
}
jsFiddle

Categories

Resources