with jQuery and having a wrapper container around the contents within each h1, I could easily hide them.
but with no wrapper container, how would one do it?
what's the best way to do something that just hides everything before the next h1?
I'm not using jQuery because this is part of a React app.
h1 {
border-bottom: solid 1px #000;
}
span {
float: right;
}
<h1>h1 <span>x</span></h1>
<p>test 1</p>
<h2>h2</h2>
<p>test 2</p>
<h1>h1-1 <span>x</span></h1>
<p>test 3</p>
<h2>h2-2</h2>
<p>test 4</p>
with jQuery and having a wrapper container around the contents within each h1, I could easily hide them.
but with no wrapper container, how would one do it?
what's the best way to do something that just hides everything before the next h1?
I'm not using jQuery because this is part of a React app.
h1 {
border-bottom: solid 1px #000;
}
span {
float: right;
}
<h1>h1 <span>x</span></h1>
<p>test 1</p>
<h2>h2</h2>
<p>test 2</p>
<h1>h1-1 <span>x</span></h1>
<p>test 3</p>
<h2>h2-2</h2>
<p>test 4</p>
or wrap everything after and before the next h1 in a div tag?
Share Improve this question edited Jun 3, 2018 at 12:25 totalnoob asked May 28, 2018 at 18:37 totalnoobtotalnoob 2,74110 gold badges39 silver badges72 bronze badges 1-
If the number of subentries is very limited you could always just use the
+
operator: add some class (e.g. expanded) when it should be expanded and then just spam enough:h1.expanded+p, h1.expanded+p+p, h1.expanded+p+p+p, ... { ... }
– Fabian N. Commented Jun 4, 2018 at 12:25
6 Answers
Reset to default 4 +25I am able to do it with this css. Just add the class "start" to the h1 from which you want to start collapsing the elements and the class "upto" to the h1 upto which you want the elements to collapse.
CSS
.start ~ *:not(h1) {
display: none;
}
.upto ~ * {
display: block !important;
}
HTML
<h1 class='start'>h1 <span>x</span></h1>
...
<h1 class ='upto'>h1-1 <span>x</span></h1>
Here all the elements between start and upto will be hidden. You can have the collapse effect by placing adding the 'start' and 'upto' classes accordingly
The addition of the class to the next h1 could be also done via Javascript. So, I can have a simple js function which sets the 'start' and the 'upto'.
function collapse(startHeaderNumber, uptoHeaderNumber) {
var allHeaders = document.getElementsByTagName("h1");
var totalNoOfHeaders = allHeaders.length;
if (startHeaderNumber > totalNoOfHeaders) {
return;
}
var startHeader = allHeaders[startHeaderNumber - 1];
startHeader.classList.add('start');
if (uptoHeaderNumber <= totalNoOfHeaders) {
var uptoHeader = allHeaders[uptoHeaderNumber - 1];
uptoHeader.classList.add('upto');
}
}
And you can simply call it like
collapse(1 ,2)
And it would collapse all the items between headers 1 and 2. Or, you can call it like,
collapse(1)
which will collapse all elements from the first header till the last.
For a full demo, please see this fiddle
sounds like you want something similar to jQuery's nextUntil()
function. Here is a good guide to doing that in vanilla js. The code ends up looking like this:
var nextUntil = function (elem, selector, filter) {
// Setup siblings array
var siblings = [];
// Get the next sibling element
elem = elem.nextElementSibling;
// As long as a sibling exists
while (elem) {
// If we've reached our match, bail
if (elem.matches(selector)) break;
// If filtering by a selector, check if the sibling matches
if (filter && !elem.matches(filter)) {
elem = elem.nextElementSibling;
continue;
}
// Otherwise, push it to the siblings array
siblings.push(elem);
// Get the next sibling element
elem = elem.nextElementSibling;
}
return siblings;
};
I obviously don't know the context behind what you are doing, but I reckon there's a better way around it by altering the HTML. This could potentially even let you do some of this with just css nth child selectors
You can use previousElementSibling recursively to get all of the siblings of your h1
elements. Here is working example using previousElementSibling
:
const elems = document.getElementsByTagName('h1');
for (let i = 0; i < elems.length; i++) hidePrev(elems[i]);
function hidePrev(elem)
{
var pre = elem.previousElementSibling;
if (!pre) return;
pre.style.display = 'none';
hidePrev(pre);
}
h1 {
border-bottom: solid 1px #000;
}
span {
float: right;
}
<h1>h1 <span>x</span></h1>
<p>test 1</p>
<h2>h2</h2>
<p>test 2</p>
<h1>h1-1 <span>x</span></h1>
<p>test 3</p>
<h2>h2-2</h2>
<p>test 4</p>
I have mented the code. And I hope you understand what is going on here, but if not, just ment below.
function hideUntilNextSiblingWithSameName(elementName) {
/*
* @Param {elementName: String}
* Hide all elements until the next one with the same name
*/
var trackH1 = 0,
element = document.getElementsByTagName(elementName)[0],
node = element.parentNode.firstChild;
do {
// Keep truck of element with the same name.
if (node.tagName === element.tagName) trackH1 += 1;
// Stop if catch element with same name
if (trackH1 == 2) break;
// Do not hide link, script, style and text node
if (node.nodeType === 3 || node.tagName === "LINK" || node.tagName === "SCRIPT" || node.tagName === "STYLE") continue;
// Hide element
else {
node.style.visibility = "hidden";
}
} while (node = node.nextSibling)
}
hideUntilNextSiblingWithSameName("h1")
<h1>h1 <span>x</span></h1>
<p>test 1</p>
<h2>h2</h2>
<p>test 2</p>
<h1>h1-1 <span>x</span></h1>
<p>test 3</p>
<h2>h2-2</h2>
<p>test 4</p>
<link rel="stylesheet" href="">
<script></script>
<style></style>
https://codepen.io/anon/pen/QxGVNG
At the moment, I hide the elements. But if you want to even not display the elements, use node.style.display = "none";
instead of node.style.visibility = "hidden";
in the else clause.
You can do this in a simpler way like the following. The idea is to find the immediate parent of the nodes, body
in this case, and find all the immediate children of it.
Then, start removing the elements until we've found the desired element. In this example, I've tried to hide the elements, but you can simply change the code the remove them instead.
Following is a working demo:
const elems = document.querySelectorAll('body > *');
const elemBefore = document.querySelectorAll('h1')[1];
for (let i = 0; i < elems.length; i++) {
let elem = elems[i];
if (elem === elemBefore) {
break;
}
elem.style.display = 'none';
}
h1 {
border-bottom: solid 1px #000;
}
span {
float: right;
}
<h1>h1 <span>x</span></h1>
<p>test 1</p>
<h2>h2</h2>
<p>test 2</p>
<h1>h1-1 <span>x</span></h1>
<p>test 3</p>
<h2>h2-2</h2>
<p>test 4</p>
Maybe you can use a trick and hide the
behind the h1 and h2 via css:
const selector = document.querySelectorAll('h1, h2');
const clickH = function (event) {
const h = event.target;
const attr = h.getAttribute('class');
if (attr === '') {
h.setAttribute('class', 'closed')
return undefined;
}
h.setAttribute('class', '')
}
selector.forEach((h) => {
h.setAttribute('class', 'closed')
h.addEventListener('click', clickH);
})
h1, h2 {
position: relative;
border-bottom: solid 1px #000;
background-color: #fff;
height: 50px;
border-bottom: solid 1px #000;
display: block;
z-index: 2;
margin-bottom: 0;
}
.closed {
margin-bottom: -50px;
}
p {
height: 30px;
}
span {
float: right;
}
<h1>h1 <span>x</span></h1>
<p>test 1</p>
<h2>h2</h2>
<p>test 2</p>
<h1>h1-1 <span>x</span></h1>
<p>test 3</p>
<h2>h2-2</h2>
<p>test 4</p>