Nested dropdrown menu - javascript

I'm trying to build a nested dropdown menu with javascript and click event. My html is this:
<ul>
<li id="menu-item-439" class="dropdown">
hostelerĂ­a
<ul class="sub-menu">
<li id="menu-item-459">
Comanderos
</li>
<li id="menu-item-457" class="dropdown">
Venta online
<ul class="sub-menu">
<li>
PortalRest Pedidos
</li>
</ul>
</li>
</ul>
</li>
</ul>
and my javascript:
document.querySelectorAll('.dropdown').forEach(i => {
i.addEventListener('click', (e) => {
i.classList.toggle('show');
})
});
The problem is in the nested "dropdown" class. When i click in "child dropdown" (id="menu-item-457") toogle not only affects that element but also affects the parent (id="menu-item-439")
Thanks!!

Here
HTML
<ul>
<li id="menu-item-439" class="dropdown">
hostelerĂ­a
<ul class="sub-menu">
<li id="menu-item-459">
Comanderos
</li>
<li id="menu-item-457" class="dropdown">
Venta online
<ul class="sub-menu">
<li>
PortalRest Pedidos
</li>
</ul>
</li>
</ul>
</li>
</ul>
JS
let n=0, n2=0;
function toggle() {
n++;
if (n%2==0) {
document.getElementsByClassName("sub-menu")[0].style.display="none";
}
else {
document.getElementsByClassName("sub-menu")[0].style.display="block";
}
}
function toggle2() {
n2++;
if (n2%2==0) {
document.getElementsByClassName("sub-menu")[1].style.display="none";
}
else {
document.getElementsByClassName("sub-menu")[1].style.display="block";
}
}
CSS
.sub-menu {display: none}
If you wanted same with JQuery toggle method, then js should be like this
JS
function toggle() {
document.getElementsByClassName("sub-menu")[0].toggle();
}
function toggle2() {
document.getElementsByClassName("sub-menu")[1].toggle();
}

Related

JS: how can I select a child in event.target?

I have a nested megu menu like this:
<ul class="nav-wrap">
<li class="main-nav">
<a>m1</a>
<ul class="sub-nav">
<li></li>
...
<li><ul>...</ul><li>
</ul>
<li>
<li class="main-nav">
<a>m1</a>
<ul class="sub-nav"></ul>
<li>
<li class="main-nav">
<a>m1</a>
<ul class="sub-nav"></ul>
<li>
...
</ul>
for each main-nav, I want hover main-nav, and add .show class in .sub-nav (its child).
how can I do that?
I try this:
var dropdownMenus = [].slice.call(document.querySelectorAll('.main-nav'));
var dropdownList = dropdownElementList.map(function (dropdownToggleEl) {
dropdownToggleEl.onmouseover= function(e){
e.target.children.querySelector('ul.sub-nav').classList.toggle('show');
};
dropdownToggleEl.onmouseleave= function(e){
e.target.children.querySelector('ul.sub-nav').classList.toggle('show');
};
});
But I get this error:
net::ERR_CERT_DATE_INVALID
Uncaught TypeError: e.target.children.querySelector is not a function at HTMLLIElement.dropdownToggleEl.onmouseover
There is code missing in multiple places in JS, HTML and CSS, I've added the missing pieces. Use onmouseenter instead of onmouseover as it always provides the same element on which you added the event listener for e.target while same is not true for onmouseover.
let dropdownElementList = [].slice.call(document.querySelectorAll('.main-nav'));
dropdownElementList.map(function (dropdownToggleEl) {
dropdownToggleEl.onmouseenter= function(e){
e.target.querySelector('ul.sub-nav').classList.remove('hide');
e.target.querySelector('ul.sub-nav').classList.add('show');
};
dropdownToggleEl.onmouseleave= function(e){
e.target.querySelector('ul.sub-nav').classList.remove('show');
e.target.querySelector('ul.sub-nav').classList.add('hide');
};
});
.show {
display: block;
}
.hide {
display: none
}
<ul class="nav-wrap">
<li class="main-nav">
<a>m111</a>
<ul class="sub-nav hide">
<li>m1.s11</li>
<li>m1.s22</li>
<li><ul>...</ul></li>
</ul>
</li>
<li class="main-nav">
<a>m122</a>
<ul class="sub-nav hide">
<li>m2.s11</li>
<li>m2.s22</li>
</ul>
</li>
<li class="main-nav">
<a>m133</a>
<ul class="sub-nav"></ul>
</li>
...
</ul>

Display parent UL and for the root parent both UL and LI node

