PYB
PYB

Reputation: 533

How to remove a class from an element based on matching classList

I have a HTML table where each column is part of its own class. When clicking a cell in a column, a new class should be added to that cell (.selected). Other cells in the same column should have the '.selected' class removed, so that only 1 cell per column can be part of the '.selected' class. To do this, I compare the classList of all elements in the table to 'this' being the clicked cell, remove the class when the list matches, and then re-add to the clicked cell. This fails, ultimately having all cells part of both classes.

Logs show that for the clicked cell, the classList correctly updates (added, then removed, then re-added). For the other cells, they are unaltered by the 'if' condition. Here is full HTML doc:

<!DOCTYPE html>
<html>
<head>
    <title>test</title>
</head>

<body>

<style>
    table {
    font-family: google-sans, sans-serif;
    border-collapse: collapse;
    width: 100%;
    }

    td,
    th {
    border: 2px solid #48236F;
    text-align: center;
    padding: 8px;
    width: 30%;

    }
    th {
    background-color: grey;
    color: white;
    border: 2px solid #ffffff;
    width: 10%;
    }

    .option {
    color: #48236F;
    font-weight: bold;
    }

    .selected {
    background-color: #b7dc90;
    }
</style>


<div>Choose your coverage situation</div>
<select id="coverage" onchange="mainFunction()">
    <option value="">--Choose below--</option>
    <option value="1102">Single</option>
    <option value="1610">Couple</option>
    <option value="2118">Family</option>
</select><br><br>


<span>Dollars Allocated: &emsp;</span>
<span id="dollars_id">0 $</span><br><br>

<table id="display-table">
    <tr>
        <th></th>
        <th>Col 1</th>
        <th>Col 2</th>
        <th>Col 3</th>
        </tr>
    <tr>
        <th>Option 1</th>
        <td class="col_1" value="10">101</td>
        <td class="col_2" value="20">201</td>
        <td class="col_3" value="30">301</td>
    <tr>
    <tr>
        <th>Option 2</th>
        <td class="col_1">102</td>
        <td class="col_2">202</td>
        <td class="col_3">302</td>
    <tr>
    <tr>
        <th>Option 3</th>
        <td class="col_1">103</td>
        <td class="col_2">203</td>
        <td class="col_3">303</td>
    <tr>
    <tr>
        <th>Option 4</th>
        <td class="col_1">104</td>
        <td class="col_2">204</td>
        <td class="col_3">304</td>
    <tr>
    <tr>
        <th>Option 5</th>
        <td class="col_1">105</td>
        <td class="col_2">205</td>
        <td class="col_3">305</td>
    <tr>


</table>

<br>

<span>Remaining Dollars &emsp;</span>
<span id="dollars_left_id">0 $</span><br><br>

<script>

function currencyFormat(num) {
  return num.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,') + ' $';
}

var dollars_allocated = 0;
var deduct = 0;
var test = 0;

function mainFunction() {
    dollars_allocated = Number(document.getElementById("coverage").value);
    document.getElementById("dollars_id").innerHTML = dollars_allocated;
    var table = document.getElementById('display-table');
    var cells = table.getElementsByTagName('td');
    for(let cell of cells){
        cell.onclick = function() {
            var dollars_remaining = dollars_allocated;
            // Add the clicked cell to the .selected class
            console.log(this.classList);
            this.classList.add("selected");
            console.log(this.classList);
            for (var i of document.getElementsByTagName('td')){
                if (i.classList == this.classList){
                    i.classList.remove("selected");
                }
                console.log(i.classList);
            }
            console.log(this.classList);

            this.classList.add("selected");
            console.log(this.classList);
            deduct = deduct + Number(this.innerText);
            document.getElementById("dollars_left_id").innerHTML = dollars_remaining - deduct;
        }
    }
}

window.onload = mainFunction();
</script>
</body>
</html>

I would expect the console log to show only 1 element out of the 15 to be part of the '.selected' class. The selected class shows a green background so it is easy to see the error.

Appreciate the help.

EDIT

