Since adding an event listener to the document to close the navigation on an outside click, the burger menu has gone from 100% effective to activating the active class's to being less effective and not always creating the event. Any help will be much appreciated.
const burger = document.querySelector('.nav-mob-menu');
const mobNav = document.querySelector('.mobile-nav-side');
burger.addEventListener('click', () => {
mobNav.classList.toggle('active');
burger.classList.toggle('active');
})
document.querySelectorAll('.links').forEach(e =>
e.addEventListener('click', () => {
burger.classList.remove('active');
mobNav.classList.remove('active');
}))
document.addEventListener('click', (e) => {
if(e.target.id != 'nav-mob-menu' && e.target.id != 'mobile-nav-side'){
mobNav.classList.remove('active');
burger.classList.remove('active');
}
})
Related
I want to toggle a navbar on click using a button and if the navbar is already active I want to disable it when the user clicks anywhere. The event listeners are all connected and the toggling code works normally but implementing the "click anywhere on the screen to turn off" feature isn't working.
Edit: I can now toggle by clicking anywhere but I can't toggle using the button I can only turn it on and not off.
const button = document.querySelector('button.mobile-menu-button');
const menu = document.querySelector('.mobile-menu');
let menuActive = false;
button.addEventListener('click', (e) => {
e.stopPropagation();
if (!menuActive) {
menu.classList.toggle('hidden');
menuActive = true;
}
});
window.addEventListener('click', () => {
if (menuActive) {
menu.classList.toggle('hidden');
menuActive = false;
}
});
The problem is that when you click on the button, you are also clicking on the window at the same time. You should use stopProgopation().
Here is a solution. When the button is clicked, the menu gets shown. If the user then clicks on the screen somewhere else other than the button, the menu will disappear.
<div id="menu" class="mobile-menu hidden">This is the menu</div>
<button class="mobile-menu-button">Button</button>
<script>
const button = document.querySelector('button.mobile-menu-button');
const menu = document.getElementById('menu');
let menuActive = false;
button.addEventListener('click', (e) => {
e.stopPropagation();
if (!menuActive) {
menu.classList.toggle('hidden');
menuActive = true;
}
});
window.addEventListener('click', () => {
if (menuActive) {
menu.classList.toggle('hidden');
menuActive = false;
}
});
</script>
<style>
.mobile-menu {
display: block;
}
.hidden {
display: none;
}
</style>
I found I didn't need a condition for the button itself, only the window. Thanks to everyone for helping.
const button = document.querySelector('button.mobile-menu-button');
const menu = document.querySelector('.mobile-menu');
let menuActive = false;
button.addEventListener('click', (e) => {
e.stopPropagation()
menu.classList.toggle('hidden');
menuActive = true;
});
window.addEventListener('click', () => {
if (menuActive) {
menu.classList.toggle('hidden');
menuActive = false;
}
});
at the current point, this code works, but when the user clicks to hide the menu, the useClickOutside fires too, the menu toggles off and on again... would there any way to fix that so when clicks outside it closes but when clicks the button it toggles on/off ?
const useClickOutside = (ref, handler) => {
useEffect(() => {
const clickHandler = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', clickHandler);
return () => {
document.removeEventListener('mousedown', clickHandler);
};
});
};
const Settings = () => {
const ref = useRef();
const [toggle, setToggle] = useState(false);
useClickOutside(ref, () => setToggle(false));
return (
<div className='settings'>
<button onClick={() => setToggle(!toggle)} className='settings__button'>
Menu
</button>
{toggle && (
<div ref={ref} className='settings__panel'>
<Link className='settings__links' to='/user/settings'>
Your Profile
</Link>
<Link className='settings__links' to='/user/settings'>
Todos history
</Link>
<Link className='settings__links' to='/user/settings'>
Settings
</Link>
<Link className='settings__links' value={'Logout'} to='/user/login'>
Logout
</Link>
</div>
)}
</div>
);
};
You might consider adding a onBlur event on the .settings div with a tabIndex=0.
You can then then capture blurs of the div and test if the event came from within the div or not.
const onBlur = (e: FocusEvent < HTMLElement > ) => {
if (opened?) {
const element = e.relatedTarget;
if (element == null) {
// dropdown was blured because window lost focused. probably close.
} else if (element != e.currentTarget) {
if (!e.currentTarget.contains(element as Node)) {
// blured element is not in .settings. close
}
}
}
};
If you want to get fancy you can also add a keydown and close on escape.
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
// close!
}
);
Here is a code sandbox that implements these items.
You could make use of event stopPropagation. Add the call event.stopPropagation() to your onClick handler function that hides the menu.
<button
onClick={(e) => {
e.stopPropagation();
setToggle(!toggle);
}}
className='settings__button'
>
Menu
</button>
This will prevent that the onClick event bubbles upwards to the next event listener which would be your onClickOutside listener.
UPDATE:
This will only work if your event listener is listening for onClick events. Your inline onClick event listener will stop the propagation of the event of type click only.
document.addEventListener('click', clickHandler);
return () => {
document.removeEventListener('click', clickHandler);
};
When button is focused and then become disabled, focus is still on the button.
It makes browser unresponsive for keyboard events.
Problem does not occur on Chrome, because all disabled buttons lose focus automatically. Global listener seems to work but maybe you have better solution.
window.addEventListener('click', ev => {
if (ev.target instanceof HTMLButtonElement && ev.target.disabled) {
ev.target.blur();
}
})
https://codepen.io/magdalena-chmura/pen/abOrERz?editors=1010
I know it is very late and probably you don't need it anymore, but here is working code to fix it on all visible buttons. You only need to call this function at the right moment:
initButtonObservers() {
this.element.nativeElement.querySelectorAll('button').forEach((button) => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === "disabled" && mutation.target instanceof HTMLButtonElement) {
mutation.target.blur();
}
});
});
const config: MutationObserverInit = { attributes: true };
observer.observe(button, config);
});
}
I have a fly-out menu on my website, that closes when the user clicks away (anywhere else on the page) using an event listener. I would like to do the same but for accessible user for when they tab away from the fly-out menu. Any help doing this would be appreciated.
configure() {
window.addEventListener('resize', debounce(this.resize.bind(this), 300));
window.addEventListener('blur', () => once(window, 'focus', this.resize.bind(this)));
this.more_btn.addEventListener('click', () => {
if (this.more_list.classList.contains('hidden')) {
setTimeout(() => {
once(window, 'click', () => {
this.container.classList.remove('opened');
this.more_list.classList.add('hidden');
toggleAria(this.more_list, 'aria-expanded');
});
}, 100);
}
this.container.classList.toggle('opened');
this.more_list.classList.toggle('hidden');
toggleAria(this.more_list, 'aria-expanded');
});
this.dropdown.addEventListener('click', () => {
this.toggle();
});
this.resize();
}
you can bind the eventlistener to keyup.
document.addEventListener('keyup', function(event) {
if (event.keyCode == 9) {
// close your menu
}
});
as stated in your question, close menu only when user "tab-away", bind the listener to the menu.
document.getElementById("your_menu_selector").document.addEventListener('keyup', function(event) {...}
your question has already an answer here: How to call function when I press tab key in javascript?
I'm working a site using this Bootstrap example, with a simple slide in sidebar navigation.
http://ironsummitmedia.github.io/startbootstrap-simple-sidebar/#
It is slightly modified, so I have a button for the menu to open:
// Opens the sidebar menu
$("#menu-toggle").click(function (e) {
e.preventDefault();
$("#sidebar-wrapper").toggleClass("active");
});
And a button for the menu to close:
// Closes the sidebar menu
$("#menu-close").click(function (e) {
e.preventDefault();
$("#sidebar-wrapper").toggleClass("active");
});
I want to add functionality, so it will close if I click anywhere outside the sidebar. So far I have this:
// Close the menu on click outside of the container
$(document).click(function (e) {
var container = $("#sidebar-wrapper");
if (!container.is(e.target) // if the target of the click isn't the container...
&& container.has(e.target).length === 0 // ... nor a descendant of the container
&& event.target.id !== "menu-toggle") // for the functionality of main toggle button
{
container.removeClass("active");
}
});
But it seems to remove the "active" class this way.
Best regards.
So the solution should be that if you click anywhere inside the container the click handler should do nothing and just return. But if the click is outside the container then it should close it.
Below is the click handler code which might help you.
$(document).click(function(e) {
var node = e.target;
// loop through ancestor nodes to check if the click is inside container.
// If yes then return from the handler method without doing anything
while(node.parentNode) {
if (node === container) {
return;
}
node = node.parentNode;
}
container.removeClass('active')
});
Try this
$(document).click(function (e)
{
var container = $("#wrapper");
if (!container.is(e.target) && container.has(e.target).length === 0 && event.target.id!=="menu-toggle")
{
container.addClass("toggled");
}
});
So what basically it is doing is if e is element you want to toggle the class and if the clicked e is also that then the class wil not toggle otherwise it will.
You can use a recursive function that check if a element clicked exists in cointainer from sidebar menu:
function hasElement(node, element) {
return node == element
|| (node.childNodes || []).length && Array.from(node.childNodes)
.filter(x => x.nodeType == 1)
.some(x => hasElement(x, element));
}
$('body').click(function (event) {
var container = Array.from($("#sidebar")); //Add another containers that would be clicked wihtout close sidebar
var exists = container.some(node => hasElement(node, event.target));
if (!exists)
//TODO Close sidebar here...
});