Keep sub menu open on hover with javascript - javascript

Description : I got a nav bar with my menu and submenu. I use javascript to make the submenu appear on click
Here is my html
<ul class="menu grid-x">
<div class="logo align-self-middle">
LOGO
</div>
<li class="item has-submenu align-self-middle grid-x align-middle">
<a tabindex="0">Société</a>
<ul class="submenu">
<li class="subitem text-left">Présentation</li>
<li class="subitem text-left">Nous rejoindre</li>
<li class="subitem has-submenu text-left"><a tabindex="0">Nos agences</a></li>
<li class="subitem text-left">Nos actualités</li>
</ul>
</li>
<li class="item has-submenu align-self-middle">
<a tabindex="0">Téléphonie</a>
<ul class="submenu">
<li class="subitem text-left">Téléphonie entreprise</li>
<li class="subitem text-left">Flotte mobile</li>
<li class="subitem text-left">Communications unifiées</li>
<li class="subitem text-left">Centre de contact virtuel</li>
<li class="subitem text-left">Centre de contact Multicanal</li>
</ul>
</li>
<ul>
And here is the javascript I use
const items = document.querySelectorAll(".item");
const menu = document.querySelector(".menu");
/* Activate Submenu */
function toggleItem() {
if (this.classList.contains("submenu-active")) {
this.classList.remove("submenu-active");
} else if (menu.querySelector(".submenu-active")) {
menu.querySelector(".submenu-active").classList.remove("submenu-active");
this.classList.add("submenu-active");
} else {
this.classList.add("submenu-active");
}
}
/* Event Listeners */
for (let item of items) {
if (item.querySelector(".submenu")) {
item.addEventListener("click", toggleItem, false);
item.addEventListener("keypress", toggleItem, false);
}
}
And the CSS to make the submenu appear when parent receive the submenu-active class
submenu-active .submenu {
display: block;
position: absolute;
left: 0;
top: 100%;
min-width: 200px;
}
Problem : I would like to make the submenu appear on hover. The mouseover works when I hover the item class div, but submenu disappear when I try to click on one of its elements because I'm not maintaining the item div hover.
I don't want to use jquery and can't figure out a solution. Anyone could help ?
Thanks a lot

Try this code! and add style as you want.
You can include more mouse events add class on hover OR click is up to you.
const items = document.querySelectorAll(".item");
const menu = document.querySelector(".menu");
items.forEach(item => {
item.addEventListener('click', function() {
if ( !item.classList.contains("show") ){
item.classList.add("show")
}else {
item.classList.remove("show")
}
})
})
.has-submenu {
position: relative; }
.submenu {
display: none;
background-color: #ffffff;
position: absolute;
left: 0;
top: 100%;
min-width: 200px;
}
.has-submenu.show .submenu {
display: block ;
}
<ul class="menu grid-x">
<div class="logo align-self-middle">
LOGO
</div>
<li class="item has-submenu align-self-middle grid-x align-middle ">
<a tabindex="0">Société</a>
<ul class="submenu ">
<li class="subitem text-left">Présentation</li>
<li class="subitem text-left">Nous rejoindre</li>
<li class="subitem has-submenu text-left"><a tabindex="0">Nos agences</a></li>
<li class="subitem text-left">Nos actualités</li>
</ul>
</li>
<li class="item has-submenu align-self-middle">
<a tabindex="0">Téléphonie</a>
<ul class="submenu">
<li class="subitem text-left">Téléphonie entreprise</li>
<li class="subitem text-left">Flotte mobile</li>
<li class="subitem text-left">Communications unifiées</li>
<li class="subitem text-left">Centre de contact virtuel</li>
<li class="subitem text-left">Centre de contact Multicanal</li>
</ul>
</li>
<ul>

Related

How to change dropdown arrow image with javascript