Based on zer00ne's excellent answer, here is the final function with a few adjustments: 1- Made sure that only <td> elements could be clicked 2- "Remaining" adds up all elements part of the 'selected' class, not just the target's 3- Added a 'value' attribute to each element to allow a value to be returned regardless of the cell's innerText, for more flexibility.

  <script>
    const form = document.forms.healthcare;
    const table = document.getElementById('display');
    const cells = document.getElementsByTagName('td');

    form.onchange = dollars;
    table.onclick = dollars;

    function dollars(e) {
    const target = e.target;
    const type = e.type;
    const fields = form.elements;
    const col1 = Array.from(table.querySelectorAll('.col1'));
    const col2 = Array.from(table.querySelectorAll('.col2'));
    const col3 = Array.from(table.querySelectorAll('.col3'));

    if (type === 'change') {
        if (target.matches('#coverage')) {
        fields.allocated.value = currency(parseFloat(target.value));
        }
    } else if (type === 'click') {
        if (target.matches('.col1')) {
        for (let cel1 of col1) {
            cel1.classList.remove('selected');
        }
        } else if (target.matches('.col2')) {
        for (let cel2 of col2) {
            cel2.classList.remove('selected');
        }
        } else {
        for (let cel3 of col3) {
            cel3.classList.remove('selected');
        }
        }

        // Add selected class to clicked cell
        if (target.tagName == 'TD'){
            target.classList.add('selected');
        }

        // Add total of selected cells
        var totalPremium = 0;
        for (let cell of cells){
            if (cell.classList.contains('selected')){
                totalPremium = totalPremium + parseFloat(cell.getAttribute('value'));
            }
        }

        // Substract totalPremium from the allocated dollars
        fields.remaining.value = currency(parseFloat(fields.coverage.value) - parseFloat(totalPremium));

    } else {
        return false;
    }
    }

    function currency(number, country = 'en-US') {
    return (number).toLocaleString(country, {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
        style: "currency",
        currency: "USD"
    });
    }
  </script>

Upvotes: 1

Views: 616

Answers (2)

JasonB
JasonB

Reputation: 6368

Using i.classList.value == this.classList.value in your test condition instead of comparing the entire object with the == operator worked for me.

function currencyFormat(num) {
  return num.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,') + ' $';
}

var dollars_allocated = 0;
var deduct = 0;
var test = 0;

function mainFunction() {
  dollars_allocated = Number(document.getElementById("coverage").value);
  document.getElementById("dollars_id").innerHTML = dollars_allocated;
  var table = document.getElementById('display-table');
  var cells = table.getElementsByTagName('td');
  for (let cell of cells) {
    cell.onclick = function() {
      var dollars_remaining = dollars_allocated;
      
      // Add the .selected class to the clicked cell
      this.classList.add("selected");
      var clickedCellClasslist = this.classList.value;
      
      for (var i of document.getElementsByTagName('td')) {
        if (i.classList.value == clickedCellClasslist) {
          i.classList.remove("selected");
        }
      }

      this.classList.add("selected");
      deduct = deduct + Number(this.innerText);
      document.getElementById("dollars_left_id").innerHTML = dollars_remaining - deduct;
    }
  }
}

window.onload = mainFunction();
<!DOCTYPE html>
<html>

<head>
  <title>test</title>
</head>

<body>

  <style>
    table {
      font-family: google-sans, sans-serif;
      border-collapse: collapse;
      width: 100%;
    }
    
    td,
    th {
      border: 2px solid #48236F;
      text-align: center;
      padding: 8px;
      width: 30%;
    }
    
    th {
      background-color: grey;
      color: white;
      border: 2px solid #ffffff;
      width: 10%;
    }
    
    .option {
      color: #48236F;
      font-weight: bold;
    }
    
    .selected {
      background-color: #b7dc90;
    }
  </style>


  <div>Choose your coverage situation</div>
  <select id="coverage" onchange="mainFunction()">
    <option value="">--Choose below--</option>
    <option value="1102">Single</option>
    <option value="1610">Couple</option>
    <option value="2118">Family</option>
  </select><br><br>


  <span>Dollars Allocated: &emsp;</span>
  <span id="dollars_id">0 $</span><br><br>

  <table id="display-table">
    <tr>
      <th></th>
      <th>Col 1</th>
      <th>Col 2</th>
      <th>Col 3</th>
    </tr>
    <tr>
      <th>Option 1</th>
      <td class="col_1" value="10">101</td>
      <td class="col_2" value="20">201</td>
      <td class="col_3" value="30">301</td>
    </tr>
    <tr>
      <th>Option 2</th>
      <td class="col_1">102</td>
      <td class="col_2">202</td>
      <td class="col_3">302</td>
    </tr>
    <tr>
      <th>Option 3</th>
      <td class="col_1">103</td>
      <td class="col_2">203</td>
      <td class="col_3">303</td>
    </tr>
    <tr>
      <th>Option 4</th>
      <td class="col_1">104</td>
      <td class="col_2">204</td>
      <td class="col_3">304</td>
    </tr>
    <tr>
      <th>Option 5</th>
      <td class="col_1">105</td>
      <td class="col_2">205</td>
      <td class="col_3">305</td>
    </tr>


  </table>

  <br>

  <span>Remaining Dollars &emsp;</span>
  <span id="dollars_left_id">0 $</span><br><br>


