I'm trying to select the last child element of the class .nav-item and I've tried it several ways. It doesn't grab the item and just returns 'null' and applying styles will either grab the first item or return the following error:
main.js:25 Uncaught TypeError: Cannot read property 'style' of null
at main.js:25
My code :
The top part works fine and does apply the border, but I want to apply a border on the right of the last item and a border on the left of the first item. The last item is not working.
Can anyone point out my mistake?
The HTML if that helps:
let items = document.querySelectorAll('.nav-item');
for (let i = 0; i < items.length; i++) {
items[i].style.borderTop = '0.2em solid white';
items[i].style.borderBottom = '0.2em solid white';
}
// First and Last Item
// var secondItem = document.querySelector('.list-group-item:nth-child(2)');
// secondItem.style.color = 'coral';
let lastItem = document.querySelector('.nav-item:nth-child(4)');
console.log(lastItem);
lastItem.style.borderRight = '0.2em solid white';
<nav>
<h1>Item Lister</h1>
<ul class="nav" id="list">
<li class="nav-item">One</li>
<li class="nav-item">One</li>
<li class="nav-item">One</li>
<li class="nav-item">One</li>
</ul>
</nav>
So your <nav> element has one child: the <ul>. The <a> elements are children of the <ul>.
To fix this, you would need to querySelect .nav-item ul:nth-child(4).
Here's something to visualize it a little better. The <nav> is the parent to the <ul>, who is the parent to the <a>s. That makes <nav> the grandparent to the <a>s.
nav
ul
a
li
a
li
a
li
a
li
Also important like Karl-André Gagnon said to make sure the <li> is a child of the <ul>:
nav
ul
li
a
li
a
li
a
li
a
To achieve expected result, use below option of using document.querySelectorAll
Get all elements of class - nav-item irrespective parent and child relation
Convert HTML collection into Array using Array.from
Using pop() method get the last element to style
let lastItem = Array.from(document.querySelectorAll('.nav-item')).pop();
Sample working code for reference
let items = document.querySelectorAll('.nav-item');
for (let i = 0; i < items.length; i++) {
items[i].style.borderTop = '0.2em solid white';
items[i].style.borderBottom = '0.2em solid white';
}
// First and Last Item
// var secondItem = document.querySelector('.list-group-item:nth-child(2)');
// secondItem.style.color = 'coral';
let lastItem = Array.from(document.querySelectorAll('.nav-item')).pop();
console.log(lastItem);
lastItem.style.borderRight = '0.2em solid black';
<nav>
<h1>Item Lister</h1>
<ul class="nav" id="list">
<li class="nav-item">One</li>
<li class="nav-item">One</li>
<li class="nav-item">One</li>
<li class="nav-item">One</li>
</ul>
</nav>
Codepen - https://codepen.io/nagasai/pen/jOOvVjq
Issue::
:nth-child works with sibling elements, so :nth-child(4) is not available
As per MDN
The :nth-child() CSS pseudo-class matches elements based on their position in a group of siblings.
I used the options lastElementChild and firstElementChild. These worked.
Please change .nav-item to a. It serves your purpose as a is the child element of parent ul not the .nav-item.
let lastItem = document.querySelector('a:nth-child(4)');
It worked for me. Let me know if it is working for you.
Related
I have multiple li tag, if the user clicks one of those tags it will have a class of active and then the li tag will go to the end position of the list. but if the user decided to click the li tag it will remove the class and the li tag will go back to the previous position.
$(document).ready(function(){
$("button.move").on('click', function(){
$(".active").insertAfter("li:last-child()");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="move">move</button>
<ul>
<li class="active">1st list item</li>
<li>2nd list item</li>
<li>3rd list item</li>
</ul>
You could make your list flex (or grid as david mentions in the comments) and use the order property on the li when you want to re-order them.
So no real change is happening in the DOM, but the .active element will be shown in the end.
document.querySelector('ul').addEventListener('click', (e) => {
const element = e.target;
const isLI = element.nodeName === 'LI';
if (isLI) {
element.classList.toggle('active');
removeClassFromSiblings(element, 'active');
}
})
function removeClassFromSiblings(element, classname) {
let prev = element;
let next = element;
// remove active from preceeding elements
while (prev = prev.previousElementSibling) {
prev.classList.remove('active');
}
// remove active from following elements
while (next = next.nextElementSibling) {
next.classList.remove('active');
}
}
ul {
display: flex; /* or grid */
flex-direction: column; /* if using flex */
}
.active {
order: 1;
color: red;
}
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
<li>fourth</li>
<li>fifth</li>
<ul>
I have a line of Javascript that is meant find all the HTML elements with the class "OldClassName" and replace their class with "NewClassName" however this line of code only replaces the first instance in the resulting array because of index [0]. How would I replace all the instances of "OldClassName" at once?
document.getElementsByClassName("OldClassName")[0].className = "NewClassName";
You can use querySelectorAll and loop over each selected element and change the class like below.
If you want to add use ele.classList.add(<classname>) and similarly ele.classList.remove(<classname>) to remove one.
(This one replaces all the class names with the new)
const elements = document.querySelectorAll(".old")
elements.forEach(ele => {
ele.classList = "new"
})
.old {
background: red;
}
.new {
background: blue;
}
<div class="old">1</div>
<div class="old">2</div>
<div class="old">3</div>
<div class="old">4</div>
<div class="old">5</div>
Hope this helps !
One way could be with a simple loop:
let elements = document.getElementsByClassName("OldClassName");
for (let element of elements) {
element.className = "NewClassName";
}
If you want to avoid removing any other classes on the element:
let elements = document.getElementsByClassName("OldClassName");
for (let element of elements) {
element.classList.remove("OldClassName");
element.classList.add("NewClassName");
}
You can use a for loop to achieve this.
let classToChange = document.querySelectorAll(".oldClass");
for(let i = 0; i < classToChange.length; i++)
{
classToChange[i].className = "newClass";
}
Note this will delete all other classes in the element. Several other answers address that as I type this edit out. :)
You can run for loop on the elements list length and change the class using classList and add, remove to change the class.
With the for loop you define an iterator i and then use that as your key to target each element in the list.
let className = document.querySelectorAll(".OldClassName");
for(let i = 0; i < className.length; i++){
className[i].classList.add('NewClassName');
className[i].classList.remove('OldClassName');
}
console.log(className);
<div class="OldClassName"></div>
<div class="OldClassName"></div>
<div class="OldClassName"></div>
<div class="OldClassName"></div>
<div class="OldClassName"></div>
<div class="OldClassName"></div>
Another way is to define your class in CSS and use toggle with a function. This would keep the old class and only add it on event handler and remove it on event handler.
function changeColor() {
var element = document.querySelectorAll(".OldClassElement");
for (let i = 0; i < element.length; i++) {
element[i].classList.toggle("NewClassElement");
}
}
.OldClassElement {
color: red;
}
.NewClassElement {
background-color: black;
color: white;
}
<div class="OldClassElement">
This is a DIV element.
</div>
<div class="OldClassElement">
This is a DIV element.
</div>
<div class="OldClassElement">
This is a DIV element.
</div>
<div class="OldClassElement">
This is a DIV element.
</div>
<button onclick='changeColor()'> click me</button>
From MDN
The getElementsByClassName method of Document interface returns an
array-like object of all child elements which have all of the given
class name (s)
In your case the relevant part is returns an array-like object of all child elements this means that in order to manipulate all these elements you must iterate on each one of them, as you have used the getElementsByClassName method you must iterate using for, but you can get the same result of elements using the document.querySelectorAll method but you have the advantage that you can use some methods of array like forEach in the example that makes your code more compact.
Please take a look at the following example
document.querySelector("button").addEventListener("click", toggle);
function toggle() {
document
.querySelectorAll(".old-class-name")
.forEach((element) => element.classList.toggle("new-class-name"));
}
.old-class-name {
font-weight: bold;
}
.new-class-name {
font-weight: normal;
}
<ul>
<li class="old-class-name">Item 1</li>
<li class="old-class-name">Item 2</li>
<li class="old-class-name">Item 3</li>
<li class="old-class-name">Item 4</li>
<li class="old-class-name">Item 5</li>
</ul>
<button>Toggle</button>
See
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
I am trying to learn web designing and when trying to add class into it through java script ran into trouble.
html code:
<ul>
<li onclick="switchChannel(channel1)>
#Channel1
</li>
<li onclick="switchChannel(channel2) class="selected">
#Channel2
</li>
<li onclick="switchChannel(channel3)>
#Channel3
</li>
css code:
.selected{
color:blue;
border-left:4px solid blue;
}
javascript:script.js
function switchChannel(channelName) {
}
javascript:channel.js
var channel1={
name:"Channel1",
createdOn:new Date("April 1, 2016"),
starred:false
};
var channel2={
name:"Channel1",
createdOn:new Date("April 1, 2016"),
starred:false
};
I want to be able to click a channel1 from the list and apply .selected class to it but when channel2 is clicked remove .selected from channel1 and apply it to channel2 and so on...
If I have messed up anything else in the code please feel free to comment on it.
There are a lot of answers here but they don't seem to be addressing the actual issue. Here is a quick example using vanilla JavaScript to accomplish what you are asking for.
function switchChannel(el){
// find all the elements in your channel list and loop over them
Array.prototype.slice.call(document.querySelectorAll('ul[data-tag="channelList"] li')).forEach(function(element){
// remove the selected class
element.classList.remove('selected');
});
// add the selected class to the element that was clicked
el.classList.add('selected');
}
.selected{
color:blue;
border-left:4px solid blue;
}
<!-- Add the data-tag attribute to this list so you can find it easily -->
<ul data-tag="channelList">
<li onclick="switchChannel(this)">Channel 1</li>
<li onclick="switchChannel(this)" class="selected">Channel 2</li>
<li onclick="switchChannel(this)">Channel 3</li>
</ul>
You should use getElementsByIdand getElementsbyTagNameto manipulate the DOM:
function selectChannel(channelNumber) {
let listItems = document.getElementById("items").getElementsByTagName("li");
var length = listItems.length;
for (var i = 0; i < length; i++) {
listItems[i].className = i+1 == channelNumber ? "selected" : "";
}
}
.selected {
color: blue;
border-left: 4px solid blue;
}
<ul id="items">
<li onclick="selectChannel(1)">#channel1</li>
<li onclick="selectChannel(2)" class="selected">#channel2</li>
<li onclick="selectChannel(3)">#channel3</li>
</ul>
This solution uses jQuery but I thought this might help you out. A CodePen showing a version of this in action can be seen here.
jQuery(document).ready(function(){
jQuery('li').click(function(event){
//remove all pre-existing active classes
jQuery('.selected').removeClass('selected');
//add the active class to the link we clicked
jQuery(this).addClass('selected');
event.preventDefault();
});
});
I solved it. And I thank you every for your help.
$('li').removeClass('selected');
$('li:contains(' + channelName.name + ')').addClass('selected');
As the title says, I'm trying to give a parent nav item an 'is-active' class if its child has an 'active' class in vanillaJS. I know how to do it in jQuery but I'm not wanting to add them in my project going forward.
I've managed to create a basic jsfiddle - http://jsfiddle.net/1k9su6vo/ - which is working how I intend it but I think it's a little too basic for when I start adding more complexity to the nav.
Is there a simple way to traverse the nav like jQuery does with .find(), .closest() etc but with js?
if (document.querySelector('.active')) {
document.querySelector('.active').parentNode.parentNode.parentNode.classList.add('is-active');
}
Modern browsers actually support .closest() method
see Mozilla developer site
Is there a simple way to traverse the nav like jQuery does with
.find(), .closest() etc but with js?
Yes. The Element.closest() method.
In your case, you want to add the is-active class on an immediate list item of the root list.
To make things easier, you can add a class to the root , eg: .wpr, to make immediate list items from the root easier to target:
document.querySelector('.active').closest('.wpr > li').classList.add('is-active');
if (document.querySelector('.active')) {
document.querySelector('.active').closest('.wpr > li').classList.add('is-active');
}
.is-active > a {
color: green;
}
li {
color: black;
}
.active {
color: green;
}
<ul class="wpr">
<li>test</li>
<li>test2
<ul>
<li>sub1</li>
<li><a class="active" href="#">sub2</a></li>
<li>sub3</li>
</ul>
</li>
<li>test3</li>
</ul>
To check if child node contains a specific className we can use
.classList.contains('active'); which returns boolean.
Working example:
if (document.querySelector('.active')) {
document.querySelector('.active').parentNode.parentNode.parentNode.classList.add('is-active');
}
const node = document.querySelector('.child');
const parentNode = node.parentNode;
const trigger = document.getElementById('trigger');
trigger.addEventListener('click', function() {
const hasActiveClass = node.classList.contains('active');
if (hasActiveClass) {
parentNode.classList.add('active');
}
})
.active {
color: red;
}
<div class="parent">
I am parent
<div class="child active"> I am child</div>
</div>
<button id="trigger" type="button">trigger active</button>
So I have to implement a Jquery function in which every li Element is executing the :hover pseudo class (which only changes the background color). So if I hover down to the 4th Element ALL previous li Elements (1st, 2nd, 3th) should have the changed background color from the :hover pseudo class!! But if I move up with the mouse again the :hover effect should disappear (normal background color again) up to the point where my mouse is (if it is on 2nd element only 1st and 2nd have the hover effect now) ... I have absolutely no idea how I can create such a method... I did something like
$('ul li').on('mouseenter') {
$(this).addClass('hover'); //alternatively $(this).css('background-color', 'grey');
}
but it does not remove any :hover effect and it makes failures possible like that only the first and the 5th li Element have the :hover effect but all in between remain normal which I don't want... Could you please help me?
Link to working example on jsfiddle.net
So lets start with some sample markup for a list:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
Then some css for your 'hover':
.hover {
background-color: red;
}
And some javascript to give the functionality:
$(function(){
// Our list items.
var listItems = $('ul').children();
// An event listener over all list items.
$('li').hover(hoverIn, hoverOut);
// Find the index of the current element
// and set all elements up to this element as hover.
function hoverIn() {
var index = listItems.index(this);
$.each(listItems, function(idx, ele) {
if (idx <= index) {
$(this).addClass('hover');
} else {
$(this).removeClass('hover');
}
});
}
// Remove all hover.
function hoverOut() {
$.each(listItems, function(idx, ele) {
$(this).removeClass('hover');
});
}
});
In fact this could be done entirely with css. No jQuery or JavaScript is required for this. You should consider using some html structure like this:
<ul>
<li><span>Menu item 1</span></li>
<li>
<span>Menu item 2</span>
<ul>
<li><span>Submenu item 1</span></li>
<li>
<span>Submenu item 2</span>
<ul>
<li><span>Subsubmenu item 1</span></li>
<li><span>Subsubmenu item 2</span></li>
<li><span>Subsubmenu item 3</span></li>
</ul>
</li>
<li><span>Submenu item 3</span></li>
</ul>
</li>
</ul>
Then you can use css like this
li:hover > span {
background: #9a0000;
}
See this jsfiddle: https://jsfiddle.net/aey5cusm/
Or if you ment by first, second, thirth of a single list, it can be done with css too.
ul {
list-style: none;
margin: 0;
padding: 0;
}
ul:hover li {
background: #9a0000;
}
li:hover ~ li{
background: none;
}
Just look at this jsfiddle: https://jsfiddle.net/aey5cusm/1/