I am trying to make a dropdown navigation menu where I am using arrow up and down images for the dropdown links. What I want is that when I click on a dropdown link means when I open the submenu, the arrow image should be the arrow-up image and when I close the submenu, it will go back to the arrow-down image. I have more than one dropdown link with the arrow images. The problem is that when I click on one of the dropdown links, the other link with an arrow image also gets triggered and changes. But I only want to change the image of one that I clicked. You can see and check the code below.
const nav__items = document.querySelectorAll(".nav__items");
const nav__list = document.querySelector(".nav__list");
function toggleSubMenu() {
if (this.classList.contains("nav__list--sub-active")) {
this.classList.remove("nav__list--sub-active")
} else if (nav__list.querySelector(".nav__list--sub-active")) {
nav__list
.querySelector(".nav__list--sub-active")
.classList.remove("nav__list--sub-active");
this.classList.add("nav__list--sub-active");
} else {
this.classList.add("nav__list--sub-active");
}
}
function toggleArrowUpDown() {
if (this.classList.contains("nav__list--sub-active")) {
document.querySelectorAll('.arrow_down').forEach((arrow) => {
arrow.src = "https://i.postimg.cc/Bb4ttXfk/arrow-up.png"
})
} else {
document.querySelectorAll('.arrow_down').forEach((arrow) => {
arrow.src = "https://i.postimg.cc/Hn9ctbtT/arrow-down.png"
})
}
}
for (let item of nav__items) {
if (item.querySelector(".nav__list--sub")) {
item.addEventListener('click', toggleSubMenu, false);
item.addEventListener('click', toggleArrowUpDown, false);
}
}
* {
box-sizing: border-box;
text-decoration: none;
list-style: none;
}
.nav__list {
margin: 0;
padding: 0;
}
.nav__items {
padding-bottom: 2em;
}
.nav__list--sub {
display: none;
}
.nav__list--sub-active .nav__list--sub {
display: block;
}
<nav class="nav">
<div class="container">
<ul class="nav__list">
<li class="nav__items">
Home
</li>
<li class="nav__items has-submenu">
<a tabindex="0">About <img src="https://i.postimg.cc/Hn9ctbtT/arrow-down.png" class="arrow_down" alt=""
/></a>
<ul class="nav__list--sub">
<li class="nav__items--sub sub-item">
<a href="#">Public Engagement
</a>
<ul class="nav__list--sub-sub">
<li>Cultural Map</li>
<li>Child Culture</li>
<li>Initiatives</li>
<li>Membership</li>
</ul>
</li>
<li class="nav__items--sub">Events</li>
<li class="nav__items--sub">
Publications
</li>
</ul>
</li>
<li class="nav__items has-submenu">
<a tabindex="0">Programs & Initiatives <img src="https://i.postimg.cc/Hn9ctbtT/arrow-down.png" class="arrow_down" alt=""
/></a>
<ul class="nav__list--sub">
<li class="nav__items--sub sub-item">
<a href="#">Public Engagement
</a>
</li>
<li class="nav__items--sub sub-item">
<a href="#">Events
</a>
</li>
<li class="nav__items--sub">
Publications
</li>
</ul>
</li>
</ul>
</div>
</nav>
Instead of document.querySelectorAll in your toggleArrowUpDown() method, change that to this.querySelectorAll so you're only querying on this instead of the entire document.
But you can also take this one step further and use CSS classes to manage the display, so you only need to toggle the class.
const nav__items = document.querySelectorAll(".nav__items");
const nav__list = document.querySelector(".nav__list");
function toggleSubMenu() {
if (this.classList.contains("nav__list--sub-active")) {
this.classList.remove("nav__list--sub-active")
} else if (nav__list.querySelector(".nav__list--sub-active")) {
nav__list
.querySelector(".nav__list--sub-active")
.classList.remove("nav__list--sub-active");
this.classList.add("nav__list--sub-active");
} else {
this.classList.add("nav__list--sub-active");
}
}
function toggleArrowUpDown() {
let spanClasses = this.querySelector('span').classList;
if (this.classList.contains("nav__list--sub-active")) {
spanClasses.remove('arrow_down');
spanClasses.add('arrow_up');
} else {
spanClasses.remove('arrow_up');
spanClasses.add('arrow_down');
}
}
for (let item of nav__items) {
if (item.querySelector(".nav__list--sub")) {
item.addEventListener('click', toggleSubMenu, false);
item.addEventListener('click', toggleArrowUpDown, false);
}
}
* {
box-sizing: border-box;
text-decoration: none;
list-style: none;
}
.nav__list {
margin: 0;
padding: 0;
}
.nav__items {
padding-bottom: 2em;
}
.nav__list--sub {
display: none;
}
.nav__list--sub-active .nav__list--sub {
display: block;
}
.arrow_down {
background-image: url('https://i.postimg.cc/Hn9ctbtT/arrow-down.png');
width: 12px;
height: 9px;
display: inline-block;
background-repeat: no-repeat;
}
.arrow_up {
background-image: url('https://i.postimg.cc/Bb4ttXfk/arrow-up.png');
width: 12px;
height: 9px;
display: inline-block;
background-repeat: no-repeat;
}
<nav class="nav">
<div class="container">
<ul class="nav__list">
<li class="nav__items">
Home
</li>
<li class="nav__items has-submenu">
<a tabindex="0">About <span class="arrow_down" alt=""
></span></a>
<ul class="nav__list--sub">
<li class="nav__items--sub sub-item">
<a href="#">Public Engagement
</a>
<ul class="nav__list--sub-sub">
<li>Cultural Map</li>
<li>Child Culture</li>
<li>Initiatives</li>
<li>Membership</li>
</ul>
</li>
<li class="nav__items--sub">Events</li>
<li class="nav__items--sub">
Publications
</li>
</ul>
</li>
<li class="nav__items has-submenu">
<a tabindex="0">Programs & Initiatives <span class="arrow_down" alt=""
></span></a>
<ul class="nav__list--sub">
<li class="nav__items--sub sub-item">
<a href="#">Public Engagement
</a>
</li>
<li class="nav__items--sub sub-item">
<a href="#">Events
</a>
</li>
<li class="nav__items--sub">
Publications
</li>
</ul>
</li>
</ul>
</div>
</nav>

