Sean Sean - 4 months ago 9
CSS Question

D3/CSS Dom upward traversal/selection

I have the following HTML structure to represent a calendar:

<table>
<thead>...</thead>
<tbody>
<tr>...</tr>
<tr>
<td day="4">...</td>
<td day="5">...</td>
<td day="6" class="is-startrange">...</td>
<td day="7">...</td>
<td day="8">...</td>
</tr>
<tr>
<td day="9">...</td>
<td day="10">...</td>
<td day="11">...</td>
<td day="12">
<button class="day" type="button">12</button>
</td>
<td day="13">...</td>
</tr>
</tbody>
</table>


My question is: starting from the button under day 12, how can I traverse up, select all the button elements until a
is-startrange
class is encountered?
Each table cell is a button representing a date and listeners have been added to all the button elements. When a date is clicked, I will get the selected date as starting point.

I want to add style to all the button elements between the start date and selected date (either add class or through pure CSS).

Is there a way this can be achieved in D3 selection or pure CSS?

Answer

As Gerardo Furtado already mentioned in his comment the question is not actually about traversing the DOM upwards, but rather about an iteration of td elements. This can easily be done by using d3.selectAll("td") which will yield a flattened selection of all tds found on the page. Depending on your layout you might need to further narrow the selection down to a specific table which could be done by adjusting the selector to "table.myTable td", "#tableId td" or the like.

Having this selection at hand you can apply a class, say range, by using selection.classed(names[, value]) which can take a function passed in as the second argument value:

If the value is a function, then the function is evaluated for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element. The function’s return value is then used to assign or unassign classes on each element.

The only task left is to implement a filter function which keeps track, if an element is within the desired or range or not and, thus, determines whether to assign the range class.

The following snippet shows how this could all be put together using a filter function rangeFilter() provided to .classed():

// The day parameter determines the stop criterion
function rangeFilter(day) {  
  // This property is closed over by the following function to keep track of the
  // range. If this is true, this element and following elements belong to the
  // range until this property becomes false again once reaching the button's td.
  var inRange = false;   
  
  // Filter function returning true, if the element belongs to the range.
  return function(d) {
    element = d3.select(this);   // The actual td element of this iteration step.
    // Evaluate if the element is still in the range or, in case the range has not
    // yet started, check if we reached the td.is-startrange.
    inRange = (inRange && element.attr("day") != day) 
            || element.classed("is-startrange");      

    // XOR to exclude the .is-startrange element.
    return inRange != element.classed("is-startrange"); 
  }
}

d3.selectAll("button")
  .on("click", function() {
    // For all tds check if they belong to the range and set the class based
    //  on the result of the filter function passing in this buttons value.
    d3.selectAll("td")
        .classed("range", rangeFilter(d3.select(this).text())); 
  });
.is-startrange {
  background-color: limegreen;
}

.range {
  background-color: red;
}
<script src="https://d3js.org/d3.v4.js"></script>
<h1>Hit the button</h1>
<table>
  <thead>...</thead>
  <tbody>
    <tr>...</tr>
    <tr>
      <td day="4">...4...</td>
      <td day="5">...5...</td>
      <td day="6" class="is-startrange">...6...</td>
      <td day="7">...7...</td>
      <td day="8">...8...</td>
    </tr>
    <tr>
      <td day="9">...9...</td>
      <td day="10">...10...</td>
      <td day="11">...11...</td>
      <td day="12">
        <button class="day" type="button">12</button>
      </td>
      <td day="13">...13...</td>
    </tr>
  </tbody>
</table>