I have nested unordered-lists. Each unordered-list has an h1 tag as its 'previous-sibling' tag. My goal is to click on an h1 tag and toggle the visibility of the unordered-list that comes right after it.
I am also trying to also assign classNames to each of unordered lists, based on the their title (h1) tag.
Is anyone able to help me understand why my code is not working?
Here is the code:
window.onload = function() {
let titles = document.getElementsByTagName('h1');
for (let i = 0 ; i < titles.length ; i++) {
let title = titles[i];
//grab the text in the h1 element, and add that text as a class to the h1:
let className = title.innerHTML.toLowerCase();
title.className += title.className.length ? ' ' + className : className;
let section = document.querySelectorAll(`.${className} + ul`);
if(section.length) {
section[0].className += section[0].className.length
?
` ${className} section`
:
`${className} section`;
//ADD EVENT HANDLER TO THE TITLE.
//SHOULD HIDE AND SHOW THE ADJASCENT UL ELEMENT:
title.onclick = function(e) {
section[0].classList.toggle('hide');
};
}
}
};
/* trying to toggle visibility with this class*/
.hide {
display: none;
}
/*basic styles to separate elements:*/
h1 {
color: olive;
}
ul {
border: solid orange 1px;
}
li {
//border: solid magenta 1px;
}
<div id="foods">
<h1>Food</h1>
<ul>
<li>
<h1>Fruit</h1>
<ul>
<li>
<h1>tropical</h1>
<ul>
<li>banana</li>
<li>pineapple</li>
<li>mango</li>
</ul>
</li>
<li>
<h1>stone</h1>
<ul>
<li>peach</li>
<li>pear</li>
<li>appricot</li>
</ul>
</li>
</ul>
</li>
<li>
<h1>Vegetables</h1>
<ul>
<li>
<h1>leafy</h1>
<ul>
<li>lettuce</li>
<li>spinach</li>
<li>kale</li>
</ul>
</li>
<li>
<h1>root</h1>
<ul>
<li>carrot</li>
<li>turnip</li>
<li>potato</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
The chief issue is you're using the wrong CSS property. You want display, not visibility:
.hide {
display: none;
}
If you were using visibility, the value would be hidden, but it would continue to take up room in the layout, which I'm sure isn't what you want.
But separately, the code to hook up those event handlers and add those classes (you've said you're using them for things later) can be a bit simpler:
window.onload = function() {
function toggleNext() {
this.nextElementSibling.classList.toggle("hide");
}
let titles = document.getElementsByTagName('h1');
for (let i = 0 ; i < titles.length ; i++) {
const title = titles[i];
const section = title.nextElementSibling;
const titleClass = title.innerHTML.toLowerCase();
title.classList.add(titleClass);
section.classList.add(titleClass, "section");
titles[i].addEventListener("click", toggleNext);
}
};
Live Example:
window.onload = function() {
function toggleNext() {
this.nextElementSibling.classList.toggle("hide");
}
let titles = document.getElementsByTagName('h1');
for (let i = 0 ; i < titles.length ; i++) {
const title = titles[i];
const section = title.nextElementSibling;
const titleClass = title.innerHTML.toLowerCase();
title.classList.add(titleClass);
section.classList.add(titleClass, "section");
titles[i].addEventListener("click", toggleNext);
}
};
/* trying to toggle visibility with this class*/
.hide {
display: none;
}
/*basic styles to separate elements:*/
h1 {
color: olive;
}
ul {
border: solid orange 1px;
}
li {
//border: solid magenta 1px;
}
<div id="foods">
<h1>Food</h1>
<ul>
<li>
<h1>Fruit</h1>
<ul>
<li>
<h1>tropical</h1>
<ul>
<li>banana</li>
<li>pineapple</li>
<li>mango</li>
</ul>
</li>
<li>
<h1>stone</h1>
<ul>
<li>peach</li>
<li>pear</li>
<li>appricot</li>
</ul>
</li>
</ul>
</li>
<li>
<h1>Vegetables</h1>
<ul>
<li>
<h1>leafy</h1>
<ul>
<li>lettuce</li>
<li>spinach</li>
<li>kale</li>
</ul>
</li>
<li>
<h1>root</h1>
<ul>
<li>carrot</li>
<li>turnip</li>
<li>potato</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
Side note: I would recommend not using window.load unless you really need this code to wait for all images, stylesheets, etc., to fully load before running. Instead, just make sure the code is in a script tag at the end of the document, just prior to the closing </body> tag.
Related
I want to change the value of the progress bar when each tap is clicked.
var taps = document.getElementsByClassName('et_pb_tab');
for (var i = 0; i < taps.length; i++) {
taps[i].addEventListener('click', () => {
var v1 = document.getElementById('p1').value;
v1.value = v1 + 20;
});
}
li.et_pb_tab_active {
background-color: rgba(255, 211, 14, 0.29);
}
<progress value="0" max="100" id="p1"></progress>
<ul class="et_pb_tabs_controls">
<li class="et_pb_tab et_pb_tab_active">Clase N° 1</li>
<li class="et_pb_tab" style="height: 64.8px;">Clase N° 2</li>
<li class="et_pb_tab" style="height: 64.8px;">Clase N° 3</li>
<li class="et_pb_tab" style="height: 64.8px;">Clase N° 4</li>
<li class="et_pb_tab" style="height: 64.8px;">Clase N° 5</li>
</ul>
I was trying to get the element "et_pb_tab" that is each tap and with a for add the listener, then click on the tap, the progress bar adds 20 to the value, but it's not working.
You were very close. You had mixed up the progress element itself with its value. Here's a slightly revised example of how it might work:
const
v1 = document.getElementById('p1'),
taps = document.getElementsByClassName('et_pb_tab');
for (const tap of taps) {
tap.addEventListener('click', () => v1.value += 20);
}
.et_pb_tab{ width: 2em; margin-bottom: 0.5em; border: 1px solid grey; text-align: center; }
<progress value="0" max="100" id="p1"></progress>
<ul class="et_pb_tabs_controls">
<li class="et_pb_tab"> 1 </li>
<li class="et_pb_tab"> 2 </li>
<li class="et_pb_tab"> 3 </li>
<li class="et_pb_tab"> 4 </li>
<li class="et_pb_tab"> 5 </li>
</ul>
But you might want to consider using event delegation:
const
v1 = document.getElementById('p1'),
container = document.getElementsByClassName('et_pb_tabs_controls')[0];
container.addEventListener('click', handleClick);
function handleClick(event){ // Events bubble up to ancestors
const clickedThing = event.target;
if(!clickedThing.classList.contains("et_pb_tab")){//Ignores irrelevant clicks
return;
}
v1.value += 20;
}
.et_pb_tab{ width: 2em; margin-bottom: 0.5em; border: 1px solid grey; text-align: center; }
<progress value="0" max="100" id="p1"></progress>
<ul class="et_pb_tabs_controls">
<li class="et_pb_tab"> 1 </li>
<li class="et_pb_tab"> 2 </li>
<li class="et_pb_tab"> 3 </li>
<li class="et_pb_tab"> 4 </li>
<li class="et_pb_tab"> 5 </li>
</ul>
At the moment you're immediately grabbing the value from the progress element. Just select the element, and then use the value its new value in the calculation.
Cache all the elements you need up front so that you don't need to hit the DOM on each click.
The anchors don't do much so you can probably remove them.
Instead of attaching a listener to each list item you can attach one to the container (the ul element), and then have that catch events from its children as they "bubble up" the DOM. This is known as event delegation.
If you want to add/remove active classes to your list items you can do that. Within the handleClick function remove all the active classes from the list items first, and then add one to the element that was clicked.
// Cache the progress bar, list and list items up front
const progress = document.querySelector('#p1');
const list = document.querySelector('.et_pb_tabs_controls');
const items = list.querySelectorAll('li.et_pb_tab');
// Add one listener to the list element
list.addEventListener('click', handleClick);
// When the listener detects a click...
function handleClick(e) {
// ...check that it's a list item
if (e.target.matches('li')) {
// Iterate over the list items removing all their
// active classes
items.forEach(item => item.classList.remove('active'));
// Add one to the clicked element
e.target.classList.add('active');
// Update the value of the progress bar
progress.value = progress.value + 20;
}
}
:root { --active: rgba(255,211,14,0.29); }
ul { list-style: none; margin-left: 0px; padding-left: 0px; }
li { background-color: lightgray; padding: 0.25em; }
li:hover { cursor: pointer; background-color: var(--active) }
.active { background-color: var(--active) }
<progress value="0" max="100" id="p1"></progress>
<ul class="et_pb_tabs_controls">
<li class="et_pb_tab">Clase N° 1</li>
<li class="et_pb_tab">Clase N° 2</li>
<li class="et_pb_tab">Clase N° 3</li>
<li class="et_pb_tab">Clase N° 4</li>
<li class="et_pb_tab">Clase N° 5</li>
</ul>
I think you are looking for something more like this?
(and the use of a hyperlink complicates more than it helps)
const
progressBarEl = document.querySelector('#p1')
, taps = document.querySelector('ul.et_pb_tabs_controls')
, tpas_li = document.querySelectorAll('ul.et_pb_tabs_controls > li')
;
taps.onclick = evt =>
{
if (!evt.target.matches('ul.et_pb_tabs_controls > li')) return
tpas_li.forEach( (li,indx) =>
{
if ( li.classList.toggle('et_pb_tab_active', li===evt.target) )
{
progressBarEl.value = (indx +1) *20
}
})
}
body {
font-family : Arial, Helvetica, sans-serif;
font-size : 16px;
}
ul.et_pb_tabs_controls {
list-style: none;
}
ul.et_pb_tabs_controls li {
height : 2em;
line-height : 2em;
cursor : pointer;
}
ul.et_pb_tabs_controls li.et_pb_tab_active {
background-color: rgba(255, 211, 14, 0.29);
}
<progress value="20" max="100" id="p1"></progress>
<ul class="et_pb_tabs_controls">
<li class="et_pb_tab_active" > Clase N° 1 </li>
<li > Clase N° 2 </li>
<li > Clase N° 3 </li>
<li > Clase N° 4 </li>
<li > Clase N° 5 </li>
</ul>
I have a list of letters, each one of those letters is inside a li tag :
<ul>
<li class="letter">A</li>
<li class="letter">B</li>
<li class="letter">C</li>
<li class="letter">D</li>
</ul>
The question is how to make those letters (A,B,C,D) hidden without hide the li tag itself, because it already have a style and I don't want to lose it..
and how to make them visible using javaScript function?
PS: the list is created by javaScript function :
var ul = document.createElement('ul');
ul.setAttribute('id','letters');
var txt = txtword.value;
var t, tt;
var word = new Array();
for (i=0;i<txt.length;i++)
word[i] = txt[i];
document.getElementById('answer_div').appendChild(ul);
word.forEach(wordletters);
function wordletters(element, index, arr) {
var li = document.createElement('li');
li.setAttribute('class','item');
ul.appendChild(li);
t = document.createTextNode(element);
li.innerHTML=li.innerHTML + element;
}
Thanks in advance.
<ul>
<li class="letter"><input style="display:none;" type="text" id="A" value="A"></li>
<li class="letter"><input style="display:none;" type="text" id="B" value="B"></li>
<li class="letter"><input style="display:none;" type="text" id="C" value="C"></li>
<li class="letter"><input style="display:none;" type="text" id="D" value="D"></li>
</ul>
You can use to show li data by below function.
function show_li_data(id){
$("#"+id).show();
}
one way to do it you can simply wrap the A,B,C,D inside a span like this
<ul>
<li class="letter"><span>A<span></li>
<li class="letter"><span>B<span></li>
<li class="letter"><span>C<span></li>
<li class="letter"><span>D<span></li>
</ul>
and then
li span{
display:none
}
Here is the the codepen :https://codepen.io/anon/pen/oEmJLP
Consider visibility property:
.letter:nth-child(2) {
visibility: hidden;
}
<ul>
<li class="letter">A</li>
<li class="letter">B</li>
<li class="letter">C</li>
<li class="letter">D</li>
</ul>
Or consider a small width with some oveflow trick if you want to keep the bullet.
li {
list-style:inside;
}
.letter:nth-child(2) {
width:5px;
overflow:hidden;
white-space:nowrap;
}
<ul>
<li class="letter">A</li>
<li class="letter">B</li>
<li class="letter">C</li>
<li class="letter">D</li>
</ul>
Then simply use a class that you can add/remove with JS:
document.querySelectorAll('.letter')[2].classList.add('hide');
li {
list-style:inside;
}
.hide{
width:5px;
overflow:hidden;
white-space:nowrap;
}
<ul>
<li class="letter">A</li>
<li class="letter">B</li>
<li class="letter">C</li>
<li class="letter">D</li>
</ul>
Have modified the answer.Try using text-indent; In your case just add this:
style="text-indent:-9999px;" in each li tag
The solution is :
function wordletters(element, index, arr) {
var li = document.createElement('li');
li.setAttribute('class','item');
ul.appendChild(li);
var span = document.createElement('span');
li.appendChild(span);
span.innerHTML=span.innerHTML + element;
}
This function will create a span inside the li tag, so I can hide the span without hide the li tag ..
Thank you everyone for your time ^_^
You should select this elements by their class name, and set display:none.
document.getElementByClassName("letter").style.display = 'none'
document.getElementByClassName("letter").style.display = 'block'
I am new in Jquery
My Webpage structure is like this
<div id="MenuSection">
<ul>
<li>Master // Main Menu
<ul>
<li>Master1</li>
<li>Master2</li>
<li>Master3</li>
</ul>
</li>
<li>Transaction // Main Menu
<ul>
<li>Transaction1</li>
<li>Transaction2</li>
<li>Transaction3</li>
</ul>
</li>
<li>Report // Main Menu
<ul>
<li>Report1</li>
<li>Report2</li>
<li>Report3</li>
</ul>
</li>
</ul>
</div>
I want that when all the children of any Parent(main menu) are hidden, Parent should also be hidden. Let's say if Report1, Report2, Report3 are hidden then Parent that is "Report" should also be hidden.
How can I achieve this through Jquery ?
One way is to iterate over each main menu li to see if its children are all :visible:
$("#MenuSection>ul>li").each(function() {
if ($(this).find(">ul>li:visible").length == 0) {
$(this).hide();
}
});
there are other ways to do this, such as using .filter or .map, but this should get you what you need.
Given the nested ul's the above uses > to ensure only the directly ul>li children are processed. If you have multiple levels, you might need to change accordingly, eg for the first: #MenuSection li would apply to all lis and the second .find(">ul>li:visible") only looks at direct li children.
$("#MenuSection>ul>li").each(function() {
if ($(this).find("li:visible").length == 0) {
$(this).hide();
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="MenuSection">
<ul>
<li>Master
<ul>
<li>Master1</li>
<li>Master2</li>
<li>Master3</li>
</ul>
</li>
<li>Transaction
<ul>
<li>Transaction1</li>
<li>Transaction2</li>
<li>Transaction3</li>
</ul>
</li>
<li>Report
<ul>
<li style='display:none'>Report1</li>
<li style='display:none'>Report2</li>
<li style='display:none'>Report3</li>
</ul>
</li>
</ul>
</div>
JavaScript does it fairly easy. You would need expand on this to execute this check every on the relevant list every time you hide or show a list item.
function isHidden(array) {
for(var i = 0; i < array.length - 1; i++) {
if(array[i+1].style.display != "none") {
return false;
};
};
return true;
};
var children = document.getElementById("report").getElementsByTagName("LI");
if (isHidden(children)) {
document.getElementById("report").style.display = "none";
};
You can use the the .is(':visible')
See the code:
(function($) {
$(document).ready(function() {
var $mainLinks = $('#MenuSection > ul > li');
$.each($mainLinks, function() {
if (!$(this).find('li').is(':visible')) {
$(this).hide();
}
});
});
})(jQuery);
#MenuSection > ul > li:last-child li {
display: none;
}
#MenuSection > ul > li:first-child li:first-child {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="MenuSection">
<ul>
<li>Master // Main Menu
<ul>
<li>Master1</li>
<li>Master2</li>
<li>Master3</li>
</ul>
</li>
<li>Transaction // Main Menu
<ul>
<li>Transaction1</li>
<li>Transaction2</li>
<li>Transaction3</li>
</ul>
</li>
<li>Report // Main Menu
<ul>
<li>Report1</li>
<li>Report2</li>
<li>Report3</li>
</ul>
</li>
</ul>
</div>
I make a secondary menu and I like it to be displayed when user hover a specific one of the main menu items....
I tried this code but it didn't work...
.second-menu {display:none}
ul li #2:hover + .second-menu {display:block}
<ul>
<li id="1">first</li>
<li id="2">second</li>
<li id="3">third</li>
<ul>
<div class="second-menu">
<ul>
<li>page1</li>
<li>page2</li>
<li>page3</li>
</ul>
</div>
any suggestions?....
only by css or javascript....
If you wish to use CSS, you will have to put your sub menu inside the element that you want to hover.
For the CSS, C.Raf.T's answer is perfect.
If for some reason you want to use javascript you could do something like this
document.getElementById('2').addEventListener('mouseenter', function ()
{
document.getElementById('subMenu').style.display = "block";
});
document.getElementById('2').addEventListener('mouseleave', function ()
{
document.getElementById('subMenu').style.display = "none";
});
Note: the above code requires you to add a "subMenu" id to the div containing your menu. If you wish to display serval menus with only one hover event, use a class instead.
But honestly, the CSS answer is the best way to go if all you need is nested sub menus.
If the sub menu has to be outside of the parent, you will need the javascript.
.second-menu{
display:none;
}
li:hover >.second-menu{
display:block;
}
<ul>
<li id="1">first</li>
<li id="2">second
<ul class="second-menu">
<li>page1</li>
<li>page2</li>
<li>page3</li>
</ul>
</li>
<li id="3">third</li>
<ul>
Answer using Javascript,
document.getElementById('hover').onmouseover = function(){
document.getElementById('second-menu').style.display = 'block';
}
document.getElementById('hover').onmouseout = function(){
document.getElementById('second-menu').style.display = 'none';
}
.second-menu{
display:none;
}
<ul id="hover">
<li id="1">first</li>
<li id="2">second</li>
<li id="3">third</li>
<ul>
<div class="second-menu" id="second-menu">
<ul>
<li>page1</li>
<li>page2</li>
<li>page3</li>
</ul>
</div>
Here is a fiddle
By using pure CSS you have to ensure that your submenu (.second-menu) is a child-node of your hovered HTML-Element. Because CSS unfortunately doesn't know a parent selector.
By using JS you are more flexible. Means placing the submenu wherever you wish.
* { margin: 0; padding: 0; }
.second-menu {display:none; border: 1px solid blue; width: 100%; position: absolute; left: 0; right: 0; }
ul li#two:hover > .second-menu {display:block}
.relative { position: relative; border: 1px solid black; }
li { display: inline-block; }
<ul class="relative">
<li id="one">first</li>
<li id="two">second
<ul class="second-menu">
<li>page1</li>
<li>page2</li>
<li>page3</li>
</ul>
</li>
<li id="three">third</li>
<ul>
I am kind of confused why my code doesn't work correctly, I hope You will tell me what I've done wrong.
I want to highlight navigation tab while clicked.
HTML:
<header class="mainheader">
<!-- Obrazek tutaj-->
<nav>
<ul>
<li><a id="a-home" onclick="dodajAktywne(this)" href="index.html">Home</a></li>
<li><a id="a-omnie" onclick="dodajAktywne(this)" href="omnie.html">O mnie</a></li>
<li><a id="a-kurs" onclick="dodajAktywne(this)" href="kurs.html">Kurs</a></li>
<li><a id="a-kontakt" onclick="dodajAktywne(this)" href="kontakt.html">Kontakt</a></li>
</ul>
</nav>
</header>
JavaScript:
function dodajAktywne(elem) {
var a = document.getElementsByTagName('a')
for (i = 0; i < a.length; i++) {
a[i].classList.remove('active');
}
elem.classList.add('active');
}
CSS:
.active {
color: blue;
background-color: #cf5c3f;
}
You can simplify your JavaScript to:
Fiddle
function dodajAktywne(elem) {
// get all 'a' elements
var a = document.getElementsByTagName('a');
// loop through all 'a' elements
for (i = 0; i < a.length; i++) {
// Remove the class 'active' if it exists
a[i].classList.remove('active')
}
// add 'active' classs to the element that was clicked
elem.classList.add('active');
}
If you pass the parameter this in your HTML to:
<header class="mainheader">
<!-- Obrazek tutaj-->
<nav>
<ul>
<li><a id="a-home" onclick="dodajAktywne(this)" href="#">Home</a>
</li>
<li><a id="a-omnie" onclick="dodajAktywne(this)" href="#">O mnie</a>
</li>
<li><a id="a-kurs" onclick="dodajAktywne(this)" href="#">Kurs</a>
</li>
<li><a id="a-kontakt" onclick="dodajAktywne(this)" href="#">Kontakt</a>
</li>
</ul>
</nav>
</header>
Note: I've changed href attribute to #, you will have to change it back to your .html pages
Alternatively, you can do this without JavaScript, using CSS's :focus:
Fiddle
a:focus {
color: blue;
background-color: #cf5c3f;
}
This code will highlight the navigation tab without needing to include onclick in the HTML, though it doesn't remove the background color if another tab is clicked.
document.onclick = function(event) {
var target = event.target || event.srcElement;
target.style.background = '#cf5c3f';
};
https://codepen.io/mdmcginn/pen/BwrNaj/
var list = document.querySelector('ul');
list.addEventListener('click', function(ev) {
if (ev.target.tagName === 'LI') {
ev.target.classList.toggle('checked');
}
}, false);