jQuery div show on hover with some elements - javascript

I have a navigation with two div's and some UL inside it. I want to have the second column hidden and make it to appear only on hover. Moreover it should display only UL which attribute "data-category-id" is the same as "data-parent-category-id". For some reason that is beyond my power I need to make this using jQuery. Some smooth animation would be nice as well.
Here is a code I already created, but it obviously has some issues. For some reason When I mouseenter element that should make the second column appear it keeps showing and hiding.
const itemLevelOne = $('.cs-navigation__item--level_1');
const itemLevelTwo = $('.cs-navigation__item--level_2');
const menuLevelThree = $('.cs-navigation__column___third');
itemLevelTwo.hide();
menuLevelThree.hide();
itemLevelOne.mouseenter(function () {
var attr = $(this).attr('data-category-id');
if (typeof attr !== 'undefined' && attr !== false) {
itemLevelTwo.each(function () {
if ($(this).attr('data-parent-category-id') === attr) {
$(this).show('200');
menuLevelThree.show('200');
} else {
$(this).hide('200');
menuLevelThree.hide('200');
}
});
}
});
I also created a codepen with some HTML code for so it is quite similar to what i have on my website. The second column should appear only when you HOVER on "El Hover" and "El Hover 2". In any other scenario it should be hidden.
https://codepen.io/bordini/pen/GRXgNEo
Hope there is some good soul that will be able to help me resolve this issue. Thank you very much!

