0

I have following HTML structure. Is it possible to select first and last my-item element under parent element purely by CSS?

row in which my-item element is can be wrapped in other divs and (Angular) component elements. Also component elements can be nested within each other. This is HTML structure is generated based on current page.

In JavaScript I would accomplish this by selecting all my-item elements under parent as a flat list and selecting a first and last of them.

UPDATE: Update HTML template to more resemble my actual page content.

var parentElement = document.querySelector('.parent');
var myItems = parentElement.querySelectorAll('.my-item');

var firstItem = myItems[0];
var lastItem = myItems[myItems.length-1];

firstItem.classList.add('red-background');
lastItem.classList.add('red-background');
.red-background {
  background-color: red;
}
<div class="parent">
    <person-component>
        <div class="template">
            <div class="row">
                <component-row-template>
                    <div class="col">
                        <div class="my-item">Select me!</div>
                    </div>
                    <div class="col">
                        <div class="my-item">Not me</div>
                    </div>
                </component-row-template>
                <component-row-template>
                    <div class="col">
                        <div class="my-item">Not me</div>
                    </div>
                </component-row-template>
            </div>
            <div class="row-separator"></div>
            <div class="row">
                <component-row-template>
                    <div class="col">
                        <div class="my-item">Not me</div>
                    </div>
                    <div class="col">
                        <div class="my-item">Not me</div>
                    </div>
                </component-row-template>
            </div>
        </div>
    </person-component>
    <div class="row-separator"></div>
    <organization-component>
        <div class="template">
            <div class="row">
                <component-row-template>
                    <div class="col">
                        <div class="my-item">Not me</div>
                    </div>
                </component-row-template>
            </div>
            <div class="row-separator"></div>
            <div class="row">
                <component-row-template>
                    <div class="col">
                        <div class="my-item">Not me</div>
                    </div>
                    <div class="col">
                        <div class="my-item">Not me</div>
                    </div>
                </component-row-template>
            </div>
            <div class="row-separator"></div>
            <person-component>
                <div class="template">
                    <div class="row">
                        <component-row-template>
                            <div class="col">
                                <div class="my-item">Not me</div>
                            </div>
                            <div class="col">
                                <div class="my-item">Not me</div>
                            </div>
                        </component-row-template>
                    </div>
                    <div class="row">
                        <component-row-template>
                            <div class="col">
                                <div class="my-item">Not me</div>
                            </div>
                            <div class="col">
                                <div class="my-item">Not me</div>
                            </div>
                        </component-row-template>
                    </div>
                </div>
            </person-component>
        </div>
    </organization-component>
    <div class="row-separator"></div>
    <div class="row">
        <row-template>
            <div class="col">
                <div class="my-item">Not me</div>
            </div>
            <div class="col">
                <div class="my-item">Not me</div>
            </div>
        </row-template>
    </div>
    <div class="row-separator"></div>
    <div class="row">
        <row-template>
            <div class="col">
                <div class="my-item">Not me</div>
            </div>
            <div class="col">
                <div class="my-item">Select me!</div>
            </div>
        </row-template>
    </div>
</div>
mimo
  • 6,221
  • 7
  • 42
  • 50
  • 1
    I guess there is no pure css solution if you don't like to explictly traverse the DOM with css. See this thread https://stackoverflow.com/questions/41848550/how-to-select-nth-element-of-the-same-type – Kinimod Jan 27 '20 at 09:21

2 Answers2

1

The below code can work with the assumption that every child div of the parent has the desired ".my-item" and the every ".my-item" is inside a ".col".

It will select the first and the last div from the direct siblings of ".parent" and then will try to find the first and last ".col" and apply the rule to the ".my-item" inside them.

.parent > div:last-of-type .col:last-of-type .my-item, 
.parent > div:first-of-type .col:first-of-type .my-item  {
  background-color: red;
}

If that's not the case and you really don't know anything about the html structure at all, then I am afraid that CSS cannot help you..

Nasia Makrygianni
  • 761
  • 1
  • 11
  • 19
  • Thank you for your suggestion Nasia, I wanted to make example as simple as possible, but looks like I didn't included enough complexity. My real HTML is wrapped in custom elements (angular components like `` so I can't count on `.parent` containing children of only certain type like `div`. Sorry for that, I will update my example. But I feel like there is no way to achieve what I want just by CSS. – mimo Jan 27 '20 at 15:49
  • Oh, I see.. yes, unfortunately css is not able to go through the dom like this.. When you find yourself in such cases that you cannot cover with a simple css rule that feels like unnecessarily complicated html structure. Maybe you wanna check on this first and have a 'dry' approach – Nasia Makrygianni Jan 28 '20 at 10:19
1

Assuming that all .my-items are always wrapped inside a .col that is in turn wrapped inside at least another div (section) and that each section has only one .row (assumption taken from your example), yes it is possible to achieve what you want using a purely CSS approach. The approach is:

  • Select .parent, as we're only interested only in selecting .my-item inside this element
  • As all .my-items always have a div container (whether it be a nested container with class section or simply inside a row container), we can specifically determine which wrapper containers are the first and last. These first and last containers, respectively, must be the container in which the first and last my-item are contained.
  • As all .section container only has one .row, you can easily get the first and last .col of each .section. This means that to get the first .my-item, simply select the first .col and from the first .section wrapper and apply the style to .my-item. Same logic works for your last .my-item.

Here is a minimal working example below:

let firstChild = document.querySelector('.parent > div:first-child .col:first-child .my-item')
let lastChild = document.querySelector('.parent > div:last-child .col:last-child .my-item')
console.log(firstChild, lastChild)
.parent > div:first-child .col:first-child .my-item {
  background: #000;
  color: #FFF;
}

.parent > div:last-child .col:last-child .my-item {
  background: red;
  color: #FFF;
}
<div class="parent">
  <div class="main-section">
    <div class="section-wrapper">
      <div class="row">
        <div class="col">
          <div class="my-item">Select me!</div>
        </div>
        <div class="col">
          <div class="my-item">Not me</div>
        </div>
      </div>
    </div>
  </div>
  <div class="section-wrapper">
    <div class="row">
      <div class="col">
        <div class="my-item">Not me</div>
      </div>
      <div class="col">
        <div class="my-item">Not me</div>
      </div>
    </div>
  </div>
  <div class="row">
    <div class="col">
      <div class="my-item">Not me</div>
    </div>
    <div class="col">
      <div class="my-item">Select me!</div>
    </div>
  </div>
</div>

As mentioned, this code works if the above assumption is true. One easy example of when this code will break would be when there may be more that two .rows existing in one section-like wrapper.

Richard
  • 7,037
  • 2
  • 23
  • 76
  • Thanks Richard for your example, my real HTML is a more complicated than example I provided and there can be more `row`s within section wrapper. Sorry for not being explicit about that. I will update the example in question. – mimo Jan 27 '20 at 15:37
  • @mimo Try to cover all cases of what your HTML may be. This will help to generate more accurate results, if a CSS approach is indeed possible. – Richard Jan 27 '20 at 15:45
  • @mimo I don't think that it's possible to select what you want using pure CSS. If there is though, I still think that JS is a better solution for your case. Alternatively, if you can control the HTML structure, you can add a specific class for the first and last `.my-item`. – Richard Jan 28 '20 at 08:41