skip to Main Content

First, I am not a professional. This is a personal project, so apologies if my terminology or POV of the issue is wrong. I’m working on a special kind of calculator that requires user input for the calculations. The calculator is made of several tables, but the first table is the most important one. It simply requires the user to divide a single "unit" into ratios that will be used throughout the rest of the calculator. These numbers must equal 100 or else the rest of the output will be incorrect.

I would like for the "TOTAL (%)" to have a function that checks that the number calculated in the textbox equals 100. If it doesn’t, I would like for the number to turn red along with a warning.

The issue I’m having is that the function doesn’t seem to be working. I’ve concluded that it could be one or more of the following reasons:

  1. The function simply doesn’t work as written.
  2. The function isn’t being triggered on the right element.
  3. The function isn’t being triggered by the right event.

Here’s the code that I’ve written:

function valTotal() {
  var x = document.getElementById('t1total').value;
  var y = 100;

  if (x > y) {
    alert("Warning! Your total is greater than 100%. Please adjust your ratios above so that the total equals 100% before continuing.");
    x.style.color = "#cf0911";
    return false;
  }
}

function myFun0() {
  let water = document.getElementById('t1water').value;
  let ingr1 = document.getElementById('t1ingr1').value;
  let ingr2 = document.getElementById('t1ingr2').value;
  let ingr3 = document.getElementById('t1ingr3').value;
  let ingr4 = document.getElementById('t1ingr4').value;
  let ingr5 = document.getElementById('t1ingr5').value;
  let ingr6 = document.getElementById('t1ingr6').value;
  let total = Number(water) + Number(ingr1) + Number(ingr2) + Number(ingr3) + Number(ingr4) + Number(ingr5) + Number(ingr6);

  document.getElementById("t1total").value = total;
}
body {
  font-family: monospace;
}

table,
td {
  border: 1px solid black;
  border-collapse: collapse;
}

.invis {
  border-style: hidden;
}

.txcenter {
  text-align: center;
}

.txbox1 {
  width: 50px;
}
<!DOCTYPE html>
<html>

<head>


</head>
<!--Calculator-->

<body>
  <div>
    <h2 id="calc">Substrate Calculator</h2>
    <table class="invis">
      <tr>
        <td>
          <!--Table 1: SUB RATIO-->
          <table style="float: left">
            <tr style="background-color:#751e0b">
              <td colspan="2">
                <div class="txcenter">
                  <b style="color:white">SUB RATIO</b>
                </div>
              </td>
            </tr>
            <!--Table 1: SUB RATIO > Water (%)-->
            <tr>
              <td>
                <div>Water (%)</div>
              </td>
              <td>
                <input class="txbox1" type="textbox" id="t1water" onkeyup="myFun0();myFun1();myFun2()" onchange="myFun0();myFun1();myFun2()">
              </td>
            </tr>
            <!--Table 1: SUB RATIO > Ingredient 1 (%)-->
            <tr>
              <td>
                <div>Ingredient 1 (%)</div>
              </td>
              <td>
                <input class="txbox1" type="textbox" id="t1ingr1" onkeyup="myFun0();myFun1();myFun2()" onchange="myFun0();myFun1();myFun2()">
              </td>
            </tr>
            <!--Table 1: SUB RATIO > Ingredient 2 (%)-->
            <tr>
              <td>
                <div>Ingredient 2 (%)</div>
              </td>
              <td>
                <input class="txbox1" type="textbox" id="t1ingr2" onkeyup="myFun0();myFun1();myFun2()" onchange="myFun0();myFun1();myFun2()">
              </td>
            </tr>
            <!--Table 1: SUB RATIO > Ingredient 3 (%)-->
            <tr>
              <td>
                <div>Ingredient 3 (%)</div>
              </td>
              <td>
                <input class="txbox1" type="textbox" id="t1ingr3" onkeyup="myFun0();myFun1();myFun2()" onchange="myFun0();myFun1();myFun2()">
              </td>
            </tr>
            <!--Table 1: SUB RATIO > Ingredient 4 (%)-->
            <tr>
              <td>
                <div>Ingredient 4 (%)</div>
              </td>
              <td>
                <input class="txbox1" type="textbox" id="t1ingr4" onkeyup="myFun0();myFun1();myFun2()" onchange="myFun0();myFun1();myFun2()">
              </td>
            </tr>
            <!--Table 1: SUB RATIO > Ingredient 5 (%)-->
            <tr>
              <td>
                <div>Ingredient 5 (%)</div>
              </td>
              <td>
                <input class="txbox1" type="textbox" id="t1ingr5" onkeyup="myFun0();myFun1();myFun2()" onchange="myFun0();myFun1();myFun2()">
              </td>
            </tr>
            <!--Table 1: SUB RATIO > Ingredient 6 (%)-->
            <tr>
              <td>
                <div>Ingredient 6 (%)</div>
              </td>
              <td>
                <input class="txbox1" type="textbox" id="t1ingr6" onkeyup="myFun0();myFun1();myFun2()" onchange="myFun0();myFun1();myFun2()">
              </td>
            </tr>
            <!--Table 1: SUB RATIO > TOTAL (%)-->
            <tr>
              <td>
                <div>TOTAL (%)</div>
              </td>
              <td>
                <input class="txbox1" type="textbox" id="t1total" onchange="valTotal()" readonly>
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
  </div>