Auto collapse sidebar menu list when lost focus

I just want to ask :
how to make the dropdown-menu collpase when the dropdown menu is active?
When the mouse hovers to the sidebar, the active dropdown menu expanded again?
i think i just wondering using hover but it doesnt work when i try it, so i hope someone can help me to solve this?
Simple Concept I Just want :
Mouse:hover to sidebar-icon/burger is clicked => sidebar-expanded => menu list that have:submenu clicked => dropdown-menu displayed => mouseOut from sidebar => icon is-collapsed
Mouse:hover to sidebar-icon again / sidebar:state(active) => dropdown menu is already expanded
well i dont know that you can understand my question or something.. but i hope u can make this auto collapse menu
$(document).ready(function() {
// Sidebar links
$('.sidebar .side-list li a').on('click', function() {
const $this = $(this);
if ($this.parent().hasClass('buka')) {
$this
.parent()
.children('.dropdown-menu')
.slideUp(200, () => {
$this.parent().removeClass('buka');
});
} else {
$this
.parent()
.parent()
.children('li.buka')
.children('.dropdown-menu')
.slideUp(200);
$this
.parent()
.parent()
.children('li.buka')
.children('a')
.removeClass('buka');
$this
.parent()
.parent()
.children('li.buka')
.removeClass('buka');
$this
.parent()
.children('.dropdown-menu')
.slideDown(200, () => {
$this.parent().addClass('buka');
});
}
});
// ٍSidebar Toggle
$('.burger').on('click', e => {
$('.konten').toggleClass('k-kebuka');
$('.sidebar').toggleClass('s-kebuka');
e.preventDefault();
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="sidebar" class="sidebar bg-cerah">
<div class="sidebar-dalem">
<div class="side-konten">
<ul class="side-list">
<li class="side-item dropdown">
<a class="dropdown-toggle" href="javascript:void(0)">
<span class="side-ikon">ICON</span>
<span class="side-judul">MENU</span>
<span class="panah">[>]</span>
</a>
<ul class="dropdown-menu">
<li>
SUBMENU #1
</li>
<li>
SUBMENU #2
</li>
</ul>
</li>
<li class="side-item dropdown">
<a class="dropdown-toggle" href="javascript:void(0)">
<span class="side-ikon">[X]</span>
<span class="side-judul">MENU #2</span>
<span class="panah">[>] ></span>
</a>
<ul class="dropdown-menu">
<li>
SUBMENU #1
</li>
<li>
SUBMENU #2
</li>
</ul>
</li>
<li class="side-item">
<a class="side-link" href="javascript:void(0)">
<span class="side-ikon">[X]</span>
<span class="side-judul">MENU #3</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<div id="konten" class="konten">
<div class="konten-navbar sticky-top">
<ul class="navbar-kiri">
<li>
<a id="burger" href="javascript:void(0)" class="burger">BURGER ICO</a>
</li>
<li>
SEARCH ICO
</li>
</ul>
</div>
</div>
In pure CSS, .menu:hover definitely works, but you have to build the entire menu inside of the element that has the :hover pseudo class and also either touching or overlapping:
.popup {
position: relative;
}
button {
width: 8em;
height: 40px;
}
ul {
position: absolute;
top: 36px;
left: 8px;
background: #eee;
border: 1px solid black;
display: none;
margin: 0;
width: 14em;
min-height: 8em;
padding: 0.5em;
list-style: none;
}
.popup:hover ul {
display: block;
}
<div>
<div class="popup"><button>Menu</button>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
</div>
Certainly, you'd need to style everything properly.
I also seem to remember that JQuery has OnMouseEnter and OnMouseLeave events that work kind of like :hover too.

togggle menu need to open on click and close itself when click the other menu

How can make it make
1- open toggle menu when on click menu (not opened by default)
2- close itself - if click other parent menu
trying to make it work for the project
and i copied from cssdesk
$('.inbox li').click(function(e) {
$('.inbox li.active').removeClass('active');
var $this = $(this);
if (!$this.hasClass('active')) {
$this.addClass('active');
}
e.preventDefault();
});
$(document).ready(function () {
$('.tree-toggler').click(function () {
$(this).parent().children('ul.tree').toggle(300);
});
});
<link class="cssdeck" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.1/css/bootstrap-responsive.min.css" class="cssdeck">
<ul class="inbox-nav nav" style="border-top: 1px solid #eef1f5; margin-top:10px;" >
<li class="">
<a href="javascript:;" id="fldr3">menu 1
</li>
<li class="">
<a data-title="Inbox" data-type="important" href="javascript:;">2nd Menu</a>
</li>
<li class="">
<a data-title="Sent" data-type="sent" href="javascript:;">menu2</a>
</li>
<li class="divider"></li>
<li class="active tree-toggler">
Toggle menu</li>
<ul class="nav nav-list tree">
<li class="">Link</li>
<li class="">Link</li>
</ul>
</li>
</ul>
<script class="cssdeck" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script class="cssdeck" src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
If user click is not neccesary you can do it by CSS, this is an example the 4th menu item has sub menu items
.menu,
.menu-item {
display: inline;
cursor: default;
}
.menu-item.has-subItem {
position: absolute;
}
.menu-item ul {
display: none;
position: absolute;
top: 15px;
left: 0;
padding: 0;
}
.menu-item:hover ul {
display: inline;
}
<ul class='menu'>
<li class='menu-item'>Item 1</li>
<li class='menu-item'>Item 2</li>
<li class='menu-item'>Item 3</li>
<li class='menu-item has-subItem'>Item 4
<ul>
<li class='sub-item'>sub 1</li>
<li class='sub-item'>sub 2</li>
<li class='sub-item'>sub 3</li>
</ul>
</li>
</ul>
In your $('.inbox li').click() function use jquery's .hide() function on the ul.tree element. Since .hide() won't do anything if the element is already hidden, you don't need to check if it needs to be hidden.
(Similarly, you can pull the $this.addClass('active'); statement out of the if, since it simply won't do anything if the class is already added!)

How to properly structure lists with many sub-levels

I have a large navigation menu made using li and ul tags. The problem I have is that if I try to style individual items, it becomes very challenging because each li tag has multiple ul and li elements within it and therefore the style gets applied to everything within it. For example, I'm trying to highlight the first list item "SharePoint Demo Website" but that first item contains all the other items, it's an expandable/collapsible menu).
HTML:
<ul id="expList" class="list">
<li title="Sharepoint Demo Website" value="https://demo.ca" class="collapsed expanded active">Sharepoint Demo Website
<ul style="display: block;">
<li title="Academic" value="https://demo.ca/academic" class="collapsed">Academic
<ul style="display: none;">
<li title="Board Meetings" value="https://demo.ca/academic/bm">Board Meetings</li>
<li title="Committee" value="https://demo.ca/academic/cmtte">Committee</li>
<li title="Document Management" value="https://demo.ca/academic/dm">Document Management</li>
<li title="Project Management" value="https://demo.ca/academic/pm">Project Management</li>
</ul>
</li>
<li title="Archive" value="https://demo.ca/archive">Archive</li>
<li title="Associations" value="https://demo.ca/associations" class="collapsed">Associations
<ul style="display: none;">
<li title="Board Meetings" value="https://demo.ca/associations/bm">Board Meetings</li>
<li title="Document Management" value="https://demo.ca/associations/dm">Document Management</li>
<li title="Project Management" value="https://demo.ca/associations/pm">Project Management</li>
</ul>
</li>
<li title="Developer" value="https://demo.ca/cdn">Developer</li>
<li title="Person test" value="https://demo.ca/cf_test">Person test</li>
<li title="Charity" value="https://demo.ca/charity" class="collapsed">Charity
<ul style="display: none;">
<li title="Board of Directors" value="https://demo.ca/charity/bod" class="collapsed">Board of Directors
<ul style="display: none;"><li title="Board Documents" value="https://demo.ca/charity/bod/boarddocs">Board Documents</li>
<li title="Meeting Materials" value="https://demo.ca/charity/bod/mtgmaterial">Meeting Materials</li>
</ul>
</li>
</ul>
</li>
<li title="demo" value="https://demo.ca/clite" class="collapsed">demo
<ul style="display: none;"><li title="administrator" value="https://demo.ca/clite/admin">administrator
</li>
</ul>
</li>
<li title="Company" value="https://demo.ca/company" class="collapsed">Company
<ul style="display: none;"><li title="Finance" value="https://demo.ca/company/finance">Finance</li>
<li title="Human Resources" value="https://demo.ca/company/hr" class="collapsed">Human Resources
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
CSS:
#expList ul li:hover{
background-color: #eee;
}
This is what it looks like:
This is the code that creates the list:
function traverseMap(obj, element) {
for (var key in obj) {
var item = obj[key];
var li = $('<li>', {
text: item.title,
title: item.title,
value: item.url
}).appendTo(element);
if (!$.isEmptyObject(item.children)) {
var ul = $('<ul>').appendTo(li);
traverseMap(item.children, ul);
}
}
}
traverseMap(map, $('#expList'));
}
You can use > to affect only immediate children, like this:
#expList ul > li:hover{
background-color: #eee;
}
or this:
#expList > ul > li:hover{
background-color: #eee;
}
or use the :first-child pseudo selector for just the first child:
#expList ul li:first-child:hover
{
background-color:yellow;
}
depending on what you want. You could also use class names.
The way you have right now it won't work, because the whole node will get highlighted, what you need to do is to give an extra mark up to the text you wanna highlight, for example :
<li title="Academic" value="https://demo.ca/academic" class="collapsed">
<span>Academic</span>
<ul style="display: none;">
<li title="Board Meetings" value="https://demo.ca/academic/bm">Board Meetings</li>
<li title="Committee" value="https://demo.ca/academic/cmtte">Committee</li>
<li title="Document Management" value="https://demo.ca/academic/dm">Document Management</li>
<li title="Project Management" value="https://demo.ca/academic/pm">Project Management</li>
</ul>
</li>
then :
#expList ul > li:hover > span{
background-color: #eee;
}
Given that <li title="Sharepoint Demo Website" value="https://demo.ca" class="collapsed expanded active"> contains a text node and children elements, a rule that styles the li will also apply to its children.
If you want to style the text node ("Sharepoint Demo Website") independently you will have to wrap it in an element which you can target specifically.
<ul id="expList" class="list">
<li title="Sharepoint Demo Website" value="https://demo.ca" class="collapsed expanded active">
<span>Sharepoint Demo Website</span>
<ul style="display: block;">
<li title="Academic" value="https://demo.ca/academic" class="collapsed">Academic
<ul style="display: none;">
You could now apply a rule to the text node via the span
#expList li > span {
background-color:#fee;
}
See jsfiddle.

