I want to start by apologizing for such a long question, I just hope I wont make it difficult to understand as a result.
I have created a side bar with three Menu elements in an UL which expand to show child elements, change background color and remove hover effect when clicked. I did this by defining a function that adds and remove classes containing relevant properties when the menu element is clicked.
The four specific things that I want the sidebar to do but cant seem to get it to do are as follows;
I think where I'm having most trouble with my code is in the adding and removal of classes especially given that the <a>
Tags which are nested inside the <li>
Tags (clicked elements) are where the "hover" class needs to be added/removed, as well as the <ul>
Tags that expand are also nested inside the clicked elements.
function toggleMenu(e) { var kids = document.querySelector("#menuList").children; var unselectedLink = document.querySelectorAll(".unselected a"); var unselectedDropdown = document.querySelectorAll(".unselected ul"); //adds "unselected" class to all elements exept the selected one for (var i = 0; i < kids.length; i++) { kids[i].className = "unselected"; } //adds "menuHover" class to all elements exept the selected element for (var i = 0; i < unselectedLink.length; i++) { unselectedLink[i].className = "menuHover"; } for (var i = 0; i < unselectedDropdown.length; i++) { unselectedDropdown[i].classList.remove("show") } //adds "selected" class, removes "menuHover" class and adds "toggle" to the selected element e.className = "selected"; document.querySelector(".selected a").classList.remove("menuHover"); document.querySelector(".selected ul").classList.toggle("show"); }
.sidebar { position: fixed; width: 250px; height: 100%; left: 0px; top: 0; background: #1b1b1b; font-family: sans-serif; }.menu-bar { background: #1b1b1b; height: 60px; display: flex; align-items: center; padding-left: 42px; }.side-text { color: #C5C5C5; font-weight: bold; font-size: 20px; } nav ul { background: #1b1b1b; height: 100%; width: 100%; list-style: none; margin-left: 0; padding-left: 0; } nav ul li { line-height: 40px; } nav ul li a { position: relative; color: #C5C5C5; text-decoration: none; font-size: 14px; padding-left: 43px; font-weight: normal; display: block; width: 100%; } nav ul ul { position: static; display: none; } nav ul ul li a { font-family: sans-serif; font-size: 13px; color: #e6e6e6; padding-left: 80px; font-weight: lighter; }.submenu-item:hover { background: #1e1e1e;important. } /*...........selected and show..................*/:selected { background-color; #255DAA. }:show { display; block. } /*...........unselected and hover..................*/:unselected { color; #1e1e1e. }:menuHover:hover { color; #255DAA; }
<nav class="sidebar"> <div class="menu-bar"> <label class="side-text">MENU</label> </div> <ul id="menuList"> <li class="selected" onclick="toggleMenu(this)"> <a href="#" class="" id="staff-btn">Staff</a> <ul> <li><a href="#">New Staff</a></li> <li><a href="#">View Staff</a></li> </ul> </li> <li class="unselected" onclick="toggleMenu(this)"> <a href="#" id="notes-btn" class="menuHover">Notes</a> <ul> <li><a href="#">New Note</a></li> <li><a href="#">Edit Notes</a></li> </ul> </li> <li class="unselected" onclick="toggleMenu(this)"> <a href="#" class="menuHover" id="tasks-btn">Tasks</a> <ul> <li><a href="#">New Tasks</a></li> <li><a href="#">Edit Task</a></li> </ul> </li> </nav>
I am quite close but somehow, logically I am doing things the wrong way with the JavaScript, so any adjustments to the code to make it reach all four of the above goals will be much appreciated. thanks
A simple way to do this:
If the element was selected, just unselect it
If it wasnt, unselect all elements and select the clicked element
function toggleMenu(el) {
if (el.classList.contains("selected")) {
el.classList.remove("selected");
el.classList.add("unselected");
}
else {
for (const child of document.getElementById("menuList").children) {
child.classList.remove("selected");
child.classList.add("unselected");
}
el.classList.remove("unselected");
el.classList.add("selected");
}
}
Edit 1 You can use the following css to unhide the submenu of a selected menuitem:
.selected ul {
display: block;
}
Edit 2 I went to the trouble of actually implementing it.
function toggleMenu(el) { if (el.classList.contains("selected")) { el.classList.remove("selected"); el.classList.add("unselected"); } else { for (const child of document.getElementById("menuList").children) { child.classList.remove("selected"); child.classList.add("unselected"); } el.classList.remove("unselected"); el.classList.add("selected"); } }
.sidebar { position: fixed; width: 250px; height: 100%; left: 0px; top: 0; background: #1b1b1b; font-family: sans-serif; }.menu-bar { background: #1b1b1b; height: 60px; display: flex; align-items: center; padding-left: 42px; }.side-text { color: #C5C5C5; font-weight: bold; font-size: 20px; } /* menu */.menu { background: #1b1b1b; height: 100%; width: 100%; list-style: none; margin-left: 0; padding-left: 0; }.menu-item { line-height: 40px; }.menu-item a { position: relative; color: #C5C5C5; text-decoration: none; font-size: 14px; padding-left: 43px; font-weight: normal; display: block; width: 100%; } /* submenu */.submenu { position: static; display: none; list-style: none; }.submenu-item a { font-family: sans-serif; font-size: 13px; color: #e6e6e6; padding-left: 80px; font-weight: lighter; }.submenu-item:hover { background: #1e1e1e; } /* selected and unselected */.selected { background-color: #255DAA; }.selected.submenu { display: block; }.unselected { color: #1e1e1e; }.unselected:hover a { color: #255DAA; }
<nav class="sidebar"> <div class="menu-bar"> <label class="side-text">MENU</label> </div> <ul class="menu" id="menuList"> <li class="menu-item selected" onclick="toggleMenu(this)"> <a href="#" id="staff-btn">Staff</a> <ul class="submenu"> <li class="submenu-item"><a href="#">New Staff</a></li> <li class="submenu-item"><a href="#">View Staff</a></li> </ul> </li> <li class="menu-item unselected" onclick="toggleMenu(this)"> <a href="#" id="notes-btn">Notes</a> <ul class="submenu"> <li class="submenu-item"><a href="#">New Note</a></li> <li class="submenu-item"><a href="#">Edit Notes</a></li> </ul> </li> <li class="menu-item unselected" onclick="toggleMenu(this)"> <a href="#" id="tasks-btn">Tasks</a> <ul class="submenu"> <li class="submenu-item"><a href="#">New Tasks</a></li> <li class="submenu-item"><a href="#">Edit Task</a></li> </ul> </li> </ul> </nav>
As alternative to the accepted answer, here two different approaches I was working on...
#menuList li
is a container for a list of <a>
menu items which have adjacent <ul>
sub-menu-items. For easy selection with CSS I assigned class .menu-item
to those <a>
.
The CSS logic for both versions is essentially equal:
.menu-item
adjacent ul
sub-menu-items hidden by default:hover
colors.menu-item
and adjacent ul
(either :focus
or .selected
is true).menu-item
adjacent ul
sub-menu-items visible when a .menu-item
gets selected (ditto) Difference: for CSS only we use the :focus
selector, for CSS with Javascript we use class .selected
.
CSS only (automatic focus and blur )
An <a>
gets focus when clicked (like button, input, etc. :focus
is true ). When the user clicks/taps outside the focussed element it automatically loses focus again (gets blurred and :focus
is false , as in :not(:focus) = 'blur' ). We can use the CSS :focus
selector to handle user clicks and modify elements, MDN: ':focus' .
CSS with Javascript ( focus and blur on request)
The OP wants a selected .menu-item
and its adjacent ul
sub-menu-items to stay visible until the user specifically deselects it again. This cannot be done with the :focus
selector, so we ignore that selector and use class .selected
instead to handle focus and blur requirements ourselves, MDN: HTMLElement.blur() .
The Javascript logic is fairly straightforward:
Attach a 'click'- eventListener
( MDN: Element: click event ) to main container #menuList
handling:
.menu-item
gets selected and it is the currently .selected
then blur it ( menuItemBlur()
).menu-item
open, blur that first ( menuItemBlur()
).menu-item
( menuItemFocus()
)Changes to OP code
class
attributes from HTMLhref="#"
in <#menuList li a>
to href="javascript:void(0)"
to prevent it from creating an entry in the browser history (sub-menu-items will still create an entry).The below snippet is heavily commented and should be self-explanatory.
'use-strict'; var activeItem; // Holds the currently '.selected''.submenu' (null/undefined if none) // Attach 'click' event listener to the #menuList document.getElementById('menuList').addEventListener('click', function(e) { menuItemToggle(e.target) }); function menuItemToggle(el) { if (el.classList.contains('menu-item2')) { // When a '.menu-item' gets clicked (not its kids) if (el.classList.contains('selected')) { // and it is the '.selected''.menu-item' menuItemBlur(el); // then close it and remove focus() } else { if (activeItem) // When there is a currently selected '.menu-item' menuItemBlur(activeItem); // then deactivate it menuItemFocus(el); // Now activate the clicked `.menu-item` }; }; function menuItemBlur(el) { el.classList.remove("selected"); // Set the '.menu-item' to not '.selected' activeItem = null; // and remove the reference to it el.blur(); // Remove focus from element for CSS ':focus' //...extend with other 'Blur' stuff... }; function menuItemFocus(el) { el.classList.add("selected"); // Set the '.menu-item' to '.selected' activeItem = el; // and save a reference to it //...extend with other 'Focus' stuff... }; };
.sidebar { position: fixed; width: 250px; height: 100%; left: 0px; top: 0; background: #1b1b1b; font-family: sans-serif; }.menu-bar { background: #1b1b1b; height: 60px; display: flex; align-items: center; padding-left: 42px; }.side-text { color: #c5c5c5; font-weight: bold; font-size: 20px; } nav ul { background: #1b1b1b; height: 100%; width: 100%; list-style: none; margin-left: 0; padding-left: 0; } nav ul li { line-height: 40px; } nav ul li a { position: relative; color: #c5c5c5; text-decoration: none; font-size: 14px; padding-left: 43px; font-weight: normal; display: block; width: 100%; } nav ul ul { position: static; display: none; } nav ul ul li a { font-family: sans-serif; font-size: 13px; color: #e6e6e6; padding-left: 80px; font-weight: lighter; } /*************/ /* ADDED CSS */ /*************/ /* All classes starting with "menu-item" */ [class^="menu-item"] + ul { display: none } /* hide adjacent UL */ [class^="menu-item"]:hover { color: #255daa } /* hover color */ a + ul li a:hover { color: #c5c5c5; background-color: #1b1b1b } /* menu-item adjacent sub-menu-items hover colors Here the generic form is used, but it would probably be more clear to be specific and use: - either.menu-item1:focus + ul li a:hover - or.menu-item2.selected + ul li a:hover */ /* ':focus' version This version uses the CSS ':focus' without any Javascript. Main difference with the '.selected' version below is that when the user clicks outside the '.menu-item', the '.menu-item' looses focus and therefore gets hidden again (as:focus is no longer true). */.menu-item1:focus, .menu-item1:focus + ul { color: #e6e6e6; background-color: #255DAA } /* focus colors */.menu-item1:focus + ul { display: block } /* show adjacent UL */ /* '.selected' version, with Javascript. Basically the same CSS, but now using class '.selected' instead of ':focus'. Closing occurs only on user specific 'click'. */.menu-item2.selected, .menu-item2.selected + ul { color: #e6e6e6; background-color: #255DAA } /* focus colors */.menu-item2.selected + ul { display: block } /* show adjacent UL */ /*********************/ /* for demo use only */ /*********************/ nav h3 { color: rgba(100, 149, 237,.9); /* CornflowerBlue */ font-style: italic; padding-left: 43px; }.anchor { color: white; padding-left: 43px; }.content { font-size: 1.5rem; margin: 5rem 300px; } /* preferred globals */ html,body { box-sizing: border-box; width: 100%; max-width: 100% } *::before,*::after, * { box-sizing: inherit } body { margin: 0 }
<nav class="sidebar"> <div class="menu-bar"> <label class="side-text">MENU</label> </div> <h3>test</h3> <a class="anchor" href="javascript:void(0)">some '.sidebar' <a></a> <ul id="menuList"> <h3>:focus version</h3> <li> <a class="menu-item1" href="javascript:void(0)">Staff</a> <ul> <li><a href="#">New Staff</a></li> <li><a href="#">View Staff</a></li> </ul> </li> <li> <a class="menu-item1" href="javascript:void(0)">Notes</a> <ul> <li><a href="#">New Note</a></li> <li><a href="#">Edit Notes</a></li> </ul> </li> <li> <a class="menu-item1" href="javascript:void(0)">Tasks</a> <ul> <li><a href="#">New Tasks</a></li> <li><a href="#">Edit Task</a></li> </ul> </li> <h3>.selected version</h3> <li> <a class="menu-item2" href="javascript:void(0)">Staff</a> <ul> <li><a href="#">New Staff</a></li> <li><a href="#">View Staff</a></li> </ul> </li> <li> <a class="menu-item2" href="javascript:void(0)">Notes</a> <ul> <li><a href="#">New Note</a></li> <li><a href="#">Edit Notes</a></li> </ul> </li> <li> <a class="menu-item2" href="javascript:void(0)">Tasks</a> <ul> <li><a href="#">New Tasks</a></li> <li><a href="#">Edit Task</a></li> </ul> </li> </ul> </nav> <div class="content"> <h3><b>Note</b></h3> <p> This demo uses two different approaches interchangeably creating a quirky behaviour, which under normal circumstances would not exist. </p> <p>To reproduce:</p> <ul> <li>select a <i>':focus version'</i> menu item first <li>then select a <i>'.selected version'</i> menu item </ul> <p> As you can see, the selected <i>':focus version'</i> loses focus and a second 'click' is needed to activate the <i>'.selected version'</i> menu item. This is because the first click event of the <i>'.selected version'</i> gets consumed by the blur event of the <i>':focus version'</i>. </p> <p>Just so you know...</p> </div>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.