I am working on closing toggle menu for mobiles and having a small problem. So what i want is when the toggle menu is active, user to be able to close it by touching somewhere on the screen on his device. I almost got it working, but when closed the basket in the header disappears and the menu doesn't retrieve to a hamburger icon. I am working on Wordpress website, just to notice.
I guess the problem comes from this: aria-expanded="true" , because the default value should be false after the user has closed it.
So my website is:
https://www.ngraveme.com/bg
my JQuery code is:
jQuery(document).ready(function($) {
var $menu = $('.menu');
$('.menu-toggle').click(function() {
$menu.toggle();
});
$(document).mouseup(function(e) {
if (!$menu.is(e.target) // if the target of the click isn't the container...
&&
$menu.has(e.target).length === 0) // ... nor a descendant of the container
{
$menu.hide();
}
});
});
and the original js code written from the theme i am using in wordpress is:
/**
* navigation.js
*
* Handles toggling the navigation menu for small screens.
* Also adds a focus class to parent li's for accessibility.
* Finally adds a class required to reveal the search in the handheld footer bar.
*/
(function() {
// Wait for DOM to be ready.
document.addEventListener('DOMContentLoaded', function() {
var container = document.getElementById('site-navigation');
if (!container) {
return;
}
var button = container.querySelector('button');
if (!button) {
return;
}
var menu = container.querySelector('ul');
// Hide menu toggle button if menu is empty and return early.
if (!menu) {
button.style.display = 'none';
return;
}
button.setAttribute('aria-expanded', 'false');
menu.setAttribute('aria-expanded', 'false');
menu.classList.add('nav-menu');
button.addEventListener('click', function() {
container.classList.toggle('toggled');
var expanded = container.classList.contains('toggled') ? 'true' : 'false';
button.setAttribute('aria-expanded', expanded);
menu.setAttribute('aria-expanded', expanded);
});
// Add class to footer search when clicked.
document.querySelectorAll('.storefront-handheld-footer-bar .search > a').forEach(function(anchor) {
anchor.addEventListener('click', function(event) {
anchor.parentElement.classList.toggle('active');
event.preventDefault();
});
});
// Add focus class to parents of sub-menu anchors.
document.querySelectorAll('.site-header .menu-item > a, .site-header .page_item > a, .site-header-cart a').forEach(function(anchor) {
var li = anchor.parentNode;
anchor.addEventListener('focus', function() {
li.classList.add('focus');
});
anchor.addEventListener('blur', function() {
li.classList.remove('focus');
});
});
// Add an identifying class to dropdowns when on a touch device
// This is required to switch the dropdown hiding method from a negative `left` value to `display: none`.
if (('ontouchstart' in window || navigator.maxTouchPoints) && window.innerWidth > 767) {
document.querySelectorAll('.site-header ul ul, .site-header-cart .widget_shopping_cart').forEach(function(element) {
element.classList.add('sub-menu--is-touch-device');
});
}
});
})();
Try replacing your jQuery code with this:
jQuery(document).ready(function($) {
$(document).mouseup(function(e) {
var $menuContainer = $('.menu');
var $menu = $menu.find('ul');
var $container = $('.site-navigation');
var $button = $container.find('button')
if (!$menuContainer.is(e.target) && $menuContainer.has(e.target).length === 0) {
if ($container.hasClass('toggled')) {
$button.attr('aria-expanded', false);
$menu.attr('aria-expanded', false);
}
}
});
});
It uses the vanilla-js code from the template for hiding/showing the menu, but with jQuery synthax.
Related
Hello I have a feature where when you click a link in the navigation the content of the body switches and I was combining it with a little script from this question and it works great. The only thing is that if you click a link and don't scroll the images won't show up unless you scroll at least 1px. So I was wondering if there's a fix for this. JSFiddle.
Side question: Would it be possible to wait for the content to show up until the page is fully scrolled up?
Script:
$(window).on("load",function() {
function fade(pageLoad) {
var windowTop=$(window).scrollTop(), windowBottom=windowTop+$(window).innerHeight();
var min=0, max=1, threshold=0.01;
$(".fade").each(function() {
/* Check the location of each desired element */
var objectHeight=$(this).outerHeight(), objectTop=$(this).offset().top, objectBottom=$(this).offset().top+objectHeight;
/* Fade element in/out based on its visible percentage */
if (objectTop < windowTop) {
if (objectBottom > windowTop) {$(this).fadeTo(0,min+((max-min)*((objectBottom-windowTop)/objectHeight)));}
else if ($(this).css("opacity")>=min+threshold || pageLoad) {$(this).fadeTo(0,min);}
} else if (objectBottom > windowBottom) {
if (objectTop < windowBottom) {$(this).fadeTo(0,min+((max-min)*((windowBottom-objectTop)/objectHeight)));}
else if ($(this).css("opacity")>=min+threshold || pageLoad) {$(this).fadeTo(0,min);}
} else if ($(this).css("opacity")<=max-threshold || pageLoad) {$(this).fadeTo(0,max);}
});
} fade(true); //fade elements on page-load
$(window).scroll(function(){fade(false);}); //fade elements on scroll
});
You can achieve it by taking fade method outside the load event listener that will make it available everywhere. And then call it from on the click of nav buttons.
See the js below:
$(window).on("load",function() {
this.fade(true); //fade elements on page-load
$(window).scroll(function(){this.fade(false);}); //fade elements on scroll
});
//Take below fade method outside of the load event listener.
function fade(pageLoad) {
var windowTop=$(window).scrollTop(), windowBottom=windowTop+$(window).innerHeight();
var min=0, max=1, threshold=0.01;
$(".fade").each(function() {
/* Check the location of each desired element */
var objectHeight=$(this).outerHeight(), objectTop=$(this).offset().top, objectBottom=$(this).offset().top+objectHeight;
/* Fade element in/out based on its visible percentage */
if (objectTop < windowTop) {
if (objectBottom > windowTop) {$(this).fadeTo(0,min+((max-min)*((objectBottom-windowTop)/objectHeight)));}
else if ($(this).css("opacity")>=min+threshold || pageLoad) {$(this).fadeTo(0,min);}
} else if (objectBottom > windowBottom) {
if (objectTop < windowBottom) {$(this).fadeTo(0,min+((max-min)*((windowBottom-objectTop)/objectHeight)));}
else if ($(this).css("opacity")>=min+threshold || pageLoad) {$(this).fadeTo(0,min);}
} else if ($(this).css("opacity")<=max-threshold || pageLoad) {$(this).fadeTo(0,max);}
});
}
// change activenav class, show the clicked element only and hide the others https://codepen.io/MohdHussein/pen/MWKEvdp
// grab all the buttons
let Buttons = document.querySelectorAll(".selectSection button");
// loop through the buttons using for..of
for (let button of Buttons) {
// listen for a click event
button.addEventListener('click', (e) => {
// et = event target
const et = e.target;
// slect activenav class
const activenav = document.querySelector(".activenav");
// check for the button that has activenav class and remove it
if (activenav) {
activenav.classList.remove("activenav");
}
// add activenav class to the clicked element
et.classList.add("activenav");
// select all classes with the name content
let allContent = document.querySelectorAll('.contentsec');
// loop through all content classes
for (let contentsec of allContent) {
// display the content if the class has the same data-attribute as the button
if (contentsec.getAttribute('data-number') === button.getAttribute('data-number')) {
contentsec.style.display = "block";
}
// if it's not equal then hide it.
else {
contentsec.style.display = "none";
}
}
this.fade(true); //Call fade method on click
});
}
You can test it here.
I'm stuck while implementing drop down for each individual list item on focus/hover on any of them, now on hovering over any single list item all dropdowns are getting displayed.
This is my pen: https://codepen.io/apeandme/pen/GRZOxJQ
and the JS:
// main navigation interaction
'use strict';
var navbar;
var triggerContainer;
var triggerLink;
var toggleButton;
var lastMenuItem;
var mouseOutTimer; // timer used to delay hiding of menu after mouse leaves
var mouseOutHideDelay = 0; // time (in ms) before menu is closed after mouse leaves
var menuVisible = false;
var i = 0;
window.addEventListener('DOMContentLoaded', function(e) {
navbar = document.querySelector('nav');
triggerContainer = document.querySelectorAll('nav > ul > li.with-dd');
triggerLink = document.querySelectorAll('nav > ul > li.with-dd > a');
toggleButton = document.querySelectorAll('nav > ul > li.with-dd > .toggle-button');
lastMenuItem = document.querySelectorAll('nav > ul > li.with-dd > ul > li:last-of-type > a');
// Show the menu on mouse hover of the trigger
triggerContainer.forEach(item => {
item.addEventListener('mouseover', function(e) {
showMenu();
clearTimeout(mouseOutTimer);
});
});
// Hide the menu when mouse hover leaves both the trigger and menu fly-out, but only after a short delay to help people jerky mouse movements (like those using head/eye trackers)
triggerContainer.forEach(item => {
item.addEventListener('mouseout', function(e) {
mouseOutTimer = setTimeout(function() {
hideMenu();
}, mouseOutHideDelay);
});
});
// Hide the menu when the user tabs out of it
triggerContainer.forEach(item => {
item.addEventListener('keydown', triggerKeydownHandler);
});
// Toggle the menu when the trigger is activated
toggleButton.forEach(item => {
item.addEventListener('click', toggleMenu);
});
// Close the menu when the user activates something outside the navbar.
document.body.addEventListener('click', handleBodyClick);
});
/**
Menu visibility
**/
function showMenu() {
triggerLink.forEach(item => {
item.setAttribute('aria-expanded', true);
});
toggleButton.forEach(item => {
item.setAttribute('aria-expanded', true);
});
menuVisible = true;
}
function hideMenu() {
triggerLink.forEach(item => {
item.setAttribute('aria-expanded', false);
});
toggleButton.forEach(item => {
item.setAttribute('aria-expanded', false);
});
menuVisible = false;
}
function toggleMenu() {
if (menuVisible) {
hideMenu();
} else {
showMenu();
}
}
/**
Event handlers
*/
function handleBodyClick(e) {
if (!navbar.contains(e.target)) {
hideMenu();
}
}
function triggerKeydownHandler(e) {
// Hide the menu a keyboard user tabs out of it or presses Escape
if ((e.key === 'Tab' && !e.shiftKey && e.target === lastMenuItem) || e.key == 'Escape') {
hideMenu();
// Move focus back to the menu toggle button if Escape was pressed
if (e.key == 'Escape') {
toggleButton.focus();
}
}
}
Instead of opening all the dropdowns at once when one is hovered, you need to pass the 'hover' event to the function, in order to know which one of the triggers were hovered :
triggerContainer.forEach((item) => {
item.addEventListener("mouseover", function (event) { // catch the 'hover' event
showMenu(event); // pass the event to your function
clearTimeout(mouseOutTimer);
});
});
function showMenu(event) {
event.target.setAttribute("aria-expanded", true); // know which element whas hovered
event.target.nextElementSibling.setAttribute("aria-expanded", true); // open the corresponding dropdown
menuVisible = true;
}
Thanks for the help. Have changed the code , for both showMenu and hideMenu and for mouseover and mouseout events, now individual drop downs are showing on hovering over single menu item, but the dropdowns are hiding soon enough I'm reaching to the last menu item inside the dropdown. Am I missing anything and currently this error is in the console whenever hovering over the last menu item inside the drop-down-
Uncaught TypeError: Cannot read property 'setAttribute' of null
at hideMenu (main.js:68)
at main.js:38
//main navigation interaction
"use strict";
var navbar;
var triggerContainer;
var triggerLink;
var toggleButton;
var lastMenuItem;
var mouseOutTimer; // timer used to delay hiding of menu after mouse leaves
var mouseOutHideDelay = 1000; // time (in ms) before menu is closed after mouse leaves
var menuVisible = false;
window.addEventListener("DOMContentLoaded", function (e) {
navbar = document.querySelector("nav");
triggerContainer = document.querySelectorAll("nav > ul > li.with-dd");
triggerLink = document.querySelectorAll("nav > ul > li.with-dd > a");
toggleButton = document.querySelectorAll(
"nav > ul > li.with-dd > .toggle-button"
);
lastMenuItem = document.querySelectorAll(
"nav > ul > li.with-dd > ul > li:last-of-type > a"
);
// Show the menu on mouse hover of the trigger
triggerContainer.forEach((item) => {
item.addEventListener("mouseover", function (e) {
showMenu(e);
clearTimeout(mouseOutTimer);
});
});
// Hide the menu when mouse hover leaves both the trigger and menu fly-out, but only after a short delay to help people jerky mouse movements (like those using head/eye trackers)
triggerContainer.forEach((item) => {
item.addEventListener("mouseout", function (e) {
mouseOutTimer = setTimeout(function () {
hideMenu(e);
}, mouseOutHideDelay);
});
});
// Hide the menu when the user tabs out of it
triggerContainer.forEach((item) => {
item.addEventListener("keydown", triggerKeydownHandler);
});
// Toggle the menu when the trigger is activated
toggleButton.forEach((item) => {
item.addEventListener("click", toggleMenu);
});
// Close the menu when the user activates something outside the navbar.
document.body.addEventListener("click", handleBodyClick);
});
/**
Menu visibility
**/
function showMenu(e) {
e.target.setAttribute("aria-expanded", true);
e.target.nextElementSibling.setAttribute("aria-expanded", true);
menuVisible = true;
}
function hideMenu(e) {
e.target.setAttribute("aria-expanded", false);
e.target.nextElementSibling.setAttribute("aria-expanded", false);
menuVisible = false;
}
function toggleMenu() {
if (menuVisible) {
hideMenu(e);
} else {
showMenu(e);
}
}
/**
Event handlers
*/
function handleBodyClick(e) {
if (!navbar.contains(e.target)) {
hideMenu();
}
}
function triggerKeydownHandler(e) {
// Hide the menu a keyboard user tabs out of it or presses Escape
if (
(e.key === "Tab" && !e.shiftKey && e.target === lastMenuItem) ||
e.key == "Escape"
) {
hideMenu();
// Move focus back to the menu toggle button if Escape was pressed
if (e.key == "Escape") {
toggleButton.focus();
}
}
}
I have a problem with my positioning on a mobile navigation element layout of my design.
Because I have used various Z-index’s and positioning’s when you click on the Nav button to displays mobile navigational links, part of my design is still visible and it obscures the 'return back to normal' nav button.
What I would like to do is add some more JavaScript to the code that it will remove/hide the part of my normal design that is obscuring the Nav button when clicked and then toggle back to display normally when it’s clicked again.
Here is the JS I am using:
jQuery(document).ready(function($){
//move nav element position according to window width
moveNavigation();
$(window).on('resize', function(){
(!window.requestAnimationFrame) ? setTimeout(moveNavigation, 300) : window.requestAnimationFrame(moveNavigation);
});
//mobile version - open/close navigation
$('.cd-nav-trigger').on('click', function(event){
event.preventDefault();
if($('header').hasClass('nav-is-visible')) $('.moves-out').removeClass('moves-out');
$('header').toggleClass('nav-is-visible');
$('.cd-main-nav').toggleClass('nav-is-visible');
$('.cd-main-content').toggleClass('nav-is-visible');
});
//mobile version - go back to main navigation
$('.go-back').on('click', function(event){
event.preventDefault();
$('.cd-main-nav').removeClass('moves-out');
});
//open sub-navigation
$('.cd-subnav-trigger').on('click', function(event){
event.preventDefault();
$('.cd-main-nav').toggleClass('moves-out');
});
function moveNavigation(){
var navigation = $('.cd-main-nav-wrapper');
var screenSize = checkWindowWidth();
if ( screenSize ) {
//desktop screen - insert navigation inside header element
navigation.detach();
navigation.insertBefore('.cd-nav-trigger');
} else {
//mobile screen - insert navigation after .cd-main-content element
navigation.detach();
navigation.insertAfter('.cd-main-content');
}
}
function checkWindowWidth() {
var mq = window.getComputedStyle(document.querySelector('header'), '::before').getPropertyValue('content').replace(/"/g, '').replace(/'/g, "");
return ( mq == 'mobile' ) ? false : true;
}
});
So what I would like to do is add to this a function the removes the div: “#bar_bg_default” onclick and then displays it again when its clicked again.
Something like this would work, but how would I fit it into my Nav JS code?
<script type="text/javascript">
<!--
function toggle_visibility(id) {
var e = document.getElementById(id);
if(e.style.display == 'block')
e.style.display = 'none';
else
e.style.display = 'block';
}
//-->
</script>
Click here to toggle visibility of element #bar_bg_default
<div id="bar_bg_default">This is foo</div>
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...
});
I'm currently using this following script to operate my dropdown menu so that when a user clicks the menu item the drop appears and they can currently click anywhere outside the screen to make the menu disappear but i was wondering how id go about adding functionality to make the dropdown disappear by clicking the menu item again (while keeping the option to click anywhere outside the item). Is this hard to implement?
$(document).ready(function() {
/* for keeping track of what's "open" */
var activeClass = 'dropdown-active', showingDropdown, showingMenu, showingParent;
/* hides the current menu */
var hideMenu = function() {
if(showingDropdown) {
showingDropdown.removeClass(activeClass);
showingMenu.hide();
}
};
/* recurse through dropdown menus */
$('.dropdown').each(function() {
/* track elements: menu, parent */
var dropdown = $(this);
var menu = dropdown.next('div.dropdown-menu'), parent = dropdown.parent();
/* function that shows THIS menu */
var showMenu = function() {
hideMenu();
showingDropdown = dropdown.addClass('dropdown-active');
showingMenu = menu.show();
showingParent = parent;
};
/* function to show menu when clicked */
dropdown.bind('click',function(e) {
if(e) e.stopPropagation();
if(e) e.preventDefault();
showMenu();
});
/* function to show menu when someone tabs to the box */
dropdown.bind('focus',function() {
showMenu();
});
});
/* hide when clicked outside */
$(document.body).bind('click',function(e) {
if(showingParent) {
var parentElement = showingParent[0];
if(!$.contains(parentElement,e.target) || !parentElement == e.target) {
hideMenu();
}
}
});
});
Here is my attempt based on the answer below:
/* function to show menu when clicked */
dropdown.bind('click',function(e) {
if (!dropdown.data('open')) {
dropdown.data'(open', true);
// open menu
} else {
dropdown.data('open', false);
// close menu
}
});
Although im getting errors so i assume ive played it in incorrectly or perhaps overwritten something? This is just a section from the code above as it's the only section ive edited with this answer.
Thanks
What you can do is use jQuery.data() and have a variable called open for example which is true or false depending on the state. So you could be something like this.
if (!dropdown.data('open')) {
dropdown.data'(open', true);
// open menu
} else {
dropdown.data('open', false);
// close menu
}