I am learning JavaScript and CSS and got stuck somewhere-
I have three different lists top_menu, submenu3 and submenu4, when I click on item no 3 I want submenu 3 to show up and when I click on item no 4 I want submenu4 to show up and submenu3 should vanish.
My HTML Code is-
<ul id="top_menu">
<li onclick="arrow(1)">item no 1
</li>
<li onclick="arrow(2)">item no 2
</li>
<li onclick="arrow(3)">item no 3
</li>
<li onclick="arrow(4)">item no 4
</li>
</ul>
<ul id="submenu1" style="display:none;"></ul>
<ul id="submenu2" style="display:none;"></ul>
<ul id="submenu3" style="display:none;">
<li>A3
</li>
<li>B3
</li>
<li>C3
</li>
<li>D3
</li>
<li>E3
</li>
</ul>
<ul id="submenu4" style="display:none;">
<li>A4
</li>
<li>B4
</li>
<li>C4
</li>
<li>D4
</li>
<li>E4
</li>
</ul>
and JavaScript implementation for function arrow() is
function arrow(x) {
for (i = 1; i <5; i++) {
if (i==x) {
document.getElementById("submenu"+i).style.display = "block";
}
else {
document.getElementById("submenu"+i).style.display = "none";
}
}
}
Could someone tell where the mistake is?
Thanks!
The for statement should be
for (i = 1; i <5; i++)
Apart from that it seems to be working for me.
A non elegant solution would be to declare your function at the global scope :
window.arrow=function(x){
for (i = 1; i <5; i++) {
if (i==x) {
document.getElementById("submenu"+i).style.display = "block";
}
else {
document.getElementById("submenu"+i).style.display = "none";
}
}
}
Related
I have the JS code below that I use with the form after it. However, it only calls the first dropdown and ignores the rest. The dropdown are generated by Hugo (SSG) and I all share the same ID/class in the menu. I need help to make the code work for multiple dropdown menus.
document.getElementById("dropbtn").addEventListener('click', function() {
document.getElementById("sub-menu").classList.toggle("show");
var x = document.getElementById("dropbtn").getAttribute("aria-expanded");
if (x == "true")
{
x = "false"
} else {
x = "true"
}
document.getElementById("dropbtn").setAttribute("aria-expanded", x);
});
// Close the dropdown menu if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("sub-menu");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
<nav>
<ul>
<li class="dropdown" id="dropdown">
<a id="dropbtn" class="dropbtn" href="#" aria-expanded="false">Toggle<span class="drop-icon" for="toggle">▾</span></a>
<ul id="sub-menu" class="sub-menu">
<li>
A
</li>
<li>
B
</li>
<li>
<a href="/c/" >C</a>
</li>
</ul>
</li>
<li class="dropdown" id="dropdown">
<a id="dropbtn" class="dropbtn" href="#" aria-expanded="false">Dropdown <span class="drop-icon" for="dropdown">▾</span></a>
<ul id="sub-menu" class="sub-menu">
<li>
D
</li>
<li>
E
</li>
<li>
F
</li>
</ul>
</li>
</ul>
</nav>
It's not reccomended to use multiple id's with the same name.
Here's my solution for your case.
<nav>
<ul>
<li class="dropdown">
<a class="dropbtn" aria-expanded="false" href="#">Toggle<span class="drop-icon">▾</span></a>
<ul class="sub-menu">
<li>
A
</li>
<li>
B
</li>
<li>
C
</li>
</ul>
</li>
<li class="dropdown">
<a class="dropbtn" aria-expanded="false" href="#">Dropdown <span class="drop-icon">▾</span></a>
<ul class="sub-menu">
<li>
D
</li>
<li>
E
</li>
<li>
F
</li>
</ul>
</li>
</ul>
</nav>
<script>
let dropbtns = document.getElementsByClassName('dropbtn');
for (let dropbtn of dropbtns) {
dropbtn.onclick = () => {
dropbtn.nextElementSibling.classList.toggle('show');
let expanded = dropbtn.getAttribute('aria-expanded');
dropbtn.setAttribute('aria-expanded', expanded == 'true' ? 'false' : 'true');
};
}
// Close the dropdown menu if the user clicks outside of it
window.onclick = function (event) {
if (!event.target.matches('.dropbtn')) {
let dropbtns = document.getElementsByClassName('dropbtn');
for (let dropbtn of dropbtns) {
dropbtn.nextElementSibling.classList.remove('show');
dropbtn.setAttribute('aria-expanded', 'false');
}
}
};
</script>
So, first of all. There can only be one unique ID per page, so your HTML is now invalid. You will have to have different IDs on your HTML elements (if you still need them at all). Below JS hopefully should work.
const dropdownTriggers = document.querySelectorAll('.dropbtn');
[...dropdownTriggers].forEach((trigger) => {
trigger.addEventListener('click', (e) => {
const triggerClicked = e.target;
const dropdownMenu = triggerClicked.nextElementSibling;
const isDropdownShown = Boolean(triggerClicked.getAttribute('aria-expanded'));
dropdownMenu.classList.toggle('show', !isDropdownShown);
triggerClicked.setAttribute('aria-expanded', isDropdownShown);
})
})
document.addEventListener('click', (e) => {
if (!event.target.matches('.dropbtn')) {
[...dropdownTriggers].forEach((trigger) => {
const dropdownMenu = trigger.nextElementSibling;
dropdownMenu.classList.toggle('show', false);
trigger.setAttribute('aria-expanded', false);
})
}
});
I have a simple navigation with two levels. The li-elements of the first level shall get class="n11", that of the second class="n12".
This will write class="n11" to every li-element.
var firstNavi = document.getElementsByClassName("nav1-1");
for(var i = 0; i < firstNavi.length; i++) {
var firstLi = firstNavi[i].querySelectorAll("li");
for(var i = 0; i < firstLi.length; i++) {
firstLi[i].classList.add("n11");
}
}
<ul class="nav1-1">
<li >1.1.</li>
<li >1.2
<ul class="nav1-2">
<li>1.2.1</li>
<li>1.2.2</li>
</ul>
</li>
<li>1.3
<ul class="nav1-2">
<li>1.3.1</li>
<li>1.3.2</li>
</ul>
<li>1.4</li>
</ul>
How to achieve that is written class="n12" to the second li-elements and class="n11" only to the first level entries? Thanks for any help.
You can just retrieve the first level <li> elements as well as the second level <li> elements by using the child combinator > on the first level parent <ul> element and the second level <ul> parent element and now you can just loop through each list element and add the class name accordingly like this:
const firstLevel = document.querySelectorAll('.nav1-1 > li');
const secondLevel = document.querySelectorAll('.nav1-2 > li');
for (var i = 0; i < firstLevel.length; i++) {
firstLevel[i].classList.add('n11');
}
for (var i = 0; i < secondLevel.length; i++) {
secondLevel[i].classList.add('n12');
}
<ul class="nav1-1">
<li >1.1.</li>
<li >1.2
<ul class="nav1-2">
<li>1.2.1</li>
<li>1.2.2</li>
</ul>
</li>
<li>1.3
<ul class="nav1-2">
<li>1.3.1</li>
<li>1.3.2</li>
</ul>
<li>1.4</li>
</ul>
If you do not care about IE 11 compatibility or you are using a JavaScript compiler like Babel, you can further shorten and simplify the above JavaScript by using the forEach() method and arrow functions like this:
const firstLevel = document.querySelectorAll('.nav1-1 > li');
const secondLevel = document.querySelectorAll('.nav1-2 > li');
firstLevel.forEach(e => e.classList.add('n11'));
secondLevel.forEach(e => e.classList.add('n12'));
<ul class="nav1-1">
<li >1.1.</li>
<li >1.2
<ul class="nav1-2">
<li>1.2.1</li>
<li>1.2.2</li>
</ul>
</li>
<li>1.3
<ul class="nav1-2">
<li>1.3.1</li>
<li>1.3.2</li>
</ul>
<li>1.4</li>
</ul>
You can use the ">" query selector to find li elements that have a parent of .nav1-1, then add class n11 to each. Rinse and repeat for the li elements that have a parent of .nav1-2.
Here it is in code.
'use strict';
const li1s = document.querySelectorAll('.nav1-1 > li');
for (const li of li1s)
li.classList.add('n11');
const li2s = document.querySelectorAll('.nav1-2 > li');
for (const li of li2s)
li.classList.add('n12');
<ul class="nav1-1">
<li >1.1.</li>
<li >1.2
<ul class="nav1-2">
<li>1.2.1</li>
<li>1.2.2</li>
</ul>
</li>
<li>1.3
<ul class="nav1-2">
<li>1.3.1</li>
<li>1.3.2</li>
</ul>
<li>1.4</li>
</ul>
I'm currently trying to create a double-dropdown menu using JavaScript & HTML lists. I know there is a lot about it in the internet, but these solutions don't fit me or use jQuery (and I'm doing this for JavaScript practice mostly). I've created something like that:
JS Code:
function clear_id() {
document.getElementById("first").style.display = "none";
document.getElementById("second").style.display = "none";
}
function dropdown_id(id) {
var element = document.getElementById(id);
if (element.style.display === "block") {
element.style.display = "none";
} else {
clear_id();
element.style.display = "block";
}
}
function clear_class(element) {
for (var i = 0; i < element.length; ++i) {
element[i].style.display = "none";
}
}
function dropdown_class(id, num) {
var element = document.getElementsByClassName(id);
if (element[num].style.display === "block") {
element[num].style.display = "none";
} else {
clear_class(element);
element[num].style.display = "block";
}
}
CSS Code:
ol,
ul {
display: none;
}
#main {
display: block;
}
li {
list-style-type: none;
}
HTML Code:
<ol id="main">
<li>
First
<ul id="first">
<li>
fir
<ol class="one">
<li>f</li>
<li>s</li>
</ol>
</li>
<li>
sec
<ol class="one">
<li>f</li>
<li>s</li>
</ol>
</li>
</ul>
</li>
<li>
Second
<ul id="second">
<li>fir
<ol class="two">
<li>f</li>
<li>s</li>
</ol>
</li>
<li>
sec
<ol class="two">
<li>f</li>
<li>s</li>
</ol>
</li>
</ul>
</li>
</ol>
So at this point, I simply have to expand and roll (depending on display state) after click. Here is my solution for that problem.
It works fine, but I feel that it can be done simpler, just don't know JavaScript good enough and I'm blocked by my C++ approach (recently moved from C++ to web because of curiosity). So here is my question: can it be done easier and simpler(maybe more correct)? If anyone can show me the right path, I would be very grateful.
Here is another implementation, I pass in the DOM element using the this keyword which I then use in my javascript function. It is also a bit shorter than your solution but not necessarily better.
function toggle (el) {
if (el.childNodes[1].className === 'disappear') {
el.childNodes[1].classList.remove('disappear');
} else {
el.childNodes[1].classList.add('disappear');
}
}
.disappear {
display: none;
}
li:hover{
color:red
}
<ul>
<li onclick="toggle(this);">first
<ul class="disappear">
<li>1</li>
<li>2</li>
</ul>
</li>
<li onclick="toggle(this);">second
<ul class="disappear">
<li>1</li>
<li>2</li>
</ul>
</li>
</ul>
Hopefully this is helpful for you!
I am really new at javascript but as far as I can tell, the code that I have here should be working. No errors come up when I view in the console(nothing comes up for that matter) and all that appears on the website is the button that doesn't do anything and the list items already appearing on the page
<html>
<body>
<nav>
<ul>
<li>
<button class="accordion">Collections</button>
<ul class="dropDown">
<li><p>Mojica Lookbook</p></li>
<li><p>Andrade Editorial</p></li>
<li><p>Bell Videos</p></li>
</ul>
</li>
<li>
Shop
</li>
<li>
Stores
</li>
<li>
Contact
</li>
<li>
<img src="mojica_lookbook/mojica_credits.png" class="credits">
</li>
</ul>
</nav>
</body>
<script>
var dropDown = document.getElementsByClassName("dropDown");
var i = 0;
for(i = 0; i < dropDown.length; i++) {
dropDown[i].onclick = function(){
this.classList.toggle("active");
this.nextElementSibling.classList.toggle("show");
}
}
</script>
</html>
I believe you will need to use the element passed into the callback for the onclick. Instead of using the 'this' keyword within the callback try this:
for(i = 0; i < dropDown.length; i++) {
dropDown[i].onclick = function(e){
e.target.classList.toggle("active");
e.target.nextElementSibling.classList.toggle("show");
}
}
There are a few examples here. As for the button you have not mapped a click event, so that will as you explain do nothing.
I have this HTML:
<ul class="parent">
<ul>
<li>
<ul></ul>
</li>
<li>
<ul>
<li>
<ul></ul>
</li>
</ul>
</li>
</ul>
</ul>
I need to add count classes for all nested lists in this markup, to reach this:
<ul class="parent">
<ul class="level-1">
<li>
<ul class="level-2"></ul>
</li>
<li>
<ul class="level-2">
<li>
<ul class="level-3"></ul>
</li>
</ul>
</li>
</ul>
<ul class="level-1">
<li>
<ul class="level-2"></ul>
</li>
<li>
<ul class="level-2">
<li>
<ul class="level-3"></ul>
</li>
</ul>
</li>
</ul>
</ul>
So i do this:
var parent_ul = $('.parent');
if (parent_ul.doesExist()){
var parent_ul_lists = parent_ul.find('ul');
parent_ul_lists.each(function(){
var i;
for (i = 0; i < parent_ul_lists.length; i++) {
$(this).eq(i).addClass('level-' + i);
}
})
}
But in output i have class test level-1 for all of parent list childrens. Can anybody help?
Try this
$('.parent ul').addClass(function(){
return "level-"+$(this).parents('ul').length;
});
DEMO
Try this
var parent = $('.parent').children();
next = parent;
var i = 0;
while (next.length) {
parent = next;
parent.addClass("Level_" + i);
i++;
next = next.children();
}
Demo
You need some good 'ole recursion!
Check out this JSFiddle UPDATE: corrected jsFiddle link
Here is the code:
function labelChildUL(element, count) {
element.children().each(function (index, value) {
var $me = $(value);
if ($me.is("ul")) {
$me.addClass("level-" + (count + 1));
labelChildUL($me, count + 1);
}
else
{
labelChildUL($me, count);
}
});
}
$(document).ready(function () {
var $parent = $('#parent');
labelChildUL($parent, 0);
});
I also updated your html to use id instead of class for "parent":
<ul id='parent'>
...
You can use this;
var parent_ul = $('.parent');
var parent_ul_lists = parent_ul.find('ul');
parent_ul_lists.each(function(){
$(this).addClass('level-'+ ($(this).parents().length -1)/2);
});
See working demo here: http://jsfiddle.net/huseyinbabal/qmGcC/
Note: Inspect element in output to see class assigned