Your problem is you have the <UL> of level 2 encapsulated in a <DIV> with class cs-navigation__column___third (HTML line 24) so it's part of your menuLevelThree. The menuLevelThree you keep show() and hide() - ing in the event handler for each item of level two which is why the entire level two menu is flickering.
Maybe you misplaced the level 3 DIV?
I commented out your menuLevelThree manipulation in the event handler and changed the first call to .show() to make it visible initially and now level 2 menu shows. Now you need to also implement an mouseleave event handler to hide the level 2 menu.
Oh, and if you are open to using jQuery-UI you may want to consider the https://jqueryui.com/menu/ widget - then you don't need to manually handle the showing and hiding of menu items. However I think you'll need to construct your menu structure hierarchically (level 2 is child of top level and level 3 is child of one particular level 2).
const itemLevelOne = $('.cs-navigation__item--level_1');
const itemLevelTwo = $('.cs-navigation__item--level_2');
const menuLevelThree = $('.cs-navigation__column___third');
itemLevelTwo.hide();
menuLevelThree.show(); // change to show()
itemLevelOne.mouseenter(function () {
var attr = $(this).attr('data-category-id');
if (typeof attr !== 'undefined' && attr !== false) {
itemLevelTwo.each(function () {
if ($(this).attr('data-parent-category-id') === attr) {
$(this).show('200');
// menuLevelThree.show('200');
} else {
$(this).hide('200');
//menuLevelThree.hide('200');
}
});
}
});
nav {
display: flex;
}
.cs-navigation__column___second{
background: red;
}
.cs-navigation__column___third{
background: pink;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav>
<div class="cs-navigation__column cs-navigation__column___second cs-navigation__extras" style="width: 291.16px;">
<ul class="cs-navigation__list cs-navigation__list--level_1" data-parent-item-id="7" style="column-count: 1; width: 150px;">
<li class="cs-navigation__item cs-navigation__item--level_1">
El 1
</li>
<li class="cs-navigation__item cs-navigation__item--level_1">
El 2
</li>
<li class="cs-navigation__item cs-navigation__item--level_1" data-category-id="57">
<a href="#" class="cs-navigation__link cs-navigation__link--level_1">
El Hover</a>
</li>
<li class="cs-navigation__item cs-navigation__item--level_1" data-category-id="60">
<a href="#" class="cs-navigation__link cs-navigation__link--level_1">
El Hover 2</a>
</li>
<li class="cs-navigation__item cs-navigation__item--level_1">
El 3
</li>
</ul>
</div>
<div class="cs-navigation__column cs-navigation__column___third cs-navigation__extras" style="width: 150px;">
<ul class="cs-navigation__list cs-navigation__list--level_2" data-parent-item-id="57">
<li class="cs-navigation__item cs-navigation__item--level_2" data-category-id="32" data-parent-category-id="57">
1
</li>
<li class="cs-navigation__item cs-navigation__item--level_2" data-category-id="32" data-parent-category-id="57">
2
</li>
<li class="cs-navigation__item cs-navigation__item--level_2" data-category-id="32" data-parent-category-id="57">
3
</li>
</ul>
<ul class="cs-navigation__list cs-navigation__list--level_2" data-parent-item-id="60">
<li class="cs-navigation__item cs-navigation__item--level_2" data-category-id="34" data-parent-category-id="60">
1
</li>
<li class="cs-navigation__item cs-navigation__item--level_2" data-category-id="34" data-parent-category-id="60">
2
</li>
</ul>
</div>
</nav>

Related

Hide a list item if the next siblings are all hidden

I have the following list:
<ul>
<li class="title">A</li>
<li class="item" style="display: none">a1</li>
<li class="item" style="display: none">a2</li>
<li class="title">B</li>
<li class="item" style="display: none">b1</li>
<li class="item">b2</li>
</ul>
I would like to create a JavaScript function which checks if the two list items after A are both set to display: none. If they're both set to display: none, then set their title "A" to display: none as well.
I want the script to do the checking for B as well, but don't hide "B" because not all the siblings after it are set to display: none.
I'm thinkering how to proceed for a few hours now but have no idea. Can you give me some ideas how can I do that, not necessarily code, just ideas. Thanks!
You can loop through all li.title and walk down all the next siblings of each li.title instance that are .item using Element.matches() and store in array.
Then check that array of item siblings for all hidden or not and use that determination what to do with display for current instance of li.title
const titles = document.querySelectorAll('li.title');
Array.from(titles).forEach(li => {
const items = [];
let next = li.nextElementSibling;
while (next && next.matches('.item')) {
items.push(next);
next = next.nextElementSibling;
}
const hide = items.every(n => n.style.display === 'none')
if (hide) {
li.style.display = 'none';
} else {
li.style.removeProperty('display')
}
})
<ul>
<li class="title">A</li><!-- should get hidden-->
<li class="item" style="display: none">a1</li>
<li class="item" style="display: none">a2</li>
<li class="title" style="display: none">B</li><!-- should get shown -->
<li class="item" style="display: none">b1</li>
<li class="item">b2</li>
</ul>
The answer by #charlietfl is a pretty good one. But in case you have no problem with using jQuery, I find the below code a bit simpler.
You can learn more about nextAll() here.
const titles = $('.title');
titles.each(function() {
const siblings = $(this).nextAll().slice(0, 2);
const siblingsWithDisplayNone = siblings.filter(function() {
return $(this).css('display') == 'none'
});
if (siblings.length === siblingsWithDisplayNone.length) {
$(this).css('display', 'none')
}
});
See it in action here

jquery get the height of the submenu

I have the <ul> tag as below. When clicked on the anchor link it should display a div with the ul list items underneath. On the click function of anchor tag, I need to get the complete height of the div (in fact the height of the ul with li items), the submenu
<ul>
<li>
<a></a>
<div>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</li>
</ul>
The html code is as below:
<ul class="menu level-1 plain" id="header-menu">
#{
var i = 1; //Used for submenu ID
}
#foreach (var menuItem in Model.Header.TopLevelNavigation)
{
if (i < 6)
{
<li #Html.Raw(i > Model.MenuMidPoint ? "class=\"sub-menu-nudge-left\"" : "")>
#if (menuItem.ContentLink.HasChildPages())
{
<a href="#sub-menu-#i" class="menu-link js-ui-header-all-menu-drill-down" aria-haspopup="true">
<span class="icon icon-chevron-left #Model.GetBoxIcon(i)"></span>
<span>#menuItem.Name</span>
</a>
<div id="sub-menu-#i" class="sub-menu" aria-label="submenu" aria-hidden="true">
#Html.DisplayEnumerableIContent("<ul class=\"level-2 plain\">{0}</ul>", "<li>{0}</li>", "menu-link", menuItem.ContentLink.GetChildPages(true, true))
</div>
}
</li>
}
i++;
}
</ul>
I tried the jquery function as below:
I tried as below, but I am unable to get the height of the submenu. It never gets into the foreach loop at all. Could anyone please help
$(".menu-link").click(function () {
var $subnavdev = $(this).parent().sublings('sub-menu').siblings('level-2 plain')
var totalHeight = 0;
$subnavdev.find('li').each(function() {
totalHeight += $(this).outerHeight(true);
});
alert(totalHeight);
});
Some of your class selectors were not specified correctly, plus there was a typographical error (all on Line 2).
Update - It also seems the siblings() methods may not be behaving as you intend them to... - And I got the .level-2 selector wrong still - since you need another . before plain. (See below example)
By using the closest() method you can go up to the nearest parent (for example, <li>), then use find() to pick out all .level-2.plain items nested within that parent.
Remember that your $(this) inside the handler function refers to the handled element, (in this case the .menu-link item being clicked.
$(".menu-link").click(function () {
var $subnavdev = $(this).closest('li').find('.level-2.plain');
// ...
});

Prevent default only on some levels

I am making a menu that has submenu. When I click on a menu item I am using prevent default because it is a tags, but on the submenu level I don't want to prevent default. I haven't been able to figure out how to make it work so it doesn't affect the top level.
<div id="block-menu-block-2">
<ul class="menu">
<li>
1
</li>
<li>
2
<ul class="menu">
<li>2.1</li>
<li>2.2</li>
<li>2.3</li>
<li>2.4</li>
</ul>
</li>
<li>
3
<ul class="menu">
<li>3.1</li>
<li>3.2</li>
<li>3.3</li>
<li>3.4</li>
</ul>
</li>
<li>
4
</li>
<li>
5
</li>
<li>
6
<ul class="menu">
<li>6.1</li>
</ul>
</li>
</ul>
</div>
Here is the jQuery
$('#block-menu-block-2 ul li').on("click", function() {
if ($(this).children().is('ul')) {
if ($(this).find('ul').is(':visible')) {
$(this).find('ul').hide("blind");
$(this).removeClass('menuItemSelected');
$(this).find('ul').removeClass('menuItemSelected');
} else {
$(this).parent().find('li ul').hide("blind");
$(this).parent().find('.menuItemSelected').removeClass('menuItemSelected');
$(this).find('ul').show("blind");
$(this).addClass('menuItemSelected');
$(this).find('ul').addClass('menuItemSelected');
};
event.preventDefault()
}
});
Here is a codepen for reference: http://codepen.io/mathiasha/pen/bVpMyo
Added some stuff. Changed some stuff. Couldn't paste into codepen from my phone so this might not work. Code first, then word wall.
$('#block-menu-block-2 ul li').each (function () {
var $this = $(this);
if ($this.find ('ul:first').length > 0) {
$this.click (function () {
if ($this.find ('ul:visible').length > 0) {
$this.removeClass ('menuItemSelected').find ('ul').removeClass('menuItemSelected').hide ('blind');
} else {
$this.parent ().find ('ul li').hide ('blind');
$this.parent ().find('.menuItemSelected').removeClass ('menuItemSelected');
$this.addClass ('menuItemSelected').find ('ul').show ('blind').addClass ('menuItemSelected');
}
});
}
});
$('#block-menu-block-2 > ul > li > a').click (function (e) {
if ($(this).find ('ul:first').length > 0)
e.preventDefault ();
});
The real answer lies in only putting the preventDefault only on the a tag and only when it is the immediate child of a li tag tjat is the immediate child of a ul tag that is the immediate child of the block-menu. See the last 3 lines.
The rest of the code below should only add the click listener to li tags with ul tags inside. Tried to use chaining to limit the number of jQuery objects created. Might have messed up what it was doing. You only really need to remove preventDefault from where it is and than use the last 3 lines.
Can you not add a class to your submenu triggers, e.g. .submenu-trigger, and then use the following jQuery:
$(document).on('click', function(e) {
if ($(e.target).hasClass('submenu-trigger')) e.preventDefault();
});
Ignoring all the other menu manipulation and putting the event on <a> tags you can simply check if the <a> has a sibling <ul> and if it does prevent default
$('#block-menu-block-2 a').click(function(e){
if( $(this).siblings('ul').length ){
e.preventDefault();
}
// menu manipulation code
});

Selecting multiple li classes in a menu

I'm creating a portfolio site, and I'm struggling to select the proper elements with JQuery. My goal is to show/hide the .inner (Task) items when clicking the .outer (Category) items. I have .arrows that rotate when the menu is expanded.
This is a similar question, and the accompanying jsfiddle.
Thanks to Tats_innit for the original answer.
I have this HTML:
<li class="outer" hook="01">
<div class="arrow"></div>
Category 1
</li>
<li class="inner" id="menu-01">
Task 1
</li>
<li class="inner" id="menu-01">
Task 2
</li>
<li class="inner" id="menu-01">
Task 3
</li>
<li class="outer" hook="02">
<div class="arrow"></div>
Category 2
</li>
<li class="inner" id="menu-02">
Task 1
</li>
<li class="inner" id="menu-02">
Task 2
</li>
<li class="inner" id="menu-02">
Task 3
</li>
And this jquery:
$(document).ready(function(){
$('.outer').click(function(){
var elem = $('#menu-'+$(this).attr('hook')),
arrow = $(this).children('.arrow')
if (!elem.is(':visible')) {
arrow.rotate({animateTo:90, duration:128});
} else {
arrow.rotate({animateTo:0, duration:128});
}
elem.slideToggle('128ms', function() {
});
return false;
});
});
I understand that I need to change var elem = $('#menu-'+$(this).attr('hook')) but I'm not sure how to display all instances of .inner.
I didn't nest the .inner elements because I have a hover state background-color: #f1f1f1; for the .outer class.
This problem can be solved, but there are many things wrong with your approach in the first place, so I would like to aim to change those, instead. And then solve the problem.
The id attribute is unique. You cannot have multiple elements with the same id in the same page; for that reason, use class.
hook is not a valid HTML attribute; while it is true that this won't hurt you and browsers will most likely ignore it, I would rather see it standardized as data-hook if you so wish. This will allow you also to use the standard APIs.
You should nest the .inner inside the .outer because it makes sense semantically and it will degrade gracefully and be understandable to screen readers as well. The problem you mention about the background on hover, I think could be easily solved with some good CSS: though I didn't understand the specific issue, so I cannot say.
But fact is, that using nesting you probably don't need most of those ids, classes and random data attributes (unless obviously you need them for something else than just opening up a list).
After that you HTML will look like this:
<ul>
<li class="outer">
<div class="arrow"></div>
Category 1
<ul>
<li>
Task 1
</li>
<li>
Task 2
</li>
<li>
Task 3
</li>
</ul>
</li>
<li class="outer">
<div class="arrow"></div>
Category 1
<ul>
<li>
Task 1
</li>
<li>
Task 2
</li>
<li>
Task 3
</li>
</ul>
</li>
</ul>
And you jQuery like so:
$('.outer').click(function(){
var elem = $(this).children('ul'),
arrow = $(this).children('.arrow')
if (!elem.is(':visible')) {
arrow.rotate({animateTo:90, duration:128});
} else {
arrow.rotate({animateTo:0, duration:128});
}
elem.slideToggle('128ms', function() {
});
return false;
});
As a final note, .arrow is not a very semantic element, since it's not even used to activate the animation (the click is bound the the li.outer) I would remove it altogether and instead achieve the same effect using a pseudo-element on the said li, and perhaps rotate it with CSS3.
Edit
I am not sure why you were not able to make it function properly; though you tell me that you appreciated the comment regarding the nesting, your fiddle didn't use the correct nesting.
In any case, I put up an example for you with the advice I gave you here, and basically I just copied the code verbatim as I wrote it here. I made a few adjustments, though: such as implementing the CSS pseudoelement and getting rid of that div.arrow element, which honestly looked bad.
Obviously is just an example, but it shows how think this problem should be approached. Hopefully it will solve your doubts:
Working example
You can nest a set of outer and inner into a div
<div>
<li class="outer">
<div class="arrow"></div>
Category 1
</li>
<li class="inner">
Task 1
</li>
<li class="inner">
Task 2
</li>
<li class="inner">
Task 3
</li>
</div>
then your js code goes like
$('.outer').click(function(){
var elem = $(this).siblings('.inner'),
arrow = $(this).children('.arrow')
if (!elem.is(':visible')) {
arrow.rotate({animateTo:90, duration:128});
} else {
arrow.rotate({animateTo:0, duration:128});
}
elem.slideToggle('128ms', function() {
});
return false;
});
won't affect your background-color for outer class in the meantime
<script>
$(document).ready(function(){
$('li.inner').hide();
$('.outer').click(function()
{
var elem = $(this).attr('hook');
$('li.inner').hide();
$(".menu-"+elem).toggle();
});
});
</script>
<ul>
<li class="outer" hook="01">
<div class="arrow"></div>
Category 1
</li>
<li class="inner menu-01">
Task 1
</li>
<li class="inner menu-01">
Task 2
</li>
<li class="inner menu-01">
Task 3
</li>
<li class="outer" hook="02">
<div class="arrow"></div>
Category 2
</li>
<li class="inner menu-02">
Task 1
</li>
<li class="inner menu-02">
Task 2
</li>
<li class="inner menu-02">
Task 3
</li>
</ul>
first of all change li id to class because 2 li don't have same id
I guess you are looking for nextUntil()
$(this).nextUntil('.outer').slideToggle('218ms');
http://jsfiddle.net/bf7Ke/53/

jquery menu inheritance

I have the following simple 3 level menu structure:
<nav class="main-nav" class="list">
<ul>
<li class="first lev1">Home</li>
<li class="lev1">Lev 1 1</li>
<li class="lev1 hasc active">
Lev 1 2
<ul>
<li class="first lev2 hasc">
Lev 2 1
<ul>
<li class="first lev3">Lev 3 1 </li>
<li class="lev3">Lev 3 2</li>
</ul>
</li>
</ul>
</li>
<li class="lev1 hasc active">Lev 1 3</li>
</ul>
</nav>
I'm trying to make the menu work so that the second level menu slides open when it has children but is an active link when it does not and then all 3rd level links are always active links.
The following jquery code works for all the required functionality for the 2nd level menu (prevents default and opens the 3rd level if there are children, but if not makes the link active) the problem is that i'm not sure how to over ride the prevent default for the third level?
$(".lev2").click(function (event) {
event.preventDefault();
if ($(this).hasClass('on')) {
$(this).children('ul').slideUp();
$(this).removeClass('on');
} else {
$(this).children('ul').slideDown();
$(this).addClass('on');
}
});
I've looked into starting at the root of the menu class 'main-nav' and then try to branch the code but as it is nested I'm not finding any logic that will work? any ideas most welcome.
The problem seems to be that you´re binding the click event to the entire li element and not just the link.
Try this instead:
$(".lev2 > a").click(function (event) {
event.preventDefault();
$(this).parent().toggleClass('on').children('ul').slideToggle();
});
It binds the event to the link and toggles your actions on the parent li and ul for the next level.
Live example at jsFiddle
This works. Just checks for a level 3 click first to tell the level 2 function whether level 3 has been clicked or not.
$(document).ready(function(){
var level2 = true;
$("li.lev3").click(function(){
level2 = false;
});
$("li.lev2").click( function(event){
if (level2){
alert ("lvl2");
event.preventDefault();
if($(this).hasClass('on')) {
$(this).children('ul').slideUp();
$(this).removeClass('on');
}else{
$(this).children('ul').slideDown();
$(this).addClass('on');
}
}
});
});

Categories

Resources