Re-increment class on children divs when parent div class changes - javascript

I need to re-increment the class ("summary-item-${_}") on every 4, 3, or 2 children ("forEach" and "parseInt") depending on the class change of the parent div ".sqs-gallery-design-carousel-slides-in-view-4", ".sqs-gallery-design-carousel-slides-in-view-3", ".sqs-gallery-design-carousel-slides-in-view-2".
The initial page load adds the class to each child depending on the parent div class. But when adjusting the browser screen size the child classes do not toggle. As an example:
• .summary-item-list.sqs-gallery-design-carousel-slides-in-view-4 > .summary-item-1 on the first 4 children, .summary-item-2 on the second 4 children and so on.
• .summary-item-list.sqs-gallery-design-carousel-slides-in-view-3 > .summary-item-1 on the first 3 children, .summary-item-2 on the second 3 children and so on.
• .summary-item-list.sqs-gallery-design-carousel-slides-in-view-2 > .summary-item-1 on the first 2 children, .summary-item-2 on the second 2 children and so on.
/*-- Not for editing ---*/
window.addEventListener("resize", function() {
var w = window.innerWidth;
var responsiveDiv = document.querySelectorAll('.summary-item-list');
responsiveDiv.forEach((element) => {
if(w < 479) {
element.classList.remove("sqs-gallery-design-carousel-slides-in-view-4");
element.classList.remove("sqs-gallery-design-carousel-slides-in-view-3");
element.classList.add("sqs-gallery-design-carousel-slides-in-view-2");
} else if(w > 479 && w < 767) {
element.classList.remove("sqs-gallery-design-carousel-slides-in-view-4");
element.classList.add("sqs-gallery-design-carousel-slides-in-view-3");
element.classList.remove("sqs-gallery-design-carousel-slides-in-view-2");
} else if(w > 767) {
element.classList.add("sqs-gallery-design-carousel-slides-in-view-4");
element.classList.remove("sqs-gallery-design-carousel-slides-in-view-3");
element.classList.remove("sqs-gallery-design-carousel-slides-in-view-2");
}
})
});
/*-- Not for editing ---*/
/*-- For editing ---*/
document.addEventListener("load", function() {
const carouselSlidesFour = document.querySelectorAll(".sqs-gallery-design-carousel-slides-in-view-4 .summary-item");
const carouselSlidesThree = document.querySelectorAll(".sqs-gallery-design-carousel-slides-in-view-3 .summary-item");
const carouselSlidesTwo = document.querySelectorAll(".sqs-gallery-design-carousel-slides-in-view-2 .summary-item");
carouselSlidesFour.forEach((element, x) => {
const a = parseInt(x / 4) + 1;
if ($(".four-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-4" )){
element.classList.add(`summary-item-${a}`);
} else if ($(".four-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-3" )){
element.classList.remove(`summary-item-${a}`);
} else if ($(".four-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-2" )){
element.classList.remove(`summary-item-${a}`);
}
});
carouselSlidesThree.forEach((element, y) => {
const b = parseInt(y / 3) + 1;
if ($(".three-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-3" )){
element.classList.add(`summary-item-${b}`);
} else if ($(".three-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-4" )){
element.classList.remove(`summary-item-${b}`);
} else if ($(".three-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-2" )){
element.classList.remove(`summary-item-${b}`);
}
});
carouselSlidesTwo.forEach((element, z) => {
const c = parseInt(z / 2) + 1;
if ($(".two-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-2" )){
element.classList.add(`summary-item-${c}`);
} else if ($(".two-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-4" )){
element.classList.remove(`summary-item-${c}`);
} else if ($(".two-row .summary-item-list").hasClass( "sqs-gallery-design-carousel-slides-in-view-3" )){
element.classList.remove(`summary-item-${c}`);
}
});
}(document));
/*-- For editing ---*/
.sqs-block-summary {
border: solid 2px #000000;
margin: 10px;
}
.sqs-block-content {
margin: 10px;
}
.summary-item {
display: inline-block;
white-space: nowrap;
vertical-align: top;
padding-left: 1%;
padding-right: 1%;
}
.sqs-gallery-design-carousel-slides-in-view-4 .summary-item {
width: 24%;
}
.sqs-gallery-design-carousel-slides-in-view-3 .summary-item {
width: 32%;
}
.sqs-gallery-design-carousel-slides-in-view-2 .summary-item {
width: 49%;
}
.sqs-block-summary * {
box-sizing: border-box;
}
.summary-item-list-container {
width: 100%;
overflow: hidden;
}
.summary-item-list {
list-style-type: none;
margin: 0;
padding: 0;
vertical-align: top;
}
.summary-item-1 {
color: red !important;
}
.summary-item-2 {
color: green !important;
}
.summary-item-3 {
color: blue !important;
}
.summary-item-4 {
color: orange !important;
}
.summary-item-5 {
color: grey !important;
}
.summary-item-6 {
color: purple !important;
}
.identity.sqs-gallery-design-carousel-slides-in-view-4 .view-4, .identity.sqs-gallery-design-carousel-slides-in-view-3 .view-3, .identity.sqs-gallery-design-carousel-slides-in-view-2 .view-2 {
display: block;
}
.identity.sqs-gallery-design-carousel-slides-in-view-4 :not(.view-4), .identity.sqs-gallery-design-carousel-slides-in-view-3 :not(.view-3), .identity.sqs-gallery-design-carousel-slides-in-view-2 :not(.view-2) {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="identity summary-item-list sqs-gallery-design-carousel-slides-in-view-4">
<div class="view-4">Current gallery slide count on screen size: 4</div>
<div class="view-3">Current gallery slide count on screen size: 3</div>
<div class="view-2">Current gallery slide count on screen size: 2</div>
</div>
<div class="sqs-block-summary four-row">Gallery 1
<div class="sqs-block-content">
<div class="summary-block-wrapper">
<div class="summary-item-list-container">
<div class="summary-item-list sqs-gallery-design-carousel-slides-in-view-4">
<div class="summary-item"> Item 1 </div>
<div class="summary-item"> Item 2 </div>
<div class="summary-item"> Item 3 </div>
<div class="summary-item"> Item 4 </div>
<div class="summary-item"> Item 5 </div>
<div class="summary-item"> Item 6 </div>
<div class="summary-item"> Item 7 </div>
<div class="summary-item"> Item 8 </div>
<div class="summary-item"> Item 9 </div>
<div class="summary-item"> Item 10 </div>
<div class="summary-item"> Item 11 </div>
</div>
</div>
</div>
</div>
</div>
<div class="sqs-block-summary three-row">Gallery 2
<div class="sqs-block-content">
<div class="summary-block-wrapper">
<div class="summary-item-list-container">
<div class="summary-item-list sqs-gallery-design-carousel-slides-in-view-3">
<div class="summary-item"> Item 1 </div>
<div class="summary-item"> Item 2 </div>
<div class="summary-item"> Item 3 </div>
<div class="summary-item"> Item 4 </div>
<div class="summary-item"> Item 5 </div>
<div class="summary-item"> Item 6 </div>
<div class="summary-item"> Item 7 </div>
<div class="summary-item"> Item 8 </div>
<div class="summary-item"> Item 9 </div>
<div class="summary-item"> Item 10 </div>
<div class="summary-item"> Item 11 </div>
</div>
</div>
</div>
</div>
</div>
<div class="sqs-block-summary two-row">Gallery 3
<div class="sqs-block-content">
<div class="summary-block-wrapper">
<div class="summary-item-list-container">
<div class="summary-item-list sqs-gallery-design-carousel-slides-in-view-2">
<div class="summary-item"> Item 1 </div>
<div class="summary-item"> Item 2 </div>
<div class="summary-item"> Item 3 </div>
<div class="summary-item"> Item 4 </div>
<div class="summary-item"> Item 5 </div>
<div class="summary-item"> Item 6 </div>
<div class="summary-item"> Item 7 </div>
<div class="summary-item"> Item 8 </div>
<div class="summary-item"> Item 9 </div>
<div class="summary-item"> Item 10 </div>
<div class="summary-item"> Item 11 </div>
</div>
</div>
</div>
</div>
</div>

Related

Event delegation does not work if the bound target is nested

For a comment list I use the event delegation pattern after a recommendation from Stackoverflow colleagues (mplungjan, Michel). It works well and I am very enthusiastic about this pattern. But as I already suspected, there will be problems if the bound element (button) contains two child elements (span, span).
Since I want to get the CommentID from the target in the parent element of the child element, it only works in the cases when you click exactly between the spans inside the button. Actually a case for currentTarget but that doesn't work in this case because the tapped element is the whole comment list.
Question: What do I have to do to fix it?
const commentList = document.querySelector('.comment-list');
commentList.addEventListener('click', (ev) => {
console.log('1. clicked');
const getObjectId = () => {
return ev.target.parentNode.parentNode.getAttribute('data-comment-id');
}
if (! getObjectId()) return false;
if (ev.target.classList.contains('delete')) {
console.log('2. Delete action');
console.log('3. for relatedID', getObjectId());
}
if (ev.target.classList.contains('edit')) {
console.log('2. Edit action');
console.log('3. for relatedID', getObjectId());
}
if (ev.target.classList.contains('flag')) {
console.log('2. Flag action');
console.log('3. for relatedID', getObjectId());
}
});
.controller {
display: flex;
gap:20px;
}
.comment {
margin-bottom: 20px;
background: gray;
}
.controller button > span {
background: orange;
}
.controller button span:first-child {
margin-right: 10px;
}
<div class="comment-list">
<div class="comment">
<div class="content">lorem 1. Dont work! Nested button.</div>
<div class="controller" data-comment-id="1">
<div class="delete">
<button class="delete"><span>delete</span><span>ICON</span></button>
</div>
<div class="edit">
<button class="edit"><span>edit</span><span>ICON</span></button>
</div>
<div class="flag">
<button class="flag"><span>flag</span><span>ICON</span></button>
</div>
</div>
</div>
<div class="comment">
<div class="content">lorem 2. Work! </div>
<div class="controller" data-comment-id="2">
<div class="delete"><button class="delete">delete</button></div>
<div class="edit"><button class="edit">edit</button></div>
<div class="flag"><button class="flag">flag</button></div>
</div>
</div>
<div class="comment">
<div class="content">lorem 3. Work! </div>
<div class="controller" data-comment-id="3">
<div class="delete"><button class="delete">delete</button></div>
<div class="edit"><button class="edit">edit</button></div>
<div class="flag"><button class="flag">flag</button></div>
</div>
</div>
</div>
The problem is that you're using .parentNode.parentNode to get to the element with data-comment-id, but the number of parents changes when the target is nested inside additional <span> elements.
Don't hard-code the nesting levels, use .closest() to find the containing controller node.
const getObjectId = () => {
return ev.target.closest('.controller').getAttribute('data-comment-id');
}
Building on my last comment in the other question
const tgtButtonWhenSpansInsideButton = e.target.closest("button")
Cache the objects
the closest method will get the button itself even if no children
Make sure you get the class from the containing element of what you want to call a button
const commentList = document.querySelector('.comment-list');
const getObjectId = (tgt) => tgt.closest('.controller').dataset.commentId;
commentList.addEventListener('click', (ev) => {
const tgt = ev.target.closest("button")
const objectId = getObjectId(tgt);
if (!objectId) return;
console.log(objectId,"clicked")
if (tgt.classList.contains('delete')) {
console.log('2. Delete action');
console.log('3. for relatedID', objectId);
}
if (tgt.classList.contains('edit')) {
console.log('2. Edit action');
console.log('3. for relatedID', objectId);
}
if (tgt.classList.contains('flag')) {
console.log('2. Flag action');
console.log('3. for relatedID', objectId);
}
});
.controller {
display: flex;
gap: 20px;
}
.comment {
margin-bottom: 20px;
background: gray;
}
.controller button>span {
background: orange;
}
.controller button span:first-child {
margin-right: 10px;
}
<div class="comment-list">
<div class="comment">
<div class="content">lorem 1. Dont work! Nested button.</div>
<div class="controller" data-comment-id="1">
<div class="delete">
<button class="delete"><span>delete</span><span>ICON</span></button>
</div>
<div class="edit">
<button class="edit"><span>edit</span><span>ICON</span></button>
</div>
<div class="flag">
<button class="flag"><span>flag</span><span>ICON</span></button>
</div>
</div>
</div>
<div class="comment">
<div class="content">lorem 2. Work! </div>
<div class="controller" data-comment-id="2">
<div class="delete"><button class="delete">delete</button></div>
<div class="edit"><button class="edit">edit</button></div>
<div class="flag"><button class="flag">flag</button></div>
</div>
</div>
<div class="comment">
<div class="content">lorem 3. Work! </div>
<div class="controller" data-comment-id="3">
<div class="delete"><button class="delete">delete</button></div>
<div class="edit"><button class="edit">edit</button></div>
<div class="flag"><button class="flag">flag</button></div>
</div>
</div>
</div>
In this case I would "traverse" the DOM up if it wasn't a button that was clicked, something like this
const commentList = document.querySelector('.comment-list');
commentList.addEventListener('click', (ev) => {
console.log('1. clicked', ev.target.tagName);
let target = ev.target
if (target.tagName === "SPAN") {
target = target.parentElement
}
const commentId = target.parentElement.parentElement.getAttribute('data-comment-id');
if (!commentId) return false;
if (target.classList.contains('delete')) {
console.log('2. Delete action');
console.log('3. for relatedID', commentId);
}
if (target.classList.contains('edit')) {
console.log('2. Edit action');
console.log('3. for relatedID', commentId);
}
if (target.classList.contains('flag')) {
console.log('2. Flag action');
console.log('3. for relatedID', commentId);
}
});
.controller {
display: flex;
gap:20px;
}
.comment {
margin-bottom: 20px;
background: gray;
}
.controller button > span {
background: orange;
}
.controller button span:first-child {
margin-right: 10px;
}
<div class="comment-list">
<div class="comment">
<div class="content">lorem 1. Dont work! Nested button.</div>
<div class="controller" data-comment-id="1">
<div class="delete">
<button class="delete"><span>delete</span><span>ICON</span></button>
</div>
<div class="edit">
<button class="edit"><span>edit</span><span>ICON</span></button>
</div>
<div class="flag">
<button class="flag"><span>flag</span><span>ICON</span></button>
</div>
</div>
</div>
<div class="comment">
<div class="content">lorem 2. Work! </div>
<div class="controller" data-comment-id="2">
<div class="delete"><button class="delete">delete</button></div>
<div class="edit"><button class="edit">edit</button></div>
<div class="flag"><button class="flag">flag</button></div>
</div>
</div>
<div class="comment">
<div class="content">lorem 3. Work! </div>
<div class="controller" data-comment-id="3">
<div class="delete"><button class="delete">delete</button></div>
<div class="edit"><button class="edit">edit</button></div>
<div class="flag"><button class="flag">flag</button></div>
</div>
</div>
</div>

Change the Page layout when a button is clicked

I want to change the layout of a page that has 3 columns:
<div>
<div class="container">
<div class="row" >
<div class="col-md-4"></div>
<div class="col-md-4"></div>
<div class="col-md-4"></div>
</div>
</div>
</div>
... to 4 columns when a button is clicked:
<div>
<div class="container">
<div class="row" >
<div class="col-md-3"></div>
<div class="col-md-3"></div>
<div class="col-md-3"></div>
<div class="col-md-3"></div>
</div>
</div>
</div>
I have no clue on how to do this.
There are many ways you can add another div. Here is my approach :
function appendDiv(){
let row = document.getElementsByClassName('row');
// change className for all the col-md-4 div
document.querySelectorAll('.col-md-4').forEach(function(item) {
item.className = 'col-md-3';
})
//create new div;
let col = document.createElement('div');
// add classname to div
col.className = "col-md-3"
row[0].appendChild(col)
}
.col-md-4{
border : 1px solid blue;
height : 20px;
}
.col-md-3{
border : 1px solid green;
height : 20px;
}
<div>
<div class="container">
<div class="row" >
<div class="col-md-4"></div>
<div class="col-md-4"></div>
<div class="col-md-4"></div>
</div>
<button onClick='appendDiv()'>click</button>
</div>
</div>
There's a few ways this could be done depending on your data, however, here's one angle.
If you have both your 4 column & 3 column versions of the data loaded on the page (but one hidden with css). You could run something like this.
HTML
<div id="colsThree" class="displayArea show">
<div class="container">
<div class="row" >
<div class="col-md-4"></div>
<div class="col-md-4"></div>
<div class="col-md-4"></div>
</div>
</div>
</div>
<div id="colsFour" class="displayArea">
<div class="container">
<div class="row" >
<div class="col-md-4"></div>
<div class="col-md-4"></div>
<div class="col-md-4"></div>
</div>
</div>
</div>
<button id="changeColumns">Click Me To Change Columns</button>
Javascript
const buttonEl = document.querySelector("#changeColumns");
buttonEl.addEventListener('click', () => {
const outputEls = document.querySelectorAll('.displayArea')
outputEls.forEach((outputEl) => {
outputEl.toggle("show")
})
});
CSS
.displayArea {
display: none;
}
.displayArea.show {
display: block;
}
Use forEach and appendChild method.
const btn = document.querySelector('#btn')
btn.onclick = function() {
const targetClasses = document.querySelectorAll('.col-md-4')
targetClasses.forEach((tag, idx) => {
tag.className = 'col-md-3'
const lastIdx = targetClasses.length - 1
if (idx === lastIdx) {
const tag = document.createElement('div')
, row = document.querySelector('.row')
tag.className = 'col-md-3'
tag.innerText = '4'
row.appendChild(tag)
}
})
console.log(targetClasses)
return
}
<div>
<button id="btn">Click me</button>
<div class="container">
<div class="row" >
<div class="col-md-4">1</div>
<div class="col-md-4">2</div>
<div class="col-md-4">3</div>
</div>
</div>
</div>
If you're only using vanilla HTML, CSS, and JavaScript, then one of the ways to achieve this is by adding a click listener to the button beforehand. FYI: for brevity's sake, I'll call the div element with row class as parent. When user clicks the button, then it should
remove col-md-4 class and add col-md-3 class to all the children elements of parent.
add a new div element with col-md-3 class into parent.
Here's a link to the codepen for your reference.
const button = document.querySelector('button');
const rowDiv = document.querySelector('.row');
button.addEventListener('click', (e) => {
Array.from(rowDiv.children).forEach(childDiv => {
childDiv.classList.remove('col-md-4');
childDiv.classList.add('col-md-3');
});
const newDiv = document.createElement('div');
newDiv.classList.add('col-md-3');
rowDiv.appendChild(newDiv);
// I disabled the button to prevent the user
// from clicking it the second time.
e.target.disabled = true;
});
.button-parent {
margin: 15px 0;
}
.row {
height: 100vh;
}
.row > div:nth-child(1) {
background: red;
}
.row > div:nth-child(2) {
background: blue;
}
.row > div:nth-child(3) {
background: yellow;
}
.row > div:nth-child(4) {
background: green;
}
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<div>
<div class="container">
<div class="button-parent">
<button class="btn btn-primary">Add div</button>
</div>
<div class="row">
<div class="col-md-4"></div>
<div class="col-md-4"></div>
<div class="col-md-4"></div>
</div>
</div>
</div>
</body>

How to select the content from the element that I clicked on (using data attributes)

I am working with HTML and JS and I am trying to add a click event listener so that the index = 0 value updates automatically on click.
Depending on which .tab element I click, the content inside of the div.tab should change to display either Hello1, Hello2 or Hello3.
The first tab should display the tab content hello1 accordingly.
I have provided both with data attributes in the HTML. I want the corresponding div.tab content to be output if both have the same data attribute with vanilla JS.
JS
const tabbarContent = document.querySelectorAll(".tab-content");
const tabbarButtons = document.querySelectorAll(".tab");
const index = 0;
tabbarContent[index].classList.add('active');
tabbarButtons[index].classList.add('active');
const dataAttribute1 = tabbarButtons[index].getAttribute("data-tab");
const dataAttribute2 = tabbarContent[index].getAttribute("data-tab");
function clickTab() {
for (let index = 0; index.length; index++) {
if (dataAttribute1[index] === dataAttribute2[index]) {
dataAttribute2.classList.add("active");
}
}
}
tabbarButtons[index].addEventListener("click", clickTab);
HTML
<div class="container">
<div class="inner-container">
<div class="tab tab1" data-tab="1">
1
</div>
<div class="tab tab2" data-tab="2">
2
</div>
<div class="tab tab3" data-tab="3">
3
</div>
</div>
<div class="inner-container">
<div class="tab-content content1" data-tab="1">
<h1>Hello1</h1>
</div>
<div class="tab-content content2" data-tab="2">
<h1>Hello2</h1>
</div>
<div class="tab-content content3" data-tab="3">
<h1>Hello3</h1>
</div>
</div>
</div>
Thanks
Jessy
If I understand correctly what you want to achieve, you could do something like:
const tabbarButtons = document.querySelectorAll(".tab");
const tabbarContents = document.querySelectorAll(".tab-content");
tabbarButtons.forEach((tabBtn) => {
tabBtn.addEventListener('click', () => {
tabbarContents.forEach(tabCnt => {
if (tabCnt.dataset.tab === tabBtn.dataset.tab) {
tabCnt.classList.add('active');
} else {
tabCnt.classList.remove('active');
}
})
});
})
.tab {
display: inline-block;
padding: 5px;
border: 1px solid #ccc;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
<div class="container">
<div class="inner-container">
<div class="tab tab1" data-tab="1">
1
</div>
<div class="tab tab2" data-tab="2">
2
</div>
<div class="tab tab3" data-tab="3">
3
</div>
</div>
<div class="inner-container">
<div class="tab-content content1" data-tab="1">
<h1>Hello1</h1>
</div>
<div class="tab-content content2" data-tab="2">
<h1>Hello2</h1>
</div>
<div class="tab-content content3" data-tab="3">
<h1>Hello3</h1>
</div>
</div>
</div>

JS: Hide all elements except of one (by id) (and all of its descending child elements and its parents)

I try to select all elements on a page except of #content_container and all its descendent subelements and all of its parents. Later I want to hide all the selected elements.
However I already fail with this:
var elems = document.querySelectorAll("body *:not(#content_container *)");
Browser console (chrome) returns: Failed to execute 'querySelectorAll' on 'Document': 'body *:not(#content_container *)' is not a valid selector.
Whereas "body *:not(#content_container)" would not return an error.
How can this be accomplished?
note: cant use jQuery.
Edit:
Some of you wanted an example. The goal is to keep #content_container AND its content visible on the site, whereas all the other content will be hidden.
<body>
<div class="hide">
<div class="hide">
</div>
</div>
<div>
<div>
<div class="hide">
<div class="hide">
</div>
</div>
<div id="content_container">
<p> ------- content_container div ------- </p>
<div>
<div>
<div>
</div>
</div>
</div>
<div>
</div>
</div>
</div>
<div class="hide">
</div>
</div>
<div class="hide">
<div class="hide">
<div class="hide">
<div class="hide">
</div>
</div>
</div>
</div>
<div class="hide">
<div class="hide">
</div>
</div>
The only way I can see to do this is to get the subject node, then add all it's siblings to the "don't show" collection, then go up its parent nodes collecting and adding all siblings as you go, stopping at the body.
The following only adds siblings and parent siblings, not the node itself or its immediate ancestors, stopping once the body child nodes are processed. It also only collects element nodes and ignores all others, so empty #text node siblings will still be there, but should not affect the layout. If you want to get rid of those, just delete them.
Click the "Hide stuff" button.
function hideStuff(){
var el = document.querySelector('#content_container');
var node, nodes = [];
do {
var parent = el.parentNode;
// Collect element children
for (var i=0, iLen=parent.childNodes.length; i<iLen; i++) {
node = parent.childNodes[i];
// Collect only sibling nodes that are elements and not the current element
if (node.nodeType == 1 && node != el) {
nodes.push(node);
}
}
// Go up to parent
el = parent;
// Stop when processed the body's child nodes
} while (el.tagName.toLowerCase() != 'body');
// Hide the collected nodes
nodes.forEach(function(node){
node.style.display = 'none';
});
}
<body>
<div class="hide">hide
<div class="hide">hide
</div>
</div>
<div>
<div>
<div class="hide">hide
<div class="hide">hide
</div>
</div>
<div id="content_container">
<p> ------- content_container div ------- </p>
<button onclick="hideStuff()">Hide stuff</button>
<div>don't hide
<div>don't hide
<div>don't hide
</div>
</div>
</div>
<div>don't hide
</div>
</div>
</div>
<div class="hide">hide
</div>
</div>
<div class="hide">hide
<div class="hide">hide
<div class="hide">hide
<div class="hide">hide
</div>
</div>
</div>
</div>
<div class="hide">hide
<div class="hide">hide
</div>
</div>
</body>
This selects then hides everything inside the <body> except #content_container, its children and its parents.
class="hide" is added to the HTML purely to indicate which elements will be hidden, but isn't programmatically useful.
It works by applying the class="hidden" to every element within the <body>, unless it's #content_container or it contains #content_container.
An !important CSS rule then forces the children of #content_container to remain visible.
For demonstration, I have added a 1 second delay before the hiding occurs.
setTimeout( () => { // for demo
document.querySelectorAll( "body *:not( #content_container )" )
.forEach( ( v ) => {
if ( !v.querySelector( "#content_container" ) ) {
v.classList.add( "hidden" );
}
} );
}, 1000 );
body {
padding: .5rem;
background: lightgrey;
}
div {
padding: inherit;
border: 1px solid black;
background: lightblue;
}
#content_container {
background: lightgreen;
visibility: visible !important;
}
/* to hide the children, remove this rule */
#content_container * {
visibility: visible !important;
}
.hidden {
visibility: hidden;
}
<body>
<div>
<div class="hide">
<div class="hide">
<div class="hide">
<div class="hide"></div>
</div>
</div>
</div>
<div>
<div>
<div id="content_container">
<div>
<div>
<div></div>
</div>
</div>
</div>
<div class="hide">
<div class="hide">
<div class="hide">
<div class="hide"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
This solution doesn't maintain the visibility of #content_container's parents.
If #content_container's children should also be hidden, only a minor change to the CSS will fix it.
I have highlighted the CSS that keeps the children visible.
setTimeout( () => { // for demo
document.querySelectorAll( "body *:not( #content_container )" )
.forEach( ( v ) => { v.classList.add( "hidden" ); } );
}, 1000 );
body {
padding: .5rem;
background: lightgrey;
}
div {
padding: inherit;
border: 1px solid black;
background: lightblue;
}
#content_container {
background: lightgreen;
visibility: visible !important;
}
/* to hide the children, remove this rule */
#content_container * {
visibility: visible !important;
}
.hidden {
visibility: hidden;
}
<body>
<div>
<div class="hide">
<div class="hide">
<div class="hide">
<div class="hide"></div>
</div>
</div>
</div>
<div>
<div>
<div id="content_container">
<div>
<div>
<div></div>
</div>
</div>
</div>
<div class="hide">
<div class="hide">
<div class="hide">
<div class="hide"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
A far more efficient and simpler alternative to the previous solution is to hide the <body> (and therefore everything else on the page) whilst forcing the visibility of #content_container and its children.
This is a more direct route, faster to apply and easier to reverse if required; my first solution sets a lot of redundant classes.
setTimeout( () => { // for demo
document.querySelector( "body" ).classList.add( "hidden" );
}, 1000 );
body {
padding: .5rem;
background: lightgrey;
}
div {
padding: inherit;
border: 1px solid black;
background: lightblue;
}
#content_container {
background: lightgreen;
visibility: visible !important;
}
.hidden {
visibility: hidden;
}
<body>
<div>
<div class="hide">
<div class="hide">
<div class="hide">
<div class="hide"></div>
</div>
</div>
</div>
<div>
<div>
<div id="content_container">
<div>
<div>
<div></div>
</div>
</div>
</div>
<div class="hide">
<div class="hide">
<div class="hide">
<div class="hide"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>