click blank area go back to selected section

<li class="list ">A
<ul class="names">
<li class="list">1
</li>
<li class="list">2
</li>
</ul>
</li>
<li class="list ">B
<ul class="names selected">
<li class="list selected">1
</li>
<li class="list">2
</li>
<li class="list">3
</li>
<li class="list">4
</li>
</ul>
</li>
<li class="list ">C
<ul class="names">
<li class="list">1
</li>
<li class="list">2
</li>
<li class="list">3
</li>
<li class="list">4
</li>
</ul>
</li>
$('.list').click(function () {
var that = this;
$('.list').each(function () {
if (that == this) return true; //continue
$('.names:not(:hidden)', this).slideToggle();
});
$('ul.names', this).slideToggle();
})
ul.names{display: none;}
li.list{
width:150px;
background:#A9FF7A;
}
ul.names {
width:150px;
background:#A9FF7A;
}
ul.selected{
display: block;
}
li.selected{
background:red;
}
online Sample: http://jsfiddle.net/gyYyd/
B's submenu 1 is highlighted. If I click on menu A or C, then A or C section will be opened, but how do I click PAGE BLANK area (outside of the background color) to go back to B section (to open B section)
Thanks in advance
You can capture clicks on the document object and trigger a click on the required list item.
$(document).click(function() {
var selected = $('.selected:first');
if(!selected.closest('ul.names').is(':visible')) {
selected.closest('.list').trigger('click');
}
});
Also, make sure to return false from your current list item click handler - so that normal clicks on list items don't propagate to the above handler.
DEMO: http://jsfiddle.net/gyYyd/2/

Categories

Resources