Reputation: 47
I have a div with 5 elements, and I only want to show 1 at a time.
Is there a way to change .classname:nth-child(1)
to .classname:nth-child(3)
in javascript?
Or do I need a different approach?
I have a display none on the parent and i want to display flex the nth child
Upvotes: 1
Views: 573
Reputation: 122906
I suppose this different approach may be viable. The snippet uses event delegation:
document.addEventListener(`click`, handle);
function handle(evt) {
if (evt.target.id === `next`) {
let next;
// remove .active from all elements
document.querySelectorAll(`.className`).forEach(
(elem, i) => {
if (elem.classList.contains(`active`)) {
// Note: nth-child is not zero based
next = i + 2 > 5 ? 1 : i + 2;
}
elem.classList.remove(`active`);
});
// add .active to the next element
document.querySelector(`.className:nth-child(${next})`)
.classList.add(`active`);
}
}
.className {
display: none;
}
.active {
display: block;
}
<div class="className active">div 1</div>
<div class="className">div 2</div>
<div class="className">div 3</div>
<div class="className">div 4</div>
<div class="className">div 5</div>
<button id="next">next</button>
In reply to @tacoshys
It is inefficient and consumes way more resources and time than necessary
I beg to differ. Let's cite Donald Knuth here
Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%."
About inefficiency of event delegation I cite the linked page @javascript.info:
... the delegation may add CPU load, because the container-level handler reacts on events in any place of the container, no matter whether they interest us or not. But usually the load is negligible, so we don’t take it into account.
It does not scale
Sure, it's up to anyone to make it more scalable. The snippet was just an rough indication for how to solve OPs problem.
(it is hard to read)
Readability, like beauty, is in the eye of the beholder.
Now about your code: the const
you define for the div.className
nodeList may give you trouble later on, e.g. when elements are added dynamically. So, about scalability ...
The latter is demonstrated in the next snippet (which uses your idea - which, by the way, is not a bad idea ;).
document.addEventListener(`click`, handle);
const logLine = txt => (console.clear(), txt && console.log(txt));
const ALL_ELEMS = document.querySelectorAll(`.className`);
for (let i = 0; i < 5; i += 1) {
addElemDemo(i);
}
logLine(`There are now ${document.querySelectorAll(`.className`)
.length} div.className in the document. `);
function activate(activeClass) {
logLine();
let activeIndex = [...ALL_ELEMS]
.findIndex(el => el.classList.contains(activeClass));
if (activeIndex >= 0) {
ALL_ELEMS[activeIndex].classList.remove(activeClass);
return ALL_ELEMS[activeIndex + 1 >= ALL_ELEMS.length ?
0 : activeIndex + 1].classList.add(activeClass);
}
return logLine(`Well, there you have it ... ` +
`you have to make a round trip using 'no really, next'`);
}
function activate4Real(selector, activeClass) {
const all_elems = document.querySelectorAll(selector);
let activeIndex = [...all_elems]
.findIndex(el => el.classList.contains(activeClass));
if (activeIndex + 1 > ALL_ELEMS.length - 1) {
logLine(`Try clicking the 'next' button now`);
}
all_elems[activeIndex].classList.remove(activeClass);
all_elems[activeIndex === all_elems.length-1 ?
0 : activeIndex + 1].classList.add(activeClass);
}
function addElemDemo(index) {
const lastIndex = ALL_ELEMS.length + index;
document.querySelector(`#next`).insertAdjacentElement(
`beforebegin`,
Object.assign(document.createElement(`div`),
{className: `className`, textContent: `div ${lastIndex + 1}`} ) );
};
function handle(evt) {
if (evt.target.id === `next`) {
return activate( evt.target.dataset.activeclass );
}
if (evt.target.id === `reallyNext`) {
return activate4Real(
evt.target.dataset.forselector,
evt.target.dataset.activeclass );
}
}
.className {
display: none;
}
.active {
display: block;
}
<div class="className active">div 1</div>
<div class="className">div 2</div>
<div class="className">div 3</div>
<div class="className">div 4</div>
<div class="className">div 5</div>
<button
id="next"
data-activeclass="active";
>next</button>
<button
id="reallyNext"
data-forselector=".className"
data-activeclass="active";
>no really, next</button>
Upvotes: 1
Reputation: 13002
@Kooilnc Answer works but has 2 major issues:
Inefficient
For once the code appends the EventListener
to the whole document and then checks on every click if the button was clicked. The `EventListener should be added to the button itself:
const BUTTON = documents.querySelector('#next');
BUTTON.addEventListener('click', function() {
...
})
Then he uses querySelectorAll
which returns a NodeList. Then he iterates through the NodeList and checks every Element if it has the "active" class. If he has found that element in his iteration he removes that class. The efficient (fast and resource-saving) solution is to select the element that has the class directly and remove the class from that element:
document.querySelector('.className.active').classList.remove('active);
No need for an iteration that takes more time and resources.
Scalability
Scalability should always be a concern. The code from the mentioned answer has the issue that it requires to be exact 5 elements. It will no longer work correctly if you have more or fewer elements. As such it requires manual maintenance and fixing as soon as you add or removes content.
A good and clean code should not care about how many elements you have to work correctly. The trick here is to check in the script how many elements you have by using NodeList.length
.
Improved Solution
const BUTTON = document.querySelector('#next');
const ELEMENTS = document.querySelectorAll('.className')
BUTTON.addEventListener('click', function() {
let active = document.querySelector('.className.active');
let indexOfActive = [].indexOf.call(ELEMENTS, active) + 1;
active.classList.remove('active');
if (indexOfActive === ELEMENTS.length) {
indexOfActive = 0;
}
ELEMENTS[indexOfActive].classList.add('active');
})
.className {
display: none;
}
.active {
display: block;
}
<div class="className active">div 1</div>
<div class="className">div 2</div>
<div class="className">div 3</div>
<div class="className">div 4</div>
<div class="className">div 5</div>
<button id="next">next</button>
How does the code Work?
NodeList
+ 1
to the index to select the next element in the NodeList unless the index is equal to the length of the NodeList in which case it resets the index to 0
Upvotes: 0