</body>

</html>

Upvotes: 0

zer00ne
zer00ne

Reputation: 43910

There's a ton of changes but the change concerning the actual question:

"...show only 1 element out of the 15 to be part of the '.selected' class."

is to remove .selected from all <td>, then add .selected to the clicked <td>. The clicked <td> can be easily determined by using the Event.target property.

  for (let cell of cells) {
    cell.classList.remove('selected');
  }
  target.classList.add('selected');

Edit

In the OP there is some conflicting information:

"...so that only 1 cell per column can be part of the '.selected' class."

Rather than one td.selected there is a possibility for three td.selected -- one per column.

Demo 1 features the following behavior:

Only one td.selected

Demo 2 features the following behavior:

One td.selected per column for a total of three td.selected


Demo 1

const form = document.forms.healthcare;
const table = document.getElementById('display');

form.onchange = dollars;
table.onclick = dollars;

function dollars(e) {
  const target = e.target;
  const type = e.type;
  const fields = form.elements;
  const cells = table.querySelectorAll('td');

  if (type === 'change') {
    if (target.matches('#coverage')) {
      fields.allocated.value = currency(parseFloat(target.value));
    }
  } else if (type === 'click') {
    if (target.matches('td')) {
      for (let cell of cells) {
        cell.classList.remove('selected');
      }
      target.classList.add('selected');
      let deduct = parseFloat(target.textContent);
      fields.remaining.value = currency(parseFloat(fields.coverage.value) - deduct);
    }
  } else {
    return false;
  }
}

function currency(number, country = 'en-US') {
  return (number).toLocaleString(country, {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
    style: "currency",
    currency: "USD"
  });
}
:root {
  font: 400 small-caps 14px/1.2 Arial
}

table {
  border-collapse: collapse;
  table-layout: fixed;
  width: 100%;
}

td,
th {
  border: 2px solid #48236F;
  text-align: center;
  padding: 8px;
  width: 25%;
}

th {
  background-color: grey;
  color: white;
  border: 2px solid #ffffff;
}

td,
output {
  font-family: Consolas
}

select {
  font: inherit
}

.option {
  color: #48236F;
  font-weight: bold;
}

.selected {
  background-color: #b7dc90;
}
<!DOCTYPE html>
<html>

<head>
  <title>test</title>
  <style></style>
</head>

<body>
  <form id='healthcare'>
    <label for='coverage'>Choose Your Coverage</label><br>
    <select id="coverage">
      <option value="">--Coverage--</option>
      <option value="1102">Single</option>
      <option value="1610">Married</option>
      <option value="2118">Dependents</option>
      <option value="3728">Married with Dependents</option>
    </select><br><br>

    <label for='allocated'>Allocated: &emsp;</label>
    <output id="allocated">0</output><br><br>

    <table id="display">
      <tr>
        <th></th>
        <th>Col 1</th>
        <th>Col 2</th>
        <th>Col 3</th>
      </tr>
      <tr>
        <th>Option 1</th>
        <td>101</td>
        <td>201</td>
        <td>301</td>
      </tr>
      <tr>
        <th>Option 2</th>
        <td>102</td>
        <td>202</td>
        <td>302</td>
      </tr>
      <tr>
        <th>Option 3</th>
        <td>103</td>
        <td>203</td>
        <td>303</td>
      </tr>
      <tr>
        <th>Option 4</th>
        <td>104</td>
        <td>204</td>
        <td>304</td>
      </tr>
      <tr>
        <th>Option 5</th>
        <td>105</td>
        <td>205</td>
        <td>305</td>
      </tr>
    </table>
    <br>
    <label for='remaining'>Remaining: &emsp;</label>
    <output id="remaining">0</output><br><br>
  </form>
  <script></script>
