How to find the next class from table which has a text value

Viewed 45

i am trying to calculate the consumed volume from a table from each day (date with it's previous date). When there are no table rows with empty volumes, this works correct. But when there are some table rows which have empty volumes, it fails. In code below you can see what i mean:

<table>
  <tr>
    <td class="date">2021-08-15</td>
    <td class="volume">80</td>
    <td class="consumed"></td> // text 30 (110 - 80)
  </tr>
  <tr>
    <td class="date">2021-08-14</td>
    <td class="volume">110</td>
    <td class="consumed"></td> // text 50 (160 - 110)
  </tr>
  <tr>
    <td class="date">2021-08-13</td>
    <td class="volume">160</td>
    <td class="consumed"></td> // text 60 (220 - 160)
  </tr>
  <tr>
    <td class="date">2021-08-12</td>
    <td class="volume">220</td>
    <td class="consumed"></td> 
  </tr>
</table>

$('.volume').each(function() {
  const next = $(this).parent().next().find('.volume')
  $(this).siblings('.consumed').html(+next.html() - $(this).html())
})

Above works great but it fails when my table looks like this:

<table>
  <tr>
    <td class="date">2021-08-15</td>
    <td class="volume">80</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-15</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-14</td>
    <td class="volume">110</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-14</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-14</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-13</td>
    <td class="volume">160</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-13</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-12</td>
    <td class="volume">220</td>
    <td class="consumed"></td>
  </tr>
</table>

Here is a fiddle how it should look like: Fiddle

So i need something like:

$('.volume').each(function() {
  const next = $(this).parent().next().find('.volume')// find next volume if not empty!
  $(this).siblings('.consumed').html(+next.html() - $(this).html()) // which belongs to a volume which is not empty!


3 Answers

You could get all next tr elements from the current one and then filter out the ones without the text and then take the first one from that result and that will be your next value.

$('.volume').each(function() {
  const currentValue = $(this).text();
  const nextValue = $(this)
    .parent('tr')
    .nextAll('tr')
    .filter(function() {
      return $(this).find('.volume').text().length;
    }).first().find('.volume').text()

  if (currentValue && nextValue) {
    $(this).next('.consumed').text(
      Number(nextValue) - Number(currentValue)
    )
  }
})
td.date, td.volume, td.consumed {
  border: 1px solid #ccc;
  padding: 5px;
}
h1 {
  font-weight: bold;
  font-size:20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Without empty volumes: shows correct</h1>

<table class="table">
  <thead>
    <tr>
      <th>Date</th>
      <th>Vol</th>
      <th>Consumed</th>
    </tr>
  </thead>
  <tr>
    <td class="date">2021-08-15</td>
    <td class="volume">80</td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-14</td>
    <td class="volume">110</td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-13</td>
    <td class="volume">160</td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-12</td>
    <td class="volume">220</td>
    <td class="consumed"></td>
  </tr>
</table>
<!----------------------------------------------->
<br /><br />
<h1>With some empty volumes; now it fails</h1>
<table class="table">
  <thead>
    <tr>
      <th>Date</th>
      <th>Vol</th>
      <th>Consumed</th>
    </tr>
  </thead>
  <tr>
    <td class="date">2021-08-15</td>
    <td class="volume">80</td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-15</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-14</td>
    <td class="volume">110</td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-14</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-14</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-13</td>
    <td class="volume">160</td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-13</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-12</td>
    <td class="volume">220</td>
    <td class="consumed"></td>
  </tr>

+next.html() will create a NaN when there isn't a value there. Try +(next.html() || $(this).html()) instead.

$('.volume').each(function() {
  const next = $(this).parent().next().find('.volume')
  var next_val = +(next.html() || $(this).html());
  $(this).siblings('.consumed').html(next_val - $(this).html())
})
td{
 border: 1px solid black;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<table>
  <tr>
    <td class="date">2021-08-15</td>
    <td class="volume">80</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-15</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-14</td>
    <td class="volume">110</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-14</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-14</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-13</td>
    <td class="volume">160</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-13</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-12</td>
    <td class="volume">220</td>
    <td class="consumed"></td>
  </tr>
</table>

Easy way to attack it, look for the volume elements with text, do not select the trs elements.

var volumeElems = $(".volume").not(":empty");
volumeElems.each(function(index, elem){
  if (index===volumeElems.length-1) return;
  const currentElem = $(elem);
  const curr = +currentElem.text();
  const prev = +volumeElems.eq(index+1).text();
  currentElem.next().text(prev-curr);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
  <tr>
    <td class="date">2021-08-15</td>
    <td class="volume">80</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-15</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-14</td>
    <td class="volume">110</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-14</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-14</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-13</td>
    <td class="volume">160</td>
    <td class="consumed"></td>
  </tr>
   <tr>
    <td class="date">2021-08-13</td>
    <td class="volume"></td>
    <td class="consumed"></td>
  </tr>
  <tr>
    <td class="date">2021-08-12</td>
    <td class="volume">220</td>
    <td class="consumed"></td>
  </tr>
</table>

Related