I have implemented a menu example where when i click on parent menu link all the child elements are visible inside them and when i click on any of the child element it collapses...I have made a demo here http://codepen.io/anon/pen/KzpKrm ...When i click on child link i want the list to be opened like that only and indicate that this link has been selected...Somebody please help
function showmenu(elem) {
// Clear any currently open menu
var openMenu = document.getElementById("activeMenu");
if (openMenu) {
openMenu.removeAttribute("id");
// Stop if we're just closing the current menu
if (openMenu === elem) {
return;
}
}
// Only apply it if the element actually has LI child nodes.
// OPTIONAL: Will still work without if statement.
if (elem.getElementsByTagName("li").length > 0) {
elem.setAttribute("id", "activeMenu");
}
}
Well you can add event.stopPropagation() to the nested ul:
<ul id="nav">
<li onclick="showmenu(this)" class="sectionMenu">
Service
<ul onclick="event.stopPropagation()">
<li><a> Ro </a> </li>
<li> <a>List</a> </li>
<li><a>Service Plan</a> </li>
</ul>
</li>
....
</ul>
You could check if the actual element clicked is a parent (i.e. has class sectionMenu). If not then return.
You can do this by adding the following code to your javascript:
// Checks if element clicked has class sectionMenu. Otherwise return.
var $elementClicked = event.target;
if ($elementClicked.className != 'sectionMenu')
return;
CodePen
function showmenu(elem) {
// Checks if element clicked has class sectionMenu. Otherwise return.
var $elementClicked = event.target;
if ($elementClicked.className != 'sectionMenu')
return;
// Clear any currently open menu
var openMenu = document.getElementById("activeMenu");
if (openMenu) {
openMenu.removeAttribute("id");
// Stop if we're just closing the current menu
if (openMenu === elem) {
return;
}
}
// Only apply it if the element actually has LI child nodes.
// OPTIONAL: Will still work without if statement.
if (elem.getElementsByTagName("li").length > 0) {
elem.setAttribute("id", "activeMenu");
}
}
img {
padding-left: 5px;
}
#nav {
height: 100%;
padding: 20px;
cursor: pointer;
border: 3px solid #3e4547;
box-shadow: 2px 2px 8px #000000;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
ul {
width: 200px;
list-style: none;
margin: 0;
padding: 5px;
}
ul li {
display: block;
padding: 0 10px;
overflow: hidden;
padding: 5px;
}
ul ul {
display: none;
}
ul ul li {
float: none;
user-select: #b6ff00;
}
#activeMenu ul {
display: block;
}
ul li:hover {
background-color: #bcbdc1;
}
ul ul li:hover {
background-color: red;
}
.arrow {
background-image: url("./png/2.png");
transition: 0.3s;
transform: rotateX(-180deg);
}
li.sectionMenu:before {
content: '\2795';
font-size: 13px;
color: #777;
float: right;
margin-left: 5px;
}
li.sectionMenu#activeMenu:before {
content: "\2796";
}
<div>
<ul id="nav">
<li onclick="showmenu(this)" class="sectionMenu">
Service
<ul>
<li><a> Ro </a>
</li>
<li> <a>List</a>
</li>
<li><a>Service Plan</a>
</li>
</ul>
</li>
<li onclick="showmenu(this)" class="sectionMenu">
Customer
<ul>
<li>New Customer</li>
<li>customer List</li>
</ul>
</li>
<li onclick="showmenu(this)" class="sectionMenu">
Parts
<ul>
<li>New Part</li>
<li>Parts List</li>
</ul>
</li>
<li onclick="showmenu(this)" class="sectionMenu">
Admin
<ul>
<li>New Employee</li>
<li>Employee List</li>
<li>Employee Roles</li>
<li>Employee Work Schedulee</li>
<li>Holidays</li>
<li>Employee List</li>
</ul>
</li>
</ul>
</div>
Even U could try this....Remember to pass event argument for the onclick handlers
function showmenu(elem,event) {
// Clear any currently open menu
event.preventDefault();
// alert(event.currentTarget.getAttribute("id"))
var openMenu = document.getElementById("activeMenu");
if (openMenu) {
if(openMenu.children[0]==event.target.parentNode)
return;
openMenu.removeAttribute("id");
// Stop if we're just closing the current menu
if (openMenu === elem) {
return;
}
}
// Only apply it if the element actually has LI child nodes.
// OPTIONAL: Will still work without if statement.
if (elem.getElementsByTagName("li").length > 0) {
event.currentTarget.setAttribute("id", "activeMenu");
}
}
CodePen
Related
I've trying to create dropdown menu on click and close menu when clicked outside.
My currently problem is when i clicked on menu it only toggle or drop down only 1 menu, it doesn't toggle or dropdown on the other menu,
I've searching for the solution i found this one https://stackoverflow.com/a/3028037/14862921
function hideOnClickOutside(element) {
const outsideClickListener = event => {
if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
element.style.display = 'none'
removeClickListener()
}
}
const removeClickListener = () => {
document.removeEventListener('click', outsideClickListener)
}
document.addEventListener('click', outsideClickListener)
}
const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length )
at the Edit – 2018-03-11 this is pure Javascrilpt, I want to apply this code to my project, honestly i'm new to Javascript i have no idea how to raplace the code to my project.
Any help or advices i will appreciate it. Thank you very much.
This is my project.
https://jsfiddle.net/Yurael/7aju8x1k/14/
function dropDownMenu() {
var btnNav = document.querySelector('.nav-menu');
var dropSub = document.querySelector('.nav-dropdown');
btnNav.addEventListener('click', function () {
dropSub.classList.toggle('show');
})
}
dropDownMenu();
/*Block*/
.nav li {
display: inline-block;
list-style: none;
color: black;
width: 100px;
height: auto;
border: 1px solid red;
font-size: 20px;
text-align: center;
}
/*Element*/
.nav-dropdown {
display: none;
position: absolute;
}
.nav-dropdown li {
position: relative;
display: flex;
left: -40%;
}
.show {
display: block;
}
<ul class="nav">
<li>Home</li>
<li class="nav-menu">Menu1
<ul class="nav-dropdown">
<li>Example1</li>
<li>Example2</li>
<li>Example3</li>
</ul>
</li>
<li class="nav-menu">Menu2
<ul class="nav-dropdown">
<li>Example1</li>
<li>Example2</li>
<li>Example3</li>
</ul>
</li>
</ul>
querySelector returns only the first match. You want to select all of the menu buttons, so use querySelectorAll and iterate over the resulting NodeList:
document.addEventListener(
'DOMContentLoaded', // make sure DOM is fully parsed before accessing elements!
function() {
const buttons = document.querySelectorAll('.nav-menu');
for (const button of buttons) {
button.addEventListener('click', function(event) {
if (event.target.matches('li.nav-menu')) { // make sure it's not a click from the dropdown that has bubbled up
button.querySelector('.nav-dropdown').classList.toggle('show');
}
})
}
// on outside click, close all dropdowns
document.addEventListener('click', function(event) {
const clickedElement = event.target;
const dropdowns = document.querySelectorAll('.nav-dropdown');
if (clickedElement.closest('.nav-menu') === null) {
for (const dropdown of dropdowns) {
dropdown.classList.remove('show');
}
}
})
}
);
/*Block*/
.nav li {
display: inline-block;
list-style: none;
color: black;
width: 100px;
height: auto;
border: 1px solid red;
font-size: 20px;
text-align: center;
}
/*Element*/
.nav-dropdown {
display: none;
position: absolute;
}
.nav-dropdown li {
position: relative;
display: flex;
left: -40%;
}
.show {
display: block;
}
<ul class="nav">
<li>Home</li>
<li class="nav-menu">Menu1
<ul class="nav-dropdown">
<li>Example1</li>
<li>Example2</li>
<li>Example3</li>
</ul>
</li>
<li class="nav-menu">Menu2
<ul class="nav-dropdown">
<li>Example1</li>
<li>Example2</li>
<li>Example3</li>
</ul>
</li>
</ul>
I'm completely new to JavaScript in frontend development, and I've got a really basic question I can't seem to figure out... I'm making a tabular navigation bar where the active element is highlighted.
I know there are other ways to accomplish the desired result, but what is wrong with my script here?
function navTabsClick(child) {
getElementsByClassName("active")[0].classList.remove("active");
child.getElementsByTagName("li")[0].className = "active";
}
ul{
display: flex;
list-style-type: none;
}
a{
text-decoration: none;
}
a:link, a:visited{
color: black;
}
a:hover{
cursor: pointer;
}
li{
margin: 0;
padding: 10px 30px;
background-color: lightgray;
}
li:hover{
background-color: darkgray;
}
li.active{
background-color: red;
}
<nav>
<ul>
<a onclick="navTabsClick(this)">
<li class="active">1</li>
</a>
<a onclick="navTabsClick(this)">
<li>2</li>
</a>
<a onclick="navTabsClick(this)">
<li>3</li>
</a>
</ul>
</nav>
If I remove the top line of my JavaScript function, I can get other tabs to highlight, but as is I get the following error:
Uncaught ReferenceError: getElementsByClassName is not defined
What am I missing?
Also, as a secondary question, is there a better way to handle navbar JavaScript than this?
Try doing document.getElementByClassName
function navTabsClick(child) {
document.getElementsByClassName("active")[0].classList.remove("active");
child.getElementsByTagName("li")[0].className = "active";
}
ul{
display: flex;
list-style-type: none;
}
a{
text-decoration: none;
}
a:link, a:visited{
color: black;
}
a:hover{
cursor: pointer;
}
li{
margin: 0;
padding: 10px 30px;
background-color: lightgray;
}
li:hover{
background-color: darkgray;
}
li.active{
background-color: red;
}
<nav>
<ul>
<a onclick="navTabsClick(this)">
<li class="active">1</li>
</a>
<a onclick="navTabsClick(this)">
<li>2</li>
</a>
<a onclick="navTabsClick(this)">
<li>3</li>
</a>
</ul>
</nav>
The function is document.getElementsByClassName, i.e.:
function navTabsClick(child) {
document.getElementsByClassName("active")[0].classList.remove("active");
child.getElementsByTagName("li")[0].className = "active";
}
ul {
display: flex;
list-style-type: none;
}
a {
text-decoration: none;
}
a:link,
a:visited {
color: black;
}
a:hover {
cursor: pointer;
}
li {
margin: 0;
padding: 10px 30px;
background-color: lightgray;
}
li:hover {
background-color: darkgray;
}
li.active {
background-color: red;
}
<nav>
<ul>
<a onclick="navTabsClick(this)">
<li class="active">1</li>
</a>
<a onclick="navTabsClick(this)">
<li>2</li>
</a>
<a onclick="navTabsClick(this)">
<li>3</li>
</a>
</ul>
</nav>
You are missing the document before getElementsByClassName
Others have pointed out that getElementsByClassName must be called on an element, but that does not address the second part of your question.
Is there a better way to handle navbar JavaScript than this?
Right now, you are setting an event listener on each menu item which is unnecessary. The click event exposes the element that was clicked in the target property. You can set a single event listener on the menu itself and use this property to determine which item was clicked.
This also allows you to remove the a tags around your menu items. I assume those will cause issues with screen readers. Edit: As pointed out by Scott, the click event could have been set directly on the li tag originally.
Here's a modified example with a single event for the menu.
function navClicked(nav, item) {
/* Do not attempt to set the nav
menu itself to the active item */
if (nav === item) return;
/* Do not change the active item if the
item that was clicked is already active */
if (item.classList.contains("active")) return;
/* Remove the class from all nav items */
Array.from(nav.children)
.forEach(child => child.classList.remove("active"));
/* Add the class to the item that was clicked */
item.classList.add("active");
}
ul {
display: flex;
list-style-type: none;
}
a {
text-decoration: none;
}
a:link,
a:visited {
color: black;
}
a:hover {
cursor: pointer;
}
li {
margin: 0;
padding: 10px 30px;
background-color: lightgray;
}
li:hover {
background-color: darkgray;
}
li.active {
background-color: red;
}
<nav>
<ul onclick="navClicked(this, event.target)"> <!-- 'this' is the ul element, 'event.target' is the li element that was clicked -->
<li class="active">1</li>
<li>2</li>
<li>3</li>
</ul>
</nav>
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;
only one selected/clicked item to expand at a time while all the
rest of the unselected menu elements are collapsed. That means if I
click the first item, it expands and when I click the second one,
the first one collapses while the one I clicked expands etc.
The selected/clicked element changes its background color to
indicate it's selected.
The selected/clicked element has no hover effect on the text while
the unselected elements have a hover effect of text color change on
them.
I also want the selected menu element be able to toggle the
expansion on and off not affecting the other elements in the UL.
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)">
Staff
<ul>
<li>New Staff</li>
<li>View Staff</li>
</ul>
</li>
<li class="unselected" onclick="toggleMenu(this)">
Notes
<ul>
<li>New Note</li>
<li>Edit Notes</li>
</ul>
</li>
<li class="unselected" onclick="toggleMenu(this)">
Tasks
<ul>
<li>New Tasks</li>
<li>Edit Task</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)">
Staff
<ul class="submenu">
<li class="submenu-item">New Staff</li>
<li class="submenu-item">View Staff</li>
</ul>
</li>
<li class="menu-item unselected" onclick="toggleMenu(this)">
Notes
<ul class="submenu">
<li class="submenu-item">New Note</li>
<li class="submenu-item">Edit Notes</li>
</ul>
</li>
<li class="menu-item unselected" onclick="toggleMenu(this)">
Tasks
<ul class="submenu">
<li class="submenu-item">New Tasks</li>
<li class="submenu-item">Edit Task</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:
set the .menu-item adjacent ul sub-menu-items hidden by default
define :hover colors
define colors for a selected .menu-item and adjacent ul (either :focus or .selected is true)
make .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:
when a .menu-item gets selected and it is the currently .selected then blur it (menuItemBlur())
otherwise
when we have a previously selected .menu-item open, blur that first (menuItemBlur())
and then focus the newly selected .menu-item (menuItemFocus())
Changes to OP code
removed unneeded CSS
removed unneeded class attributes from HTML
changed href="#" 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>New Staff</li>
<li>View Staff</li>
</ul>
</li>
<li>
<a class="menu-item1" href="javascript:void(0)">Notes</a>
<ul>
<li>New Note</li>
<li>Edit Notes</li>
</ul>
</li>
<li>
<a class="menu-item1" href="javascript:void(0)">Tasks</a>
<ul>
<li>New Tasks</li>
<li>Edit Task</li>
</ul>
</li>
<h3>.selected version</h3>
<li>
<a class="menu-item2" href="javascript:void(0)">Staff</a>
<ul>
<li>New Staff</li>
<li>View Staff</li>
</ul>
</li>
<li>
<a class="menu-item2" href="javascript:void(0)">Notes</a>
<ul>
<li>New Note</li>
<li>Edit Notes</li>
</ul>
</li>
<li>
<a class="menu-item2" href="javascript:void(0)">Tasks</a>
<ul>
<li>New Tasks</li>
<li>Edit Task</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>
I have a navigation given by an nested ul list like this
<ul id='mainNav'>
<li> Some Stuff!
<ul>
<li>Page 1</li>
<li>Page 2</li>
</ul>
</li>
<li> Hover here to login!
<ul >
<li>
<div class='login'>
<p>Login</p>
<input type='password'>
<button>Okay</button>
</div>
</li>
</ul>
</ul>
and I use jQuery to show the menu on hover
$('ul#mainNav > li').hover(function() {
$(this).addClass("showMenu");
},
function(){
$(this).removeClass("showMenu");
}
);
This works fine, except for the input box for the login. In the moment where one moves the mouse pointer from the input box to a browser proposal entry, jQuery thinks that one left the li element and removes the class showMenu so that the submenu disappears.
So when I hover over the li element the login form opens
And as soon as I hover over the browser proposal the li element dissapears except for the browser proposal
I also created a jFiddle.
How can I tell jQuery to keep hovering if I move over the browser proposal from the input element?
As a solution You can remove the class .showMenu if event.target is not input
Also blur the input when the dropdown becomes hidden
$('ul#mainNav > li').hover(function(e) {
$(this).addClass("showMenu");
},
function(e) {
if (!$(e.target).is('input')) {
$(this).removeClass("showMenu");
$('input').blur();
}
});
#mainNav>li {
border: 3px solid #08C;
background-color: #FFF;
display: inline-block;
}
#mainNav li {
margin: 0;
padding: 0;
}
#mainNav,
#mainNav ul {
list-style: none;
margin: 0;
padding: 0;
}
#mainNav li>ul {
position: absolute;
display: none;
z-index: 1;
min-width: 200px;
padding: 0px;
margin: 0px;
text-align: left;
background-color: #FFF;
box-sizing: border-box;
}
#mainNav li.showMenu>ul {
display: block
}
.login {
border: 3px solid #08C;
padding: 20px;
margin: 0
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form method>
<ul id='mainNav'>
<li> Hover here to login!
<ul>
<li>
<div class='login'>
<p>Login</p>
<input type='password'>
<button>
Okay
</button>
</div>
</li>
</ul>
</ul>
</form>
https://jsfiddle.net/dzt87zbk/
I have a vertical menu here, which in turn has primary and secondary sub-menus. When the primary or secondary sub-menu is clicked, the whole menu will be closed. I want the sub-menu to stay open when clicked.
e.g.: vertical menu > sub-menu first > sub-menu second (clicked), the page opens up and the menu stays open.
$(function () {
$('.showFirst').click(function () {
$(this).children('ul').slideToggle();
$('.showFirst').not(this).find('ul').slideUp();
e.stopPropagation();
});
$('.showSecond').click(function () {
$(this).children('ul').slideToggle("slow");
return false;
});
$('ul li ul').click(function () {
$('ul li ul li ul').slideUp();
});
$('ul li ul li ul').click(function (e) {
$("ul li ul li ul").slideUp();
$("ul li ul").slideUp();
e.stopPropagation();
});
});
ul {
list-style: none;
cursor: pointer;
}
a {
color: black;
line-height: 25px;
text-decoration: none;
}
a:hover {
color: #aaa;
text-decoration: none;
}
span.sb-caret {
width: 0px;
height: 0px;
display: inline-block;
margin: 0px 5px;
border: 5px solid transparent;
}
span.sb-caret {
/* Caret Down */
border-top: 5px solid;
border-bottom: 0px solid transparent;
}
.sb-submenu-active > span.sb-caret {
/* Caret Up */
border-top: 0px solid transparent;
border-bottom: 5px solid;
}
ul li > ul {
display: none;
/* border:1px solid black; */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<ul>
<li class="showFirst">First<span class="sb-caret"></span>
<ul>
<li>Second
</li>
<li>Second
</li>
<li>Second
</li>
</ul>
</li>
<li class="showFirst">First<span class="sb-caret"></span>
<ul>
<li class="showSecond">Second<span class="sb-caret"></span>
<ul>
<li>third
</li>
<li>third
</li>
</ul>
</li>
<li>Second
</li>
<li>Second
</li>
</ul>
</li>
</ul>
I'm not sure how you're even able to keep track of all of those ul li ul... strings...but don't. That's setting yourself up to easily make mistakes if your DOM changes even slightly, or if you mistype something. Add classes to your markup to make it easier to traverse and reference elements. In this case, a distinct class for each level of <ul> would make things much easier.
Then, you simply have to be selective about which menus you collapse and when. You'll notice the only real change here is that I replaced your ul li ul... click handlers with these:
$('.tier-2').click(function (e) {
$('.tier-3').slideUp();
e.stopPropagation();
});
That code ensures that anytime a level-two list is clicked, all level-three ones collapse.
$('.tier-3').click(function (e) {
$('.tier-2, .tier-3')
.not(this)
.not($(this).closest('.tier-2'))
.slideUp();
e.stopPropagation();
});
This fragment ensures that if a level-three list is clicked, all other level-three lists collapse except this one, and all level-two lists collapse except the parent of this one.
See the full example below.
$(function () {
$('.showFirst').click(function () {
$(this).children('ul').slideToggle();
$('.showFirst').not(this).find('ul').slideUp();
e.stopPropagation();
});
$('.showSecond').click(function () {
$(this).children('ul').slideToggle("slow");
return false;
});
$('.tier-2').click(function (e) {
$('.tier-3').slideUp();
e.stopPropagation();
});
$('.tier-3').click(function (e) {
$('.tier-2, .tier-3')
.not(this)
.not($(this).closest('.tier-2'))
.slideUp();
e.stopPropagation();
});
});
ul {
list-style: none;
cursor: pointer;
}
a {
color: black;
line-height: 25px;
text-decoration: none;
}
a:hover {
color: #aaa;
text-decoration: none;
}
span.sb-caret {
width: 0px;
height: 0px;
display: inline-block;
margin: 0px 5px;
border: 5px solid transparent;
}
span.sb-caret {
/* Caret Down */
border-top: 5px solid;
border-bottom: 0px solid transparent;
}
.sb-submenu-active > span.sb-caret {
/* Caret Up */
border-top: 0px solid transparent;
border-bottom: 5px solid;
}
ul li > ul {
display: none;
/* border:1px solid black; */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<ul>
<li class="showFirst">First<span class="sb-caret"></span>
<ul class="tier-2">
<li>Second
</li>
<li>Second
</li>
<li>Second
</li>
</ul>
</li>
<li class="showFirst">First<span class="sb-caret"></span>
<ul class="tier-2">
<li class="showSecond">Second<span class="sb-caret"></span>
<ul class="tier-3">
<li>third
</li>
<li>third
</li>
</ul>
</li>
<li>Second
</li>
<li>Second
</li>
</ul>
</li>
</ul>