Selecting adjacent siblings as separate groups between subheadings?

Viewed 139

I need a way to identify all <p> tags between subheading tags <h2>as separated groups.

I can't use h2 ~ p as this selects all the <p> tags into a single selection.

I need to be able to stop the selection when another heading tag appears.

And the number of <p> tags will change so I doubt ::nth-child(x) will help, although I could be wrong.

<section>
  <h2>Heading I want</h2>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <h2>Heading I don't want</h2>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
</section>

Is this possible?

Update - Limitations:

Original structure can't be changed. The tool I'm using to export the text into data tables only has limited JS like commands that are as follows:

$e.text - gets the inner text or value

$e.outerHTML - get the outer html of the element

$e.prop - a function that behaves like jQuery prop function.

$e.css - a function that behaves like jQuery css function

$e.parentProp - a function that behaves like jQuery prop function.

$e.parent - get the parent element with all the same properties as $e/

3 Answers

If you are simply trying to add styling, you could apply styling to all of the <p> elements, and then remove that styling from the second group.

h2:first-child ~ p {
  background: green;
}
h2:not(:first-child) ~ p {
  background: none;
}
<section>
  <h2>Heading I want</h2>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <h2>Heading I don't want</h2>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
</section>

If you need to select them in JavaScript for something other than styling, you could use similar selectors to get all of them and then remove the ones in the second selection.

If you want to get all the groups, you could do something like this:

const elements = document.querySelectorAll('section h2, section p')

let groups = []
let currentGroup = []

for(let element of elements) {
  if(element.tagName === 'H2') {
    if(currentGroup.length) {
      groups.push(currentGroup)
      currentGroup = []
    }
  }
  else {
    currentGroup.push(element)
  }
}

if(currentGroup.length) {
  groups.push(currentGroup)
}

console.log(groups)
<section>
  <h2>Heading I want</h2>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <h2>Heading I don't want</h2>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
</section>

And if you just want the first group:

console.log(groups[0])

Or if you don't want to waste time getting the other groups:

const getFirstGroup = (elements, groupStartTagName) => {
  let items = []
  let foundStart = false
  
  for(let element of elements) {
    if(element.tagName === groupStartTagName) {
      if(foundStart) {
        return items
      }
      else {
        foundStart = true
      }
    }
    else if(foundStart) {
      items.push(element)
    }
  }
  
  // if there's only one group, we'll loop through all the elements and get here
  return items
}

const group = getFirstGroup(document.querySelectorAll('section h2, section p'), 'H2')

console.log(group)
<section>
  <h2>Heading I want</h2>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <p>Some text I want</p>
  <h2>Heading I don't want</h2>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
  <p>Text I don't want</p>
</section>

I don't know how limited you js functionality is, but if JQuery is available, you could perhaps do this.

$('section h2').each(function(){
    /*
      Here you could filter
      ex:
    */   
    if($(this).text() === 'Heading I want'){
        $(this).nextAll().each(function(){
            if($(this).is("p")){
                /*
                  Do something
                  ex:
                */
                alert($(this).text()); 
            } else {
                return false;
            };
        });
        return false;
    };
});

Related