Have an element react to another element change of state

I have two divs, wrapping 8 divs each:
<div class="binaries">
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
</div>
<div class="numbers">
<div class="each-number" data-value="128"> 128 </div>
<div class="each-number" data-value="64"> 64 </div>
<div class="each-number" data-value="32"> 32 </div>
<div class="each-number" data-value="16"> 16 </div>
<div class="each-number" data-value="8"> 8 </div>
<div class="each-number" data-value="4"> 4 </div>
<div class="each-number" data-value="2"> 2</div>
<div class="each-number" data-value="1"> 1 </div>
</div>
What I want to achieve, is that clicking, for example, the first div in the numbers section, changes the first div in the binaries section, but I'm not sure how to link them without adding a click handler to every single div in the numbers section.
Codepen:
http://codepen.io/Hyde87/full/zNGXXw/
JS:
"use strict";
let count = 0;
const output = document.getElementById("output");
const gameResult = document.getElementById("gameResult");
const numbers = document.querySelector(".numbers");
const binaries = document.querySelectorAll(".binary-number");
const randomizer = document.querySelector(".randomizer");
/* Get the number value of every number on click using event delegation, then call the testValue function */
numbers.addEventListener("click", getValue);
function getValue(e){
if (e.target.className == "each-number") {
e.target.classList.add("light");
let thisValue = e.target.getAttribute('data-value');
count += parseInt(thisValue);
console.log(count);
testValue()
}}
/* The values are added to the count variable, which is tested against the random number */
function testValue(){
if (count > parseInt(output.textContent)) {
gameResult.textContent = "Wrong value, you went over it."
count = 0;
output.textContent = "";
} else if (count === parseInt(output.textContent)) {
gameResult.textContent = "You got it right!";
output.textContent = "";
}
}
/* Gets a random number between 1 and 128 */
function getRandom() {
return Math.floor(Math.random() * (128 - 1 + 1)) + 1;
}
/* Displays the random number and resets other values so we always start from scratch when we get a new random number */
randomizer.addEventListener("click", function() {
gameResult.textContent = "";
count = 0;
output.textContent = getRandom();
for (let i = 0; i < binaries.length; i++) {
binaries[i].textContent = "0";
}
})
Add classes to binaries as
<div class="binaries">
<div class="binary-number num-128"> 0 </div>
<div class="binary-number num-64"> 0 </div>
<div class="binary-number num-32"> 0 </div>
<div class="binary-number num-16"> 0 </div>
<div class="binary-number num-8"> 0 </div>
<div class="binary-number num-4"> 0 </div>
<div class="binary-number num-2"> 0 </div>
<div class="binary-number num-1"> 0 </div>
</div>
And add these two line in getValue function
let binaryElem = document.querySelector(".binary-number.num-"+thisValue);
binaryElem.textContent = "1";
You don't have to add a click handler to every element inside numbers. Just add it to the parent element and get the index of the clicked event target.
Here is an example.
var bin = document.getElementsByClassName('binaries')[0];
var num = document.getElementsByClassName('numbers')[0];
function numClick(evt) {
var index = 0;
for(var i = num.children.length - 1; i >= 0; i--) {
if (evt.target == num.children[i]) {
index = i;
}
}
bin.children[index].classList.add('mark');
}
num.addEventListener('click', numClick)
.mark {
color: white;
background: red;
}
.binaries , .numbers {
display: flex;
justify-content: space-between;
}
<div class="binaries">
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
<div class="binary-number"> 0 </div>
</div>
<div class="numbers">
<div class="each-number" data-value="128"> 128 </div>
<div class="each-number" data-value="64"> 64 </div>
<div class="each-number" data-value="32"> 32 </div>
<div class="each-number" data-value="16"> 16 </div>
<div class="each-number" data-value="8"> 8 </div>
<div class="each-number" data-value="4"> 4 </div>
<div class="each-number" data-value="2"> 2</div>
<div class="each-number" data-value="1"> 1 </div>
</div>

Categories

Resources