</body>

</html>

is just the first table of the calculator. The issue I’m having is with the "TOTAL (%)" field which is the only place where the validation function will be used.

Many many thanks.

EDIT: The function in question is the valTotal() function.

3

Answers


  1. In your code there are some error.

    1. Inside function valTotal var x = document.getElementById('t1total'); with this line you are getting the element itself not its value. So to encounter that at the end you should do var x = document.getElementById('t1total').value; and that is the reason the validation is not working properly. in future do console.log to check if the value you want is that you are getting or not.

    2. In the if statement you are changing the color of x also so make an else statement to revert it back also if (x > y) { alert("Warning! Your total is greater than 100%. Please adjust your ratios above so that the total equals 100% before continuing."); x.style.color = "#cf0911"; return false;
      }else { document.getElementById('t1total').style.color = ""; // Reset color if valid }

      If you had previously set a color (e.g., red for an error state), this will reset it back to the default.

    3. IN the function myFun0 call valTotal function so before entering the next ingredient you could get if it exceed the 100%.

      function myFun0(){

      // rest of the code

      valTotal()

      }

    4. Now in html in the input type = "textbox" there is no such thing use type = "text" or instead of that use number and on all the input you are calling onkeyup="myFun0();myFun1();myFun2()" onchange="myFun0();myFun1();myFun2()"
      so first of all both onkeyup and onchange are doing the same thing calling the same function so you can just use one of then and also you can read about them by this link onkeyup onchnage. the other function you are calling which are not even written in your code. just do <input class="txbox1" type="text" id="t1water" onkeyup="myFun0()"> for all the input.

      And on total no need to call onchange = "valTotal()" because on every keyup the total is being calculated.

    hoping this will be helpful to you.

    function valTotal() {
          var x = document.getElementById('t1total').value;
          var y = 100;
    
          // Convert the value to a number for comparison
          if (Number(x) > y) {
            alert("Warning! Your total is greater than 100%. Please adjust your ratios above so that the total equals 100% before continuing.");
            document.getElementById('t1total').style.color = "#cf0911";  // Change color to red
            return false;
          } else {
            document.getElementById('t1total').style.color = "";  // Reset color if valid
          }
        }
    
        function myFun0() {
          let water = document.getElementById('t1water').value;
          let ingr1 = document.getElementById('t1ingr1').value;
          let ingr2 = document.getElementById('t1ingr2').value;
          let ingr3 = document.getElementById('t1ingr3').value;
          let ingr4 = document.getElementById('t1ingr4').value;
          let ingr5 = document.getElementById('t1ingr5').value;
          let ingr6 = document.getElementById('t1ingr6').value;
    
          // Calculate the total sum of all ingredient values
          let total = Number(water) + Number(ingr1) + Number(ingr2) + Number(ingr3) + Number(ingr4) + Number(ingr5) + Number(ingr6);
    
          // Display the result in t1total
          document.getElementById("t1total").value = total;
    
          // Call valTotal to check if the total exceeds 100%
          valTotal();
        }
    <!DOCTYPE html>
    <html>
    
    <head>
      <title>Substrate Calculator</title>
    </head>
    
    <body>
      <div>
        <h2 id="calc">Substrate Calculator</h2>
        <table class="invis">
          <tr>
            <td>
              <!-- Table 1: SUB RATIO -->
              <table style="float: left;">
                <tr style="background-color:#751e0b;">
                  <td colspan="2">
                    <div class="txcenter">
                      <b style="color:white;">SUB RATIO</b>
                    </div>
                  </td>
                </tr>
    
                <!-- Input rows -->
                <tr>
                  <td>Water (%)</td>
                  <td><input class="txbox1" type="text" id="t1water" onkeyup="myFun0()"></td>
                </tr>
                <tr>
                  <td>Ingredient 1 (%)</td>
                  <td><input class="txbox1" type="text" id="t1ingr1" onkeyup="myFun0()"></td>
                </tr>
                <tr>
                  <td>Ingredient 2 (%)</td>
                  <td><input class="txbox1" type="text" id="t1ingr2" onkeyup="myFun0()"></td>
                </tr>
                <tr>
                  <td>Ingredient 3 (%)</td>
                  <td><input class="txbox1" type="text" id="t1ingr3" onkeyup="myFun0()"></td>
                </tr>
                <tr>
                  <td>Ingredient 4 (%)</td>
                  <td><input class="txbox1" type="text" id="t1ingr4" onkeyup="myFun0()"></td>
                </tr>
                <tr>
                  <td>Ingredient 5 (%)</td>
                  <td><input class="txbox1" type="text" id="t1ingr5" onkeyup="myFun0()"></td>
                </tr>
                <tr>
                  <td>Ingredient 6 (%)</td>
                  <td><input class="txbox1" type="text" id="t1ingr6" onkeyup="myFun0()"></td>
                </tr>
    
                <!-- Total row -->
                <tr>
                  <td>TOTAL (%)</td>
                  <!-- because of okKeyup the total is calculating on everykeyup no need to call function here -->
                  <td><input class="txbox1" type="text" id="t1total" readonly></td>
                </tr>
              </table>
            </td>
          </tr>
        </table>
      </div>
      
      </body>
      </html>
    Login or Signup to reply.
  2. I suggest something like this:

    const
      myTableTBody = document.querySelector('#my-table')
    , PercentAll   = [...myTableTBody.querySelectorAll('input[type="range"]')]
    , PercentSum   = document.querySelector('#my-table tfoot output')
      ;
    
    /*------------------------------ init part *-*/
    PercentSum.value = 0;
    PercentSum.className = 'less';
    
    PercentAll.forEach( inR =>
      {
      inR.value                    = 0;
      inR.nextElementSibling.value = 0;
      })
    /*----------------------------  init part *- end */
    
    myTableTBody.addEventListener('input', e =>
      {
      if (!e.target.matches('input[type="range"]')) return;
    
      e.target.nextElementSibling.value = e.target.value; // set outout value.
      PercentSum.value     = PercentAll.reduce( (sum,inR) => sum += inR.valueAsNumber,0 );
      PercentSum.className = PercentSum.value == 100 ? 'eq100' : PercentSum.value < 100 ? 'less' : 'more';
      })
    body {
      font-family : Arial, Helvetica, sans-serif;
      font-size   : 14px;
      margin      : 0;
      padding     : 1em;
      }
    table {
      background      : darkblue; 
      border-collapse : separate;
      border-spacing  : 1px;
      margin-bottom   : .8em;
      }
    table caption {
      font-size    : 1.2rem;
      font-weight  : bold;
      padding      : .2em 0;
      border           : 1px solid darkblue;
      border-bottom    : 0;
      background-color : #7fccff;
      }
    table td { 
      background-color : whitesmoke; 
      padding          : .2em .5em;
      text-align       : left;
      width            : 5em;
      }
    table tr td:nth-of-type(1) { width: 8em;  }
    table tr td:nth-of-type(2) { width: 14em; }
    table td output {
      float   : right; 
      padding : .2em;
      }
    table tfoot output { 
      width : 5em; 
      text-align: right; 
      }
    .less  { background: #ffff00ce; }
    .eq100 { background: limegreen; }
    .more  { background: #dc143cce; }
    <table id="my-table">
      <caption>SUB RATIO</caption>
      <tbody>
        <tr>
          <td>Water (%)</td> 
          <td> 
            <input type="range" value="0" min="0" max="100" id="t1_Water">
            <output>0</output>
          </td>
        </tr>
        <tr>
          <td> Ingredient 1 (%)</td> 
          <td> 
            <input type="range" value="0" min="0" max="100" id="t1_ingr1">
            <output>0</output>
          </td>
        </tr>
        <tr>
          <td> Ingredient 2 (%)</td> 
          <td> 
            <input type="range" value="0" min="0" max="100" id="t1_ingr2">
            <output>0</output>
          </td>
        </tr>
        <tr>
          <td> Ingredient 3 (%)</td> 
          <td> 
            <input type="range" value="0" min="0" max="100" id="t1_ingr3">
            <output>0</output>
          </td>
        </tr>
        <tr>
          <td> Ingredient 4 (%)</td> 
          <td> 
            <input type="range" value="0" min="0" max="100" id="t1_ingr4">
            <output>0</output>
          </td>
        </tr>
        <tr>
          <td> Ingredient 5 (%)</td> 
          <td> 
            <input type="range" value="0" min="0" max="100" id="t1_ingr5">
            <output>0</output>
          </td>
        </tr>
        <tr>
          <td> Ingredient 6 (%)</td> 
          <td> 
            <input type="range" value="0" min="0" max="100" id="t1_ingr6">
            <output>0</output>
          </td>
        </tr>
      </tbody>
      <tfoot>
        <tr> <td> Total </td> <td > <output>0</output> </td> </tr> 
      </tfoot>
    </table>
    Login or Signup to reply.
  3. Edit: OP mentioned "…is just the first table of the calculator". The snippet now handles all tables identifiable with the data-attribute data-calculate-total

    Allow me to suggest some improvements to your code.

    1. it is generally not a good
      idea

      to use inline event handlers. Use
      addEventListener
      to add handling.
    2. use input[type="number"] for numeric input fields. It will give you a bit more control over the input. Aside:
      input[type="textbox"] is a non existing type1.
      Or use a slider (type="range" as demonstrated in the snippet from mister JoJo’s answer)
    3. use querySelectorAll with a relevant css selector to retrieve the input elements to use for calculation of the total.
    4. don’t annoy users with alerts!

    1 But it worked! Yep, because browsers substitute the field with the default type="text".

    With the above in mind, here is an example snippet. It uses event delegation for the handling. Note: for demo I’ve reduced the number of fields.

    document.addEventListener(`input`, handle);
    
    function handle(evt) {
      const calculateContainer = evt.target.closest(`[data-calculate-total]`);
      
      if (calculateContainer) {
        // remove the error class if applicable
        calculateContainer
          .querySelector(`.error`)?.classList.remove(`error`);
          
        // retrieve the input field for total
        const total = calculateContainer
          .querySelector(`tr.total td input`);
        
        // retrieve all input fields except the total
        const allInputFields = calculateContainer
          .querySelectorAll(`input:not([data-total])`);
        
        // use a reducer on the collection of fields 
        // (converted to array) to calculate the total
        const totalCalculated = [...allInputFields].reduce((acc, el) =>
          acc + valueToNumber(el.value), 0);
    
        // warn if total > 100
        if (totalCalculated > 100) {
          // we are using the .error class to signify an error
          return evt.target.closest(`td`).classList.add(`error`);
        }
    
        return total.value = `${totalCalculated}%`;
      }
    }
    
    // a numeric input field may not always deliver a number
    // and negative numbers would not apply, 
    // so we must check the value
    function valueToNumber(value) {
      const nr = parseInt(value);
      return !isNaN(nr) && nr > 0 ? nr : 0;
    }
    body {
      font-family: monospace;
    }
    
    table {
      border: 1px solid black;
      border-collapse: collapse;
      margin-top: .5rem;
    }
    
    td.txcenter {
      text-align: center;
      background-color: #751e0b;
      color: white;
    }
    
    td {
      padding: 1px 2px;
    }
    
    input {
      width: 50px;
    }
    
    tr.total {
      color: green;
      font-weight: bold;
      input {
        color: green;
      }
    }
    
    tr td.error input {
      color: red;
    }
    
    tr td.error:after {
      content: "total above 100%, please adjust";
      color: red;
      position: absolute;
      margin-left: 0.5rem;
    }
    <!--Table 1: SUB RATIO-->
    <table data-calculate-total="1">
      <tr>
        <td colspan="2" class="txcenter">
          <div>
            <b style="color:white">SUB RATIO</b>
          </div>
        </td>
      </tr>
      <tr>
        <td>
          <div>Water</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
      </tr>
      <tr>
        <td>
          <div>Ingredient 1</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
      </tr>
      <tr>
        <td>
          <div>Ingredient 2</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
      </tr>
      <tr>
        <td>
          <div>Ingredient 3</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
      </tr>
      <tr>
        <td>
          <div>Ingredient 4</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
        <tr class="total">
          <td>
            <div>TOTAL</div>
          </td>
          <td>
            <input data-total value="0%" readonly>
          </td>
        </tr>
    </table>
    
    <!--Table 2: SUB RATIO # 2-->
    <table data-calculate-total="1">
      <tr>
        <td colspan="2" class="txcenter">
          <div>
            <b style="color:white">SUB RATIO #2</b>
          </div>
        </td>
      </tr>
      <tr>
        <td>
          <div>Water</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
      </tr>
      <tr>
        <td>
          <div>Ingredient 1</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
      </tr>
      <tr>
        <td>
          <div>Ingredient 2</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
      </tr>
      <tr>
        <td>
          <div>Ingredient 3</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
      </tr>
      <tr>
        <td>
          <div>Ingredient 4</div>
        </td>
        <td>
          <input type="number" placeholder="%" min="0" max="100" step="1" autocomplete="off">
        </td>
        <tr class="total">
          <td>
            <div>TOTAL %</div>
          </td>
          <td>
            <input data-total value="0%" readonly>
          </td>
        </tr>
    </table>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search