</body>

</html>

Demo 2

const form = document.forms.healthcare;
const table = document.getElementById('display');

form.onchange = dollars;
table.onclick = dollars;

function dollars(e) {
  const target = e.target;
  const type = e.type;
  const fields = form.elements;
  const col1 = Array.from(table.querySelectorAll('.col1'));
  const col2 = Array.from(table.querySelectorAll('.col2'));
  const col3 = Array.from(table.querySelectorAll('.col3'));

  if (type === 'change') {
    if (target.matches('#coverage')) {
      fields.allocated.value = currency(parseFloat(target.value));
    }
  } else if (type === 'click') {
    if (target.matches('.col1')) {
      for (let cel1 of col1) {
        cel1.classList.remove('selected');
      }
    } else if (target.matches('.col2')) {
      for (let cel2 of col2) {
        cel2.classList.remove('selected');
      }
    } else {
      for (let cel3 of col3) {
        cel3.classList.remove('selected');
      }
    }
    target.classList.add('selected');

    fields.remaining.value = currency(parseFloat(fields.coverage.value) - parseFloat(target.textContent));
  } else {
    return false;
  }
}

function currency(number, country = 'en-US') {
  return (number).toLocaleString(country, {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
    style: "currency",
    currency: "USD"
  });
}
:root {
  font: 400 small-caps 14px/1.2 Arial
}

table {
  border-collapse: collapse;
  table-layout: fixed;
  width: 100%;
}

td,
th {
  border: 2px solid #48236F;
  text-align: center;
  padding: 8px;
  width: 25%;
}

th {
  background-color: grey;
  color: white;
  border: 2px solid #ffffff;
}

td,
output {
  font-family: Consolas;
  font-size: 1.1rem;
}

select {
  font: inherit
}

.option {
  color: #48236F;
  font-weight: bold;
}

.selected {
  background-color: #b7dc90;
}
<!DOCTYPE html>
<html>

<head>
  <title>Healthcare</title>
  <style></style>
</head>

<body>
  <form id='healthcare'>
    <label for='coverage'>Choose Your Coverage</label><br>
    <select id="coverage">
      <option value="">--Coverage--</option>
      <option value="1102">Single</option>
      <option value="1610">Married</option>
      <option value="2118">Dependents</option>
      <option value="3728">Married with Dependents</option>
    </select><br><br>

    <label for='allocated'>Allocated:&emsp;</label>
    <output id="allocated">0</output><br><br>

    <table id="display">
      <thead>
        <tr>
          <th></th>
          <th>Col 1</th>
          <th>Col 2</th>
          <th>Col 3</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th>Option 1</th>
          <td class='col1'>101</td>
          <td class='col2'>201</td>
          <td class='col3'>301</td>
        </tr>
        <tr>
          <th>Option 2</th>
          <td class='col1'>102</td>
          <td class='col2'>202</td>
          <td class='col3'>302</td>
        </tr>
        <tr>
          <th>Option 3</th>
          <td class='col1'>103</td>
          <td class='col2'>203</td>
          <td class='col3'>303</td>
        </tr>
        <tr>
          <th>Option 4</th>
          <td class='col1'>104</td>
          <td class='col2'>204</td>
          <td class='col3'>304</td>
        </tr>
        <tr>
          <th>Option 5</th>
          <td class='col1'>105</td>
          <td class='col2'>205</td>
          <td class='col3'>305</td>
        </tr>
      </tbody>
    </table>
    <br>
    <label for='remaining'>Remaining:&emsp;</label>
    <output id="remaining">0</output><br><br>
  </form>
  <script></script>
</body>

</html>

Upvotes: 1

Related Questions