0

I am trying to solve a problem without using JavaScript loops. I want to apply css rules to table cells. I struggle with applying the rules before my start cell.

I found this question Select all elements before element with class? I struggle with the implementation of this technique as my code would need a more sophisticated check. Especially when thinking of dealing with two bookings before the start.

If I could find the last booking before the start I could use the technique from that question.

Does anyone have an idea how to solve this without javascript?

table {
  margin: auto;
  width: 100%;
  border: thick groove black;
  border-radius: 5px;
  text-align: center;
}
td {
  border: thin groove gray;
  width: 10vw;
  height: 5vh;
}
td:hover {
  cursor: pointer;
}
td.start {
  background-color: green;
}
td.booked {
  background-color: orange;
  cursor: not-allowed;
}
td.start ~ td:not(.booked) {
  background-color: lightblue;
}
td.start ~ td ~ td.booked:hover {
  cursor: not-allowed;
}
td.start ~ td ~ td.booked ~ td {
  background-color: red;
}
td.start ~ td ~ td.booked ~ td:hover {
  cursor: not-allowed;
}
<br />
<table>
  <thead>
    <tr>
      <td>Invalid</td>
      <td>Invalid</td>
      <td>Booked</td>
      <td>OK</td>
      <td>OK</td>
      <td>Start</td>
      <td>OK</td>
      <td>OK</td>
      <td>Booked</td>
      <td>Invalid</td>
      <td>Invalid</td>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td></td>
      <td></td>
      <td class="booked"></td>
      <td></td>
      <td></td>
      <td class="start"></td>
      <td></td>
      <td></td>
      <td class="booked"></td>
      <td></td>
      <td></td>
    </tr>
  </tbody>
</table>

<p
  style="text-align:center; border: thin groove gray; border-radius:5px; margin: 1.25rem 4vw;
          padding: 1.25rem; 
          "
>
  Relative to th start cell, cells after a booking are not allowed. Cells before a booking are allowed.
  <br />
  <br />

  We can select the cells after the start cell with: <br /><br />
  <code
    style="background-color:lightgray; padding:.75rem;
               border-radius: 5px
               "
    >td.start ~ td:not(.booked) { background-color: lightblue; }</code
  >
  <br />
  <br />

  We can select the cells after the booked cell after the start cell with:
  <br /><br />
  <code
    style="background-color:lightgray; padding:.75rem;
               border-radius: 5px
               "
    >td.start ~ td ~ td.booked ~ td { background-color: red; }</code
  >
  <br />
  <br />
  <br />
  <strong>
    Q. How can we select the cells before the start cell and apply the rules ?</strong
  >
  <br />
</p>
The Fool
  • 16,715
  • 5
  • 52
  • 86

2 Answers2

0

In this case, I think can't achieve it by linked question's next-sibling combinator and universal selector combination. Use JavaScript or apply class, data-* attribute each td elements.

sanriot
  • 804
  • 4
  • 13
0

CSS doesn't do "look behind", so you can never style something based on what comes after it. The linked trick works because it styles everything, then removes the style after the condition was met. We can use the same technique.

table {
  margin: auto;
  width: 100%;
  border: thick groove black;
  border-radius: 5px;
  text-align: center;
}
td {
  border: thin groove gray;
  width: 10vw;
  height: 5vh;
}

tbody td {
  /* style everything */
  background-color: red;
  cursor: not-allowed;
}
tbody td.booked ~ * {
  /* everything following "booked" */
  background-color: lightblue;
  cursor: pointer;
}
tbody td.start {
  background-color: green;
}
tbody td.booked {
  background-color: orange;
  cursor: not-allowed;
}
tbody td.start ~ td.booked ~ * {
  /* everything following "booked" following "start" */
  background-color: red;
  cursor: not-allowed;
}
<table>
  <thead>
    <tr>
      <td>Invalid</td>
      <td>Invalid</td>
      <td>Booked</td>
      <td>OK</td>
      <td>OK</td>
      <td>Start</td>
      <td>OK</td>
      <td>OK</td>
      <td>Booked</td>
      <td>Invalid</td>
      <td>Invalid</td>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="booked"></td>
      <td></td>
      <td class="booked"></td>
      <td></td>
      <td></td>
      <td class="start"></td>
      <td></td>
      <td></td>
      <td class="booked"></td>
      <td></td>
      <td></td>
    </tr>
  </tbody>
</table>

As you can see, this works even with two 'booked' items before start, but we lose control of the 'invalid' in between them. This is because we have to style everything after 'booked' before we know if 'start' is coming next.

A better solution, if at all possible, is to add classes to each cell within the code that generates the HTML. Or, as you've mentioned, retroactively add classes with JavaScript.

Blorf
  • 536
  • 3
  • 12
  • yes thats what i was thinking. Your second cell is blue however. It should be really invalid. If any cell after the first booking should be invalid in both directions. – The Fool Mar 06 '20 at 22:43
  • Yes, I've mentioned this above. The problem is that you can't ever style something based on what comes after it. If you remove the first 'booked' class you'll see it looks the way you want, because we can style everything after that. And we can style everything after 'booked' that's also after 'start' differently, making the last 'invalid' look right. But at the beginning, you want to style based on what's coming next. That won't happen. – Blorf Mar 07 '20 at 23:43
  • Yup this is why I mentioned that if I were able to find the last booking the technique would work but otherwise not. I know its not possible to go backwards. I am using two JS loops now which I didn't want to. Starting from the start cell, and they go in both directions till the end of the row and add CSS classes if needed. So, its still just linear complexity. – The Fool Mar 08 '20 at 08:43