<ul id="ul_Book1">
<li id="li_Book1"> Book1 </li>
<ul id="ul_Chap1">
<li id= "li_Chap1"> Chapter 1</li>
<ul>
<li id="li_Sect11" > Section 1.1 </li>
<li id="li_Sect12" > Section 1.2 </li>
<li id="li_Sect13" > Section 1.3 </li>
</ul>
</ul>
<ul id="ul_Chap2">
<LI id= "li_Chap2"> Chapter 2</LI>
<ul>
<li id="li_Sect21" > Section 2.1 </li>
<li id="li_Sect22" > Section 2.2 </li>
<li id="li_Sect23" > Section 2.3 </li>
</ul>
</ul>
<ul id="ul_Chap3">
<li id= "li_Chap3"> Chapter 3</li>
<ul>
<li id="li_Sect31" > Section 3.1 </li>
<li id="li_Sect32" > Section 3.2 </li>
<li id="li_Sect33" > Section 3.3 </li>
</ul>
</ul>
function menuDisplayPath(li_id) {
var idVal = li_id;
var $obj = $('li').filter(function () { return $(this).attr('id') == idVal; }).parents();
$($obj.get().reverse()).each(function () {
if ($(this).is("li")) {
$(this).find('li').each(function () {
$(this).show();
});
$(this).find('Ul').each(function () {
$(this).show();
});
}
});
}
When a LI node is selected , say "Section 3.2" , I would like to expand/show only "Book1-- Chapter3 -- Section 3.2" and rest of the UL nodes under Book1 should not expand.
But my function menuDisplayPath(li_id) is expanding all the UL under Book1 and also display all LI's. What am I missing in the function.
Assuming all is expanded on load.
First, I corrected many missing double-quotes and extra spaces...
So here is your HTML:
<input type="reset" id="reset" value="Expand all">
<ul id="ul_Book1">
<li id="li_Book1"> Book1 </li>
<ul id="ul_Chap1">
<li id="li_Chap1"> Chapter 1</li>
<ul>
<li id="li_Sect11"> Section 1.1 </li>
<li id="li_Sect12"> Section 1.2 </li>
<li id="li_Sect13"> Section 1.3 </li>
</ul>
</ul>
<ul id="ul_Chap2">
<li id="li_Chap2"> Chapter 2</li>
<ul>
<li id="li_Sect21"> Section 2.1 </li>
<li id="li_Sect22"> Section 2.2 </li>
<li id="li_Sect23"> Section 2.3 </li>
</ul>
</ul>
<ul id="ul_Chap3">
<li id="li_Chap3"> Chapter 3</li>
<ul>
<li id="li_Sect31"> Section 3.1 </li>
<li id="li_Sect32"> Section 3.2 </li>
<li id="li_Sect33"> Section 3.3 </li>
</ul>
</ul>
</ul>
And here is the way to hide the other chapters on click of a li with a section id:
$(document).ready(function(){
$("#ul_Book1").on("click","ul ul li", function(){
$(this).parent().parent().siblings("ul").each(function(){
$(this).hide(); // Hides all other chapter
});
});
$("#reset").on("click",function(){
$("#ul_Book1").children("ul").each(function(){
$(this).show();
});
});
});
Fiddle
Now... Can't tell you how to display the <section> since I didn't see the relevant HTML.
The following worked for me:
function open($li) {
$('.content').hide();
$li.children().show();
if ($li.hasClass('section')) $li.parent().parent().children().show();
}
// test driver
$(document).ready(function() {
$('li').click(function (e) { open($(this)); e.stopPropagation(); });
});
.content {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="book">
<li class="chapter">Chapter One
<ul class="chapter content">
<li class="section">Section One<br><i class="content">1.1 contents</i></li>
<li class="section">Section Two<br><i class="content">1.2 contents</i></li>
</ul>
</li>
<li class="chapter">Chapter Two
<ul class="chapter content">
<li class="section">Section One<br><i class="content">2.1 contents</i></li>
<li class="section">Section Two<br><i class="content">2.2 contents</i></li>
</ul>
</li>
</ul>
Basically you put the section element you want to be open inside the open($li) function.
You should also only put <li> elements inside <ul> elements.
I hope this is what you wanted.

Convert dynamic DOM structure to JSON

How can convert below DOM to JSON array which will have many more leafs in thefuture . I have tried a lot but didn't got any solution. I found this but it is not possible in my case.
HTML Code
<ul id="org1">
<li>
Main
<ul data-self="1">
<li data-self="2" data-parent="1">A
<ul data-self="2">
<li data-self="5" data-parent="2">A1</li>
<li data-self="6" data-parent="2">A2</li>
<li data-self="7" data-parent="2">A3</li>
<li data-self="8" data-parent="2">A4</li>
<li data-self="9" data-parent="2">A5</li>
<li data-self="10" data-parent="2">A6</li>
</ul>
</li>
<li data-self="3" data-parent="1">B
<ul data-self="3">
<li data-self="11" data-parent="3">B1</li>
<li data-self="12" data-parent="3">B2</li>
<li data-self="13" data-parent="3">B3</li>
<li data-self="14" data-parent="3">B4</li>
</ul>
</li>
</ul>
</li>
</ul>
JSON Required is
{
1:{
2:{5,6,7,8,9,10},
3:{11,12,13,14}
}
}
Script
function recursive(dis)
{
$(this).find(' > ul ').each(function(){
params[$(this).attr('data-self')]=[];
var i=0;
$(this).find('li').each(function(){
if($(this).has('ul'))
{
params[$(this).attr('data-parent')][i++]=rec(this);
}else
{
params[$(this).attr('data-parent')][i++]=$(this).attr('data-self');
}
});
});
}
recursive('#org1');
console.log(params);
I would prefer something like the following. The 2 changes in the HTML you need to make are:
1) Add a class to your ul such as <ul data-self="2" class="nodes">. 2) Add a class to the wrapper ul such as <ul data-self="1" class="parent">.
var json = {};
$("#org1").find("ul.parent").each(function() {
var $this = $(this);
json[$this.data("self")] = {};
var $nodes = $this.find("ul.nodes");
$nodes.each(function() {
var children = $(this).find("li").map(function() {
return $(this).data("self")
}).get();
json[$this.data("self")][$(this).data("self")] = children;
});
});
alert (JSON.stringify(json));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="org1">
<li>
Main
<ul data-self="1" class="parent">
<li data-self="2" data-parent="1">A
<ul data-self="2" class="nodes">
<li data-self="5" data-parent="2">A1</li>
<li data-self="6" data-parent="2">A2</li>
<li data-self="7" data-parent="2">A3</li>
<li data-self="8" data-parent="2">A4</li>
<li data-self="9" data-parent="2">A5</li>
<li data-self="10" data-parent="2">A6</li>
</ul>
</li>
<li data-self="3" data-parent="1">B
<ul data-self="3" class="nodes">
<li data-self="11" data-parent="3">B1</li>
<li data-self="12" data-parent="3">B2</li>
<li data-self="13" data-parent="3">B3</li>
<li data-self="14" data-parent="3">B4</li>
</ul>
</li>
</ul>
</li>
</ul>

