I am using this script to open a drop down menu and then close it when anything else but the trigger is clicked. Now I am trying to add a second drop down to another area on the page and repeat the script but it is breaking.
For instance, I click button A (Gravatar), and drop down A opens.
However when I add the second script and click button B (category) to open drop down B, down down A stays open.
Also adding the second script breaks the drop down close function of the first script.
Here is the script:
<script>
function openAccount(event) {
event.stopPropagation();
document.getElementById("gravatar").classList.toggle("open");
}
window.onclick = function(event) {
document.getElementById("gravatar").classList.remove("open");
}
</script>
<script>
function openCategory(event) {
event.stopPropagation();
document.getElementById("category").classList.toggle("open");
}
window.onclick = function(event) {
document.getElementById("gravatar").classList.remove("open");
}
</script>
<li class="gravatar">
<a href="#" class="dropbtn" onclick="openAccount(event)">
<img src="<?php echo $gravatar; ?>" alt="" />
<span class="fa fa-icon fa-caret-down"></span>
</a>
<ul class="dropdown" id="gravatar">
<li class="header">
<?php echo $user['email']; ?>
</li>
</ul>
</li>
<div class="category">
Properties<span class="fa fa-icon fa-caret-down"></span>
<div id="category">Test</div>
</div>
Goals:
Multiple drop down menus on different parts of the page.
On click opens drop down.
Click on anywhere else on the page closes the drop down.
On click also closes any previously opened menu.
I'd do something really simple like putting a data* attribute on the element that's clicked on that contains the ID of the element to show or hide, e.g.
// Toggle hidden class on/off
function toggleVis(event) {
// Stop click on element bubbling (to body)
event.stopPropagation();
// Get target element
var el = document.getElementById(this.dataset.id);
// If non-target elements are visible, hide them
hideAll(el);
// Toggle target
el.classList.toggle('hidden');
}
// Hide all, excluding passed element
function hideAll(el) {
Array.from(document.querySelectorAll('ul:not(.hidden)')).forEach(function(node){
if (el != node) node.classList.add('hidden');
});
}
// Attach listeners
window.onload = function() {
// Add to linkLike spans
Array.from(document.querySelectorAll('.linkLike')).forEach(function(node) {
node.addEventListener('click', toggleVis, false);
});
// Add hideAll listener to wndow
window.addEventListener('click', hideAll, false);
// Run hideAll
hideAll();
}
/* style span like link */
.linkLike {
text-decoration: underline;
cursor: pointer;
}
/* class to hide element */
.hidden {
visibility: hidden;
}
<ul id="a"><li>A</ul>
<ul id="b"><li>B</ul>
<ul id="c"><li>C</ul>
<ul id="d"><li>D</ul>
<div><span class="linkLike" data-id="a">Toggle A</span></div>
<div><span class="linkLike" data-id="b">Toggle B</span></div>
<div><span class="linkLike" data-id="c">Toggle C</span></div>
<div><span class="linkLike" data-id="d">Toggle D</span></div>
Of course there are other ways to do the association, but ID is simple, explicit and doesn't depend on document layout or formatting.
Related
I have multiple modals with different id's.
<div id="#Modal" tabindex="-1" class= active" style="left: Opx;" role="dialog" >
<div id="#Modal-text' tabindex="0">
<div class="container">
<div class= close-btn">
<a class="closer noprint href=" javascript:void(0) aria-label="Close dialog" tabindex="0"></a>
</div>
</div>
<div class= content">..modal content goes here</div>
<div class="focus-guard" tabindex="0"></div>
</div>
<div id="#Modal-anothertext' tabindex="0"></div>
<div id="#Modal-sample' tabindex="0"></div>
</div>
The jquery function add a focus guard div and add an event listener to it whenever a tab or keyboard navigation, through it, it will go again to the close button:
"use strict"
jquery(document).ready(function ($) {
// Check if modal exists
if ($(" [id+='Modal-']").length {
// Check id's if containstring of 'Modal-' and check if modal has child with #focus-guard
if ($("[id*='Modal-']").find("focus-guard").length === 0) {
console.log("does not exist");
const focusGuard = document.createElement("div");
focusGuard.setAttribute("class", "focus-guard");
focusGuard.setAttribute("tabindex", "0");
// Add focus guard to parent
$("[id*='Modal-']").append(focusGuard);
// The closer button is being added in the DOM upon clicking modal, that's why I used DOMNodeInserted
$(document).bind("DOMNodeInserted", function (e) {
if (e.target.className="container") {
const close = document.querySelector(".closer");
console.log(close);
focusGuard.addEventListener("focus", () => {
close.focus();
});
}
});
}
});
}
Have tried also some possible selectors and isolating a single modal.
Unfortunately, the eventlistener on focus guard div does not trigger and I cannot target speciffically the "closer noprint" class.
I know it's not right to have a selector like an array("[id=Modal-]"*) to refer to the parent element of the modal but since it's multiple, not sure if this will be the right thing to do. Might there be a simple solution for this one.
Also stuck with a function that focuses on the last item clicked after dismissing the modal.
I am learning JavaScript and am trying to wrap my head around creating a nav megamenu. Basically, each button has a div after it that contains the megamenu content. Hovering OR clicking on the button adds a class to the dropdown (I could also use a show/hide within the JS, not sure which is better). The class adds opacity and visibility to the div.
I've created a "container" variable so it's only listening within the navbar, and then looping through the buttons to add the listener with nextElementSibling, but I can't seem to get it working.
Markup:
<nav id="main-nav">
<div class="nav-content">
<div class="nav-item">
<button class="nav-dropdown">Services</button>
<div class="nav-submenu">
(Links)
</div>
</div>
<div class="nav-item">
<button class="nav-dropdown">Locations</button>
<div class="nav-submenu">
(Links)
</div>
</div>
<div class="nav-item">
<button class="nav-dropdown">About Us</button>
<div class="nav-submenu">
(Links)
</div>
</div>
</div>
</nav>
JS:
( function() {
// initiating vars
var desktop, mobile, container;
// Breakpoints
desktop = window.matchMedia("(min-width: 769px)");
mobile = window.matchMedia("(max-width: 768px)");
// Target only navbar
container = document.getElementById( 'main-nav' );
if ( ! container ) {
return;
}
// Desktop dropdown click controls
if (desktop.matches) {
// only look for .nav-dropdown class
const desktopParents = container.querySelectorAll(".nav-dropdown");
// get each instance of .nav-dropdown
for (const desktopParent of desktopParents) {
// if a button is clicked, add class to that button's dropdown only
document.addEventListener('click', function(event) {
// getting correct dropdownby targeting next sibling of button (div.nav-submenu)
var dropdown = desktopParent.nextElementSibling;
var desktopParent = desktopParent.contains(event.target);
// adding and removing show dropdown class
if (dropdown.classList.contains('nav-active')) {
dropdown.classList.remove('nav-active');
} else {
dropdown.classList.add('nav-active');
}
});
}
}
}() );
Your event listener is attached to the whole document so the event is triggered when someone clicks anywhere on the page. Change document to your button variable desktopParent for the events to be triggered when the button is clicked. You can refer to the button element using this inside the event listener's callback function.
desktopParent.addEventListener('click', function(event) {
// getting correct dropdownby targeting next sibling of button (div.nav-submenu)
var dropdown = this.nextElementSibling;
var desktopParent = this.contains(event.target);
...
}
Here is an example
I have a bootstrap dropdown menu:
<div class="dropdown>
<a class="dropdown-toggle" href="#" id="menu1" data-toggle="dropdown"><img src="#imagePath" class="img-circle dropbtn" alt="Product" style="width:30px;height:30px;" /></a>
<ul class="dropdown-menu" id="productDD" role="menu" aria-labelledby="menu1"></ul>
</div>
Now the ul load the products on page load through ajax call. The issue is when I click any product in the dropdown, the dropdown gets closed. But I want to close dropdown only when mouse is clicked anywhere outside of the ul
Try something like this:
$(document).click(function(e) {
// get the clicked element
$clicked = $(e.currentTarget);
// if the clicked element is not a descendent of the dropdown
if ($clicked.closest('.dropdown').length === 0) {
// close the dropdown
$('.dropdown').removeClass('open');
}
});
In 2021 you do it this way:
$('.dropdown').on({
"shown.bs.dropdown": function() { /* whatever you want to do when it fires */ },
"click": function(e) { // handles click event
console.log(e.target) // just to check which element is on top
if($('.dropdown-menu').is(e.target)) { // if dropdown is clicked
this.closable = false // do not close it
return;
} else {
this.closable=true; // else close it
}
},
"hide.bs.dropdown": function() { return this.closable; } // save state
});
}
Bootstrap has a built-in solution
<a class="dropdown-toggle" href="#" id="menu1" data-toggle="dropdown" data-bs-auto-close="outside"><img ... /></a>
just place this script in your code
<script>
$(document).click(function() {
$(".dropdown").removeClass('open');
});
</script>
So the first block of code opens and closes the .main-navigation while putting the shadow overlay over the page at the same time. But what I would like to accomplish is to click anywhere outside the navigation to do the same action(close the menu and remove the overlay) only if the menu is opened and overlay is over the page - so if those two classs are applied.
Fiddle: https://jsfiddle.net/bfgb951w/
<header id="ovlay">
<span class="nav-toggle eq">≡</span>
<nav class="main-navigation">
<span class="nav-toggle">×</span>
<ul class="menu">
<li>About</li>
<li>Works and stuff</li>
<li>Contact</li>
</ul>
</nav>
</header>
$(document).ready(function(){
$('.nav-toggle').on('click', function(){
$('.main-navigation').toggleClass('open');
$('#ovlay').toggleClass('overlay');
});
});
$(document).click(function(){
if($('.nav-toggle').hasClass('open')) {
$('.main-navigation').toggleClass('open');
$('#ovlay').toggleClass('overlay');
}
});
You never set the open class to the .nav-toggle element so while the $(document).click() fires, the if-statement within it always yields false. Change it to:
$(document).ready(function(){
$('.nav-toggle').on('click', function(){
$('.main-navigation').toggleClass('open');
$('#ovlay').toggleClass('overlay');
return false;
});
});
$(document).click(function(event){
if($('.main-navigation').hasClass('open') && $(event.target).closest(".main-navigation").length == 0) {
$('.main-navigation').toggleClass('open');
$('#ovlay').toggleClass('overlay');
}
});
Check this fiddle: https://jsfiddle.net/1n78d9jq/
Note the added check in the document.click that prevents closing when the click is on the main menu itself.
I'm trying to make a dropdown very similar to dropbox dashboard, where if you click the username a flyout menu appears. Clicking the username again will close the flyout (toggling it every time you click).
The one caveat is that clicking anywhere except on the flyout itself will also close it.
So far, I have it working almost, but not 100%. If you click on the actual 'body' element directly, it will close the flyout as it should. By this I mean my website has a .wrapper element which doesn't take up the full height of the page. Theres a thin strip down at the bottom with no actual element covering it, only the <body> tag. Any place where .wrapper or some other element takes up space (even a 100% width invisible wrapper), it will not close the window if you click on anything where there is an element (besides body).
javascript:
// FLYOUT menu
$flyout = $('.flyout ul'),
flyoutDuration = 120;
$('.flyout h3 a').click(function(e) {
e.preventDefault();
$flyout.fadeToggle(flyoutDuration);
});
$(document).on('click',function(e) {
if ( $(e.target).parents($flyout).length === 0 ) {
$flyout.fadeOut(flyoutDuration);
}
});
HTML
<body>
<div class="blackbar">
<div class="container clearfix">
<div class="icon logo"></div>
<div class="flyout">
<h3>
Welcome back, username
</h3>
<div class="menu">
<ul>
<li><div class="users"></div>Users</li>
<li><div class="groups"></div>Groups</li>
<li><div class="configuration"></div>Configuration</li>
<li><div class="logout"></div>Logout</li>
</ul>
</div>
</div>
</div>
</div>
<div class="wrapper">
<! -- content here -->
</div>
</body>
The expected behavior should be any element you click on that isnt a descendent of .flyout should close the window (including .blackbar, the logo, etc)
To be honest - when I am doing something like this and I do not want clicks inside of the "box" to close the element - I prevent clicks from bubbling.
// FLYOUT menu
$flyout = $('.flyout ul'),
flyoutDuration = 120;
$('.flyout h3 a').click(function(e) {
e.preventDefault();
$flyout.fadeToggle(flyoutDuration);
});
$('.flyout').click(function(e) {
e.stopPropagation();
e.preventDefault();
});
$(document).on('click',function(e) {
if(flyout-open) {
$flyout.fadeOut(flyoutDuration);
}
});
Jquery Menu Click - on click anywhere on body will close the menu
In my case i wanted to close a drop down menu if its clicked primary link or outside the link i.e. anywhere on the html/body
http://jsfiddle.net/austinnoronha/k2Lwj/
var toggleClick = function(){
var divObj = $(this).next();
var nstyle = divObj.css("display");
if(nstyle == "none"){
divObj.slideDown(false,function(){
$("html").bind("click",function(){
divObj.slideUp();
$("html").unbind("click");
});
});
}
};
$(".clickme").click(toggleClick);