I am trying to build a vertical tabs, but the list items should be connected to the divs seperately. All the divs are hidden with display: none; and when someone clicks on for example tab2, the second div should be turned into display: block. In other words, the className "selected" will be added to that specific div. Got the following error in console.log (Cannot read property 'classList' of null at HTMLUListElement.). Anyone here who knows how to write the JavaScript so the list items are connected to the divs.
Only in plain JavaScript please.
document.getElementById("verticalUl").addEventListener("click", function(e) {
var e = e || window.event,
elements = document.getElementById("verticalUl").children,
content = document.querySelectorAll(".support-box");
if(e.target && e.target.nodeName == "LI") {
var attribute = e.target.getAttribute("id");
for(var i = 0; i < elements.length; i++) {
elements[i].classList.remove("active-support");
}
for (var i = 0; i < content.length; i++) {
content[i].classList.remove("selected");
};
document.querySelector("." + attribute + "-box").classList.add("selected");
e.target.classList.add("active-support");
}
});
.group {
display: none;
}
.selected {
display: block;
}
.vertical-navbar {
float: left;
width: 20%;
}
.vertical-navbar li {
list-style-type: none;
border: 1px solid red;
margin: 5px;
}
.support-box {
width: 60%;
float: right;
height: 300px;
border: 1px solid red;
}
<ul class="vertical-navbar" id="verticalUl">
<li id="tab1" class="support-tabs-label active-support">Hot Topics</li>
<li id="tab2" class="support-tabs-label">Account</li>
<li id="tab3" class="support-tabs-label">Product</li>
<li id="tab4" class="support-tabs-label">Order and Shipping</li>
</ul>
<div class="content-support1 support-box tab1-box group selected">
Hot Topics
</div>
<div class="content-support2 support-box tab2-box group">
Account
</div>
<div class="content-support3 support-box tab3-box group">
Product
</div>
<div class="content-support4 support-box tab4-box group">
Order and Shipping
</div>
On the assumption that all of the support-box elements have tabx-box, where tabx matches the clicked li element, you can simply use:
document.querySelector("." + attribute + "-box").classList.add("selected");
The Solution
document.getElementById("verticalUl").addEventListener("click", function(e) {
var e = e || window.event,
elements = document.getElementById("verticalUl").children,
content = document.querySelectorAll(".support-box");
for (var i = 0; i < content.length; i++) {
content[i].classList.remove("selected");
}
if (e.target && e.target.nodeName == "LI") {
var attribute = e.target.getAttribute("id");
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove("active-support");
}
e.target.classList.add("active-support");
document.querySelector("." + attribute + "-box").classList.add("selected");
}
});
https://jsfiddle.net/j8k09r9s/1/
Related
I'm working on displaying a list of books, and I have it set up so you can filter by category. That part of my JavaScript works, but I need to make one tweak. Right now, on page load nothing shows up until you click "show all." What do I need to change in my code so everything loads when the page loads? (Hope that makes sense)
My HTML:
<div id="myBtnContainer">
<button class="bookbtn active" onclick="filterSelection('all')">Show all</button>
<button class="bookbtn" onclick="filterSelection('autobiography')">Autobiography</button>
<button class="bookbtn" onclick="filterSelection('poetry')">Poetry</button>
<button class="bookbtn" onclick="filterSelection('fiction')">Fiction</button>
<button class="bookbtn" onclick="filterSelection('children')">Children</button></div>
<!-- Begin the listing of ALL THE BOOKS! -->
<div class="bookcontainer">
<ul class="booklist">
<div class="filterDiv autobiography">
<li>
<a href="books/singlebook.html">
<img src="images/books/hardy_silence.png" alt="" class="img-book" />
<div class="book-title">Book Title<br></div>
<div class="book-author">by Author</div>
</a>
</li>
</div>
CSS:
.bookcontainer {
overflow: hidden;
}
#myBtnContainer {
z-index: 125;
margin-bottom: 1.5rem;
align-self: left;
align-items: left;
text-align: left;
}
.filterDiv {
float: left;
text-align: center;
display: none;
/* Hidden by default */
}
/* The "show" class is added to the filtered elements */
.show {
display: block;
}
/* Style the buttons */
.bookbtn {
border: none;
outline: none;
padding: 12px 16px;
margin-right: 10px;
background-color: #f1f1f1;
cursor: pointer;
}
/* Add a light grey background on mouse-over */
.bookbtn:hover {
background-color: #ddd;
}
/* Add a dark background to the active button */
.bookbtn.active {
background-color: #666;
color: white;
}
and my JavaScript (copied from w3 if I'm being completely honest):
filterSelection("all")
function filterSelection(c) {
var x, i;
x = document.getElementsByClassName("filterDiv");
if (c == "all") c = "";
// Add the "show" class (display:block) to the filtered elements, and remove the "show" class from the elements that are not selected
for (i = 0; i < x.length; i++) {
w3RemoveClass(x[i], "show");
if (x[i].className.indexOf(c) > -1) w3AddClass(x[i], "show");
}
}
// Show filtered elements
function w3AddClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
if (arr1.indexOf(arr2[i]) == -1) {
element.className += " " + arr2[i];
}
}
}
// Hide elements that are not selected
function w3RemoveClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
while (arr1.indexOf(arr2[i]) > -1) {
arr1.splice(arr1.indexOf(arr2[i]), 1);
}
}
element.className = arr1.join(" ");
}
// Add active class to the current control button (highlight it)
var btnContainer = document.getElementById("myBtnContainer");
var btns = btnContainer.getElementsByClassName("bookbtn");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function () {
var current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace(" active", "");
this.className += " active";
});
}
Just simply add:
window.onload = () => {
filterSelection('all')
};
So it will trigger it when page loads.
I'm struggling with a to-do app. I want to cross out a LI element and add a span with an x sign. It's easy by toggling class. However, when I "untoggle" the class by clicking again. The "x" remains and when I click on the item again the "x" is duplicated. How can I prevent adding another "x" or make the "x" disappear when the items is "untoggled".
const addButton = document.querySelector("#add");
const input = document.querySelector("input[name='input-item'");
const ul = document.querySelector("ul");
const allItems = document.querySelectorAll("li");
for (let i = 0; i < allItems.length; i++) {
allItems[i].addEventListener("click", myList);
}
function myList() {
let temp = this.classList.toggle("red");
if (temp) {
let span = document.createElement("span");
span.innerHTML = "×";
span.addEventListener("click", function() {
this.parentElement.remove();
});
this.appendChild(span);
} else if (this.classList.contains("red")) {
this.getElementByTagName("span").remove();
}
}
.red {
text-decoration: line-through;
color: red;
}
span {
background-color: white;
padding: 0 0.3rem;
color: black;
margin: 0 0.2rem;
display: inline-block;
}
<div class="container">
<ul>
<li>banana</li>
<li>orange</li>
<li>grapes</li>
</ul>
<input type="text" name="input-item" placeholder="Enter a new item" /><button id="add">Add Item</button>
</div>
I am not familar with JavaScript DOM , mostly i use jQuery for script.. I am trying on JavaScript and in trouble with the below code.
Does anyone know what could be the problem?
What i would like to do is when LI is hovered, the nested OL is displayed..somehow, the below code doesn't work, even it doesn't show any error in console.
Please help me...
var ul = document.querySelector('.gnb');
var li = ul.children;
var ol = document.getElementsByTagName('ol');
var i;
for (i = 0; i < li.length; i++) {
li[i].addEventListener('mouseenter', myFunction(myshow));
li[i].addEventListener('mouseleave', myFunction(myhide));
}
function myshow() {
ol.style.display = 'block'
}
function myhide() {
ol.style.display = 'none'
}
function myFunction(fn) {
return function(e) {
if (e.target.type !== "mouseenter") return;
fn.call(e.target)
};
}
.gnb {
float: left;
margin-left: 30px;
width: auto;
height: 100%;
}
.gnb>li {
float: left;
width: 150px;
height: 100%;
list-style: none;
}
.sub {
display: none;
}
.sub.show {
display: block;
}
<ul class="gnb">
<li class="gnbLi">
Why Mailchimp?
</li>
<li class="gnbLi" onmouseenter="myFunction()">
What You Can Do
<ol class="sub">
<li>Overview</li>
<li>Create</li>
</ol>
</li>
</ul>
Problem:
Basically what you have done is dealing with an array as a single element because of getElementsByTagName returns an array of ol in the whole page.
Solve:
1. you need to get the nested ol inside the li that's being hovered using
-event.target to get the clicked li.
- querySelector to get the ol under that li.
2. you need the ol to be displayed/removed depending on the event.
so only change the Javascript to be as the following and it will work
var ul = document.querySelector('.gnb');
var li = ul.children;
for (i = 0; i < li.length; i++) {
li[i].addEventListener('mouseenter', mouseenter);
li[i].addEventListener('mouseleave',mouseleave );
}
function mouseenter(event) {
var ol = event.target.querySelector('ol');
if(ol){
ol.style.display = 'block';
}
}
function mouseleave(event) {
var ol = event.target.querySelector('ol');
if(ol){
ol.style.display = 'none';
}
}
Your code is not correct and clean, I changed a little bit of your code:
(function () {
var ul = document.querySelector('.gnb');
var li = ul.children
var ol = document.getElementsByTagName('ol')[0];
var i;
for (i = 0; i < li.length; i++) {
li[i].addEventListener('mouseenter', myFunction(myshow));
li[i].addEventListener('mouseleave', myFunction(myhide));
}
function myshow() {
ol.style.display = 'block'
}
function myhide() {
ol.style.display = 'none'
}
function myFunction(fn) {
return fn;
}
})();
.gnb {
float: left;
margin-left: 30px;
width: auto;
height: 100%;
}
.gnb > li {
float: left;
width: 150px;
height: 100%;
list-style: none;
}
.sub {
display: none;
}
.sub.show {
display: block;
}
<ul class="gnb">
<li class="gnbLi">
Why Mailchimp?
</li>
<li class="gnbLi">
What You Can Do
<ol class="sub">
<li>Overview</li>
<li>Create</li>
</ol>
</li>
</ul>
Update 10/4/18: I've updated the Snippet to reflected changes for anyone who may stumble upon this thread in seek of help. Existing check-boxes and newly added check-boxes will open/close the menu.
var statusChangeMenu, activeList, itemCheckBox, activeItems;
statusChangeMenu = document.getElementById("status-change-menu");
activeList = document.getElementById("active-items");
itemCheckBox = activeList.getElementsByClassName("item-checkbox");
activeItems = activeList.getElementsByClassName("active-item-text");
function addNewItem(event) {
event.preventDefault();
activeList.insertAdjacentHTML("afterbegin", "\
<li class=\"item\">\
<input class=\"item-checkbox\" type=\"checkbox\" name=\"checkbox\" />\
<span class=\"active-item-text\"></span>\
<button class=\"btn-complete\">complete</button>\
</li>");
activeItems[0].textContent = document.getElementById("new-item-text").value;
}
document.getElementById("btn-add-item").addEventListener("click", addNewItem, false);
activeList.addEventListener("change", function() {
var i, len;
for (i = 0, len = itemCheckBox.length; i < len || (i = 0); ++i) {
if (itemCheckBox[i].checked) {
i = 40;
break;
}
}
statusChangeMenu.style.height = i + "px";
}, false);
*{
margin: 0;
padding: 0;
}
body{
background-color: #393F4D;
}
header{
background-color: #1D1E22;
color: #FEDA6A;
text-align: center;
font-size: 10px;
}
main{
background-color: #707070;
max-width: 700px;
margin: auto;
margin-top: 20px;
padding: 15px;
}
#status-change-menu{
background-color: rgb(218, 123, 123);
margin-top: 10px;
height: 0px;
overflow: hidden;
transition: all .25s ease-in-out;
}
#status-change-menu>button>img{
height: 40px;
}
form{
background-color: #D4D4DC;
padding: 10px;
text-align: right;
box-shadow: 1px 1px 3px;
}
#new-item-text{
width: 100%;
}
#btn-add-item{
padding: 5px;
box-shadow: 1px 1px 3px;
}
.item-list-container{
background-color: #D4D4DC;
margin-top: 20px;
box-shadow: 1px 1px 3px;
}
.item{
background-color: rgb(165, 233, 222);
list-style: none;
display: grid;
grid-template-columns: auto 1fr max-content;
grid-template-rows: 30px;
margin-top: 10px;
}
.item-checkbox{
grid-column: 1/2;
width: 30px;
margin:auto;
}
.active-item-text{
grid-column: 2/3;
background: rgb(252, 252, 252);
overflow: hidden;
}
.btn-complete{
grid-column: 3/4;
}
.item>input{
height: 20px;
}
<body id="the-list">
<header>
<h1>The List V4</h1>
</header>
<main>
<form action="#">
<textarea name="textbox" id="new-item-text" cols="30" rows="1"></textarea>
<button type="submit" id="btn-add-item">Add</button>
</form>
<div id="status-change-menu" class="change-menu">
<h3>Status Change Menu</h3>
<button class="btn-bar-hold">BTN1<img src="img/btn_hold.svg" alt=""></button>
<button class="btn-bar-delete">BTN2<img src="img/btn_delete.svg" alt=""></button>
</div>
<div class="item-list-container">
<ul id="active-items" class="item-list">
<li class="item">
<input class="item-checkbox" type="checkbox" name="checkbox">
<span class="active-item-text">random text text random</span>
<button class="btn-complete">complete</button>
</li>
<li class="item">
<input class="item-checkbox" type="checkbox" name="checkbox">
<span class="active-item-text">random text text random</span>
<button class="btn-complete">complete</button>
</li>
</ul>
</div>
</main>
</body>
I'm working on a simple checklist web app using pure vanilla HTML, CSS, javascript. I've been stuck in one part all weekend. Hoping someone can shed some light on what I'm missing or doing wrong. Here's where I'm at.
My Goal
Whenever an item in the checklist (ul) is selected (via checkbox), a hidden menu slides out with various options to manipulate the selected item(s). The menu must stay visible if any of the checkboxes on the list are checked. The menu must close if no checkboxes are checked.
Where I'm Stuck
I'm able to get the menu to slide out during a 'change' event of the checkbox, but I can't get the menu element to react after the initial change event. During debugging, it also appears the menu element is not reacting to the checkbox is in a 'checked' state, but simply just reacting to the checkbox being changed in general. Here's the JS code I have, but I've tested various other configurations with no success.
Code Pen with Full Code & Snippet of related JS code below.
Updated Codepen 10/4/18
https://codepen.io/skazx/pen/mzeoEO?
var itemCheckBox = document.querySelectorAll('input[type="checkbox"]')
var statusChangeMenu = document.getElementById("status-change-menu")
for(var i = 0 ; i < itemCheckBox.length; i++){
itemCheckBox[i].addEventListener("change", function(){
if (!itemCheckBox.checked)
{statusChangeMenu.style.height = "40px";}
else
{statusChangeMenu.style.height = "0px";}
})}
I've read a few dozen different post and articles, but most were related to only having 1 checkbox or used jquery. Let me know if you need any further details. Thank you!
itemCheckBox refers to a NodeList returned by querySelectorAll, not an individual element, so saying itemCheckBox.checked doesn't really make sense.
You should be checking if any checkbox in the list is checked, which you can use with the .some() function, like so:
Here's a working demo
for (var i = 0; i < itemCheckBox.length; i++) {
itemCheckBox[i].addEventListener("change", function(event) {
if (!event.target.checked) {
statusChangeMenu.style.height = "40px";
} else {
statusChangeMenu.style.height = "0px";
}
});
}
var itemCheckBox = document.querySelectorAll('input[type="checkbox"]');
var statusChangeMenu = document.getElementById("status-change-menu");
function changeHandler (event) {
// get the list of checkboxes in real time in case any were added to the DOM
var checkboxes = document.querySelectorAll('input[type="checkbox"]');
var anyChecked = [].some.call(checkboxes, function(checkbox) { return checkbox.checked; });
// alternatively (instead of using .some()):
// var anyChecked = false;
// checkboxes.forEach(function (checkbox) {
// if (checkbox.checked) {
// anyChecked = true;
// }
// });
if (anyChecked) {
statusChangeMenu.style.height = "40px";
} else {
statusChangeMenu.style.height = "0px";
}
}
for (var i = 0; i < itemCheckBox.length; i++) {
itemCheckBox[i].addEventListener("change", changeHandler);
}
for (var i = itemCheckBox.length; i < itemCheckBox.length + 2; i++) {
// add some checkboxes dynamically
var newCheckbox = document.createElement("input");
var newLabel = document.createElement("label");
newLabel.innerText = "Checkbox " + (i + 1);
newCheckbox.type = "checkbox";
// -- IMPORTANT-- bind event listener on dynamically added checkbox
newCheckbox.addEventListener("change", changeHandler);
newLabel.appendChild(newCheckbox);
newLabel.appendChild(document.createElement("br"));
document.body.appendChild(newLabel);
}
#status-change-menu {
height: 0;
background-color: red;
overflow: hidden;
color: white;
font-weight: bold;
}
<div id="status-change-menu">I should be visible if any checkboxes are checked</div>
<label>Checkbox 1<input type="checkbox"/></label><br/>
<label>Checkbox 2<input type="checkbox"/></label><br/>
<label>Checkbox 3<input type="checkbox"/></label><br/>
mhodges is correct in that itemCheckBox is a NodeList, not an individual element. Another issue is that you are trying to test if the box that changed is checked, and if it isn't, you are closing the menu. As you described, that is not what you want.
You need another way to check to see if all check boxes are unchecked before you close the menu. A simple way to do that is just another inner loop in the onChange function:
for(var i = 0 ; i < itemCheckBox.length; i++){
itemCheckBox[i].addEventListener("change", function(){
showMenu = false
for(var j = 0; j < itemCheckBox.length; j++)
{
if(itemCheckBox[j].checked)
showMenu = true
}
if (showMenu)
{statusChangeMenu.style.height = "40px";}
else
{statusChangeMenu.style.height = "0px";}
})}
Heres a modified Snippet
I don't know how to describe this without making it more complicated.
So look at the result of the code and click on the first link with "Show", then the second one and third one.
When the second link is clicked, first one closes but text remains "Hide" and i want it to change to "Show".
So, when clicking a link, detect if any other link has text "Hide" and change it to "Show".
And please no jQuery...
document.getElementsByClassName("show")[0].onclick = function() {
var x = document.getElementsByClassName("hide")[0];
var y = document.getElementsByClassName("show")[0];
if (x.classList.contains("visible")) {
x.classList.remove("visible");
y.textContent = "Show";
} else {
closeOther();
x.classList.add("visible");
y.textContent = "Hide";
}
};
document.getElementsByClassName("show")[1].onclick = function() {
var x = document.getElementsByClassName("hide")[1];
var y = document.getElementsByClassName("show")[1];
if (x.classList.contains("visible")) {
x.classList.remove("visible");
y.textContent = "Show";
} else {
closeOther();
x.classList.add("visible");
y.textContent = "Hide";
}
};
document.getElementsByClassName("show")[2].onclick = function() {
var x = document.getElementsByClassName("hide")[2];
var y = document.getElementsByClassName("show")[2];
if (x.classList.contains("visible")) {
x.classList.remove("visible");
y.textContent = "Show";
} else {
closeOther();
x.classList.add("visible");
y.textContent = "Hide";
}
};
function closeOther() {
var visible = document.querySelectorAll(".visible"),
i, l = visible.length;
for (i = 0; i < l; ++i) {
visible[i].classList.remove("visible");
}
}
.style {
background-color: yellow;
width: 200px;
height: 200px;
display: inline-block;
}
.hide {
background-color: red;
width: 50px;
height: 50px;
display: none;
position: relative;
top: 50px;
left: 50px;
}
.hide.visible {
display: block;
}
<div class="style">
Show
<div class="hide">
</div>
</div>
<div class="style">
Show
<div class="hide">
</div>
</div>
<div class="style">
Show
<div class="hide">
</div>
</div>
I tried to write a solution which didn't use any javascript at all and worked using CSS alone. I couldn't get it to work though - CSS can identify focus but it can't identify blur (ie. when focus has just been removed).
So here is a solution which uses javascript and the classList API, instead:
var divs = document.getElementsByTagName('div');
function toggleFocus() {
for (var i = 0; i < divs.length; i++) {
if (divs[i] === this) continue;
divs[i].classList.add('show');
divs[i].classList.remove('hide');
}
this.classList.toggle('show');
this.classList.toggle('hide');
}
for (let i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', toggleFocus, false);
}
div {
display: inline-block;
position: relative;
width: 140px;
height: 140px;
background-color: rgb(255,255,0);
}
.show::before {
content: 'show';
}
.hide::before {
content: 'hide';
}
div::before {
color: rgb(0,0,255);
text-decoration: underline;
cursor: pointer;
}
.hide::after {
content: '';
position: absolute;
top: 40px;
left: 40px;
width: 50px;
height: 50px;
background-color: rgb(255,0,0);
}
<div class="show"></div>
<div class="show"></div>
<div class="show"></div>
Like this?
Just added following to closeOther():
visible = document.querySelectorAll(".show"),
i, l = visible.length;
for (i = 0; i < l; ++i) {
visible[i].textContent="Show";
}