How to get a previous DIV with jQuery

I want to animate an element (.item-i) after I click on another element(.item-1) which is following below. Now I don't know, how to get to this element.
I want only the exact above element animated, not both above, only the first element above.
I made an easy show up in http://jsfiddle.net/Eef4v/1/. The YellowBG is the "animation".
Here is my JS code:
$('.item-1').click(function() {
$(this).closest('.item-i').css( "background", "yellow" );
});
And my HTML Markup:
<ul id="one" class="level-1">
<li class="item-i">I</li>
<li class="item-ii">II
<ul class="level-2">
<li class="item-a">A</li>
<li class="item-b">B
<ul class="level-3">
<li class="item-1">1</li>
<li class="item-2">2</li>
<li class="item-3">3</li>
</ul>
</li>
<li class="item-c">C</li>
</ul>
</li>
<li class="item-iii">III</li>
</ul>
<ul id="Ul1" class="level-1">
<li class="item-i">I</li>
<li class="item-ii">II
<ul class="level-2">
<li class="item-a">A</li>
<li class="item-b">B
<ul class="level-3">
<li class="item-1">1</li>
<li class="item-2">2</li>
<li class="item-3">3</li>
</ul>
</li>
<li class="item-c">C</li>
</ul>
</li>
<li class="item-iii">III</li>
</ul>
Use:
$(this).closest('.item-ii').prev().css( "background", "yellow" );
Working Demo
For Toggling background color:
$(this).closest('.item-ii').prev().toggleClass("highlight");
CSS:
.highlight {background: yellow;}
Demo with ToggleClass
Two ways to get .item-i to highlight.
$(this).parents('.level-1').find('.item-i').css( "background", "yellow" );
but if you have multiple .item-i classes under your .level-1 then they would all turn yellow.
Working Demo
You could also write a find algorithm that goes through each parent of .item-1 and finds the closest item-i
$('.item-1').click(function () {
$(this).parents().each(function(index,elem){
var query = $(elem).find('.item-i');
if(query.length > 0)
{
query.first().css("background-color", "yellow");
return false;
}
});
});
Working Algorithm demo

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