0

I have a list of items with a data attribute of boolean value. List is sorted in a way, that items with true will always come first, false will be placed after them (it's possible that all elements will have true or false as well). I want to display only two middle elements - last of true and first of false ones. If all elements have the same value of attribute, then only single element will be displayed.

Here's example HTML:

<div class="container">
    <div class="item" data-attr="true">1<div>
    <div class="item" data-attr="true">2<div>
    <div class="item" data-attr="false">3<div>
    <div class="item" data-attr="false">4<div>
</div>

I know how to select items by attribute:

.container .item[data-attr="true"],
.container .item[data-attr="false"] {
    display: none;
}

but I don't know how to select all except first or last of those preselected items.

Expected visible output would be this:

<div class="container">
    <div class="item" data-attr="true">2<div>
    <div class="item" data-attr="false">3<div>
</div>

I can probably add some more JS code, so that those items receive additional class or another data attribute, but I wonder if it's possible with just CSS.

Soul Reaver
  • 2,012
  • 3
  • 37
  • 52
  • does this answer help you? https://stackoverflow.com/questions/13319334/select-all-except-first-and-last-td-element-in-one-selector – nonono Jan 20 '20 at 08:12
  • @nonono unfortunately it won't. I've tried this earlier. Last child means last of it's parent's child, not last of preslected children – Soul Reaver Jan 20 '20 at 08:15
  • 1
    You can select the first [false] quite easily with `[false]:first-child,[true]+[false]`, but the last [true] is a bit more complicated... – Kaiido Jan 20 '20 at 08:18
  • That's a step forward @Kaiido :) I've asked about selecting all except those two, but in the end, hiding all and overriding that with this style with single item selector would also work. Now I have to understand what you did there ... – Soul Reaver Jan 20 '20 at 08:25
  • 1
    Simply select [false] if it's the very first element (`[false]:first-child`) or if it is following a [true] (`[true]+[false]`). – Kaiido Jan 20 '20 at 08:27
  • Right, now I feel dumb :/ – Soul Reaver Jan 20 '20 at 08:29

2 Answers2

1

Unfortunately, after further reading I came to a conclusion that as of now it's not possible to achieve what I want with pure CSS. There is hope for (near?) future though.

What I want to achieve can be "simplified" by, first, hiding everything, then overriding that styling only for one or two selected items (thanks @Kaiido for your comment to OP). For now, out of 4 possible cases (required overrides), css can handle only 3:

  • last item in full true list with [data-attr="true"]:last-child,
  • first item in full false list with [data-attr="false"]:first-child,
  • first false item in mixed list with [data-attr="true"]+[data-attr="false"]

For the 4th case, this draft has to become working standard and be implemented by major browser engines:

  • last true item in mixed list with :has(+[data-attr="false"]) (I hope that's the right code for current draft).

There are ways to simulate "previous sibling selector" functionality, although that won't work when you need both previous and next sibling selectors. More on that here

Example below (I've grayed out items instead of hiding them so that original lists are visible):

div {
  border: 1px solid red;
  margin: 5px;
}

/* virtual display: none; on every item */
.container .item {
  background: gray;
}

/* "unhiding" required items */
.container .item[data-attr="true"]:last-child,
.container .item[data-attr="false"]:first-child,
.container .item[data-attr="true"]+[data-attr="false"]
{
  background: lime;
}

/* 4th case, that might work in the future with introduction of CSS selectors level 4 */
.container .item[data-attr="true"]:has( +[data-attr="false"] )
{
  background: lime;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div class="container">
    <div class="item" data-attr="true">1 true</div>
    <div class="item" data-attr="true">2 true</div>
    <div class="item" data-attr="false">3 false</div>
    <div class="item" data-attr="false">4 false</div>
  </div>
  <div class="container">
    <div class="item" data-attr="true">1 true</div>
    <div class="item" data-attr="true">2 true</div>
    <div class="item" data-attr="true">3 true</div>
    <div class="item" data-attr="true">4 true</div>
  </div>
  <div class="container">
    <div class="item" data-attr="false">1 false</div>
    <div class="item" data-attr="false">2 false</div>
    <div class="item" data-attr="false">3 false</div>
    <div class="item" data-attr="false">4 false</div>
  </div>
</body>
</html>
Soul Reaver
  • 2,012
  • 3
  • 37
  • 52
-1

I think you can achieve that with overriding. Try the following

.container .item[data-attr="true"],
.container .item[data-attr="false"] {
    display: none;
}

.container .item[data-attr="true"]:last-child {
    display: block; /* or any other you need */
}

.container .item[data-attr="false"]:first-child {
    display: block; /* or any other you need */
}
IAmVisco
  • 373
  • 5
  • 14
  • Unfortunately, first/last child selects children from their parents, ignoring initial selection of items. So applying `last-child` to first group of items in most cases styles nothing (works only when all items have same value of attribute). – Soul Reaver Jan 20 '20 at 08:19