JavaScript is not working for another JavaScripted input fields

I am trying to auto sum input fields using JavaScript in a web page. My javascript function is partially working. However .change is triggered when it is manually changed but not if it is changed by another JavaScript function. How can I triggered it even when it is not manually change?

<html>
    <head>
        <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
        <meta http-equiv="Pragma" content="no-cache">
        <meta http-equiv="Expires" content="0">
        <meta charset="utf-8">
        <link rel="icon" type="image/png" href="title-icon.png"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
        <style>
            input{width: 70px;text-align-last: center;}
            .icon{margin-right:3%;font-size:48px;color:red; animation-name: thi;animation-iteration-count: infinite; animation-duration: 3s;}
            @keyframes thi {from{color:red} to {color:white}}
        </style>
    </head>
    <body><br>
        <div style="margin:5%">
            <div class="row">
                <div class="col-4 border border-danger bg-primary">
                    <div class="text-center">Section A</div>
                </div>
                <div class="col-4 border border-danger bg-success">
                    <div class="text-center">Section B</div>
                </div>
                <div class="col-4 border border-danger bg-secondary">
                    <div class="text-center">Grand Total</div>
                </div>
            </div>
            <div class="row">
                <div class="col-4 border border-danger bg-primary">
                    <div class="row text-center">
                        <div class="col-4">
                            <div>Male</div>
                        </div>
                        <div class="col-4">
                            <div>Femle</div>
                        </div>
                        <div class="col-4">
                            <div>Total</div>
                        </div>
                    </div>
                    <div class="row text-center">
                        <div class="col-4">
                            <div><input type="text" class="male" id="maleA"></div>
                        </div>
                        <div class="col-4">
                            <div><input type="text" class="female" id="femaleA"></div>
                        </div>
                        <div class="col-4">
                            <div><input type="text" class="total" id="totalA" readonly disabled="disabled"></div>
                        </div>
                    </div>
                </div>
                
                <div class="col-4 border border-danger bg-success">
                    <div class="row text-center">
                        <div class="col-4">
                            <div>Male</div>
                        </div>
                        <div class="col-4">
                            <div>Femle</div>
                        </div>
                        <div class="col-4">
                            <div>Total</div>
                        </div>
                    </div>
                    <div class="row text-center">
                        <div class="col-4">
                            <div><input type="text" class="male" id="maleB"></div>
                        </div>
                        <div class="col-4">
                            <div><input type="text" class="female" id="femaleB"></div>
                        </div>
                        <div class="col-4">
                            <div><input type="text" class="total" id="totalB" readonly disabled="disabled"></div>
                        </div>
                    </div>
                </div>
                
                <div class="col-4 border border-danger bg-secondary">
                    <div class="row text-center">
                        <div class="col-4">
                            <div>Male</div>
                        </div>
                        <div class="col-4">
                            <div>Femle</div>
                        </div>
                        <div class="col-4">
                            <div>Total</div>
                        </div>
                    </div>
                    <div class="row text-center">
                        <div class="col-4">
                            <div><input type="text" class="male" id="maletotal" readonly disabled="disabled"></div>
                        </div>
                        <div class="col-4">
                            <div><input type="text" class="female" id="femaletotal" readonly disabled="disabled"></div>
                        </div>
                        <div class="col-4">
                            <div><input type="text" class="total" id="totalall" readonly disabled="disabled"></div>
                        </div>
                    </div><br>
                </div><br>
            </div>
            <div class="row text-right">
                <div class="col-12"><i class='icon fa fa-arrow-up'></i></div>
            </div>
            <div class="row text-right">
                <div class="col-12">Total of 'Grand Total' is not working!</div>
            </div>
            <br>
        </div>
    </body>
    <script>
        $(document).ready(function()
        { // working
            $('#maleA,#femaleA').change(function()
            {
                maleA = $('#maleA').val();
                femaleA = $('#femaleA').val();
                allA = Number(maleA)+Number(femaleA);
                $('#totalA').val(allA);
            }),
            $('#maleB,#femaleB').change(function()
            {
                maleB = $('#maleB').val();
                femaleB = $('#femaleB').val();
                allB = Number(maleB)+Number(femaleB);
                $('#totalB').val(allB);
            }),

            $('#maleA,#maleB').change(function()
            {
                maleA = $('#maleA').val();
                maleB = $('#maleB').val();
                allmale = Number(maleA)+Number(maleB);
                $('#maletotal').val(allmale);
            }),
            $('#femaleA,#femaleB').change(function()
            {
                femaleA = $('#femaleA').val();
                femaleB = $('#femaleB').val();
                allfemale = Number(femaleA)+Number(femaleB);
                $('#femaletotal').val(allfemale);
            }),

            //problem is here
            $('#maletotal,#femaletotal').change(function()
            {
                allmale = $('#maletotal').val(); //feed by javascript
                allfemale = $('#femaletotal').val(); //feed by javascript
                alltotal = Number(allmale)+Number(allfemale);
                $('#totalall').val(allA);
            })
        })
    </script>
</html>

Kindly help me to find out the mistake or suggest a better solution.


Solution 1:

The main issue is because setting the value of an input programmatically does not raise an event in the DOM - so your change event handler on the total fields in the last section never runs.

To correct this you can use jQuery's trigger() method to manually raise a change event on the field after setting its val().

Also, note that $('#totalall').val(allA); needs to be $('#totalall').val(alltotal); instead.

$(document).ready(function() { // working
  $('#maleA,#femaleA').change(function() {
      maleA = $('#maleA').val();
      femaleA = $('#femaleA').val();
      allA = Number(maleA) + Number(femaleA);
      $('#totalA').val(allA).trigger('change');
    }),
    
    $('#maleB,#femaleB').change(function() {
      maleB = $('#maleB').val();
      femaleB = $('#femaleB').val();
      allB = Number(maleB) + Number(femaleB);
      $('#totalB').val(allB).trigger('change');
    }),

    $('#maleA,#maleB').change(function() {
      maleA = $('#maleA').val();
      maleB = $('#maleB').val();
      allmale = Number(maleA) + Number(maleB);
      $('#maletotal').val(allmale).trigger('change');
    }),
    $('#femaleA,#femaleB').change(function() {
      femaleA = $('#femaleA').val();
      femaleB = $('#femaleB').val();
      allfemale = Number(femaleA) + Number(femaleB);
      $('#femaletotal').val(allfemale).trigger('change');
    }),

    //problem is here
    $('#maletotal,#femaletotal').change(function() {
      allmale = $('#maletotal').val(); //feed by javascript
      allfemale = $('#femaletotal').val(); //feed by javascript
      alltotal = Number(allmale) + Number(allfemale);
      $('#totalall').val(alltotal);
    })
})
input {
  width: 70px;
  text-align-last: center;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

<div style="margin:5%">
  <div class="row">
    <div class="col-4 border border-danger bg-primary">
      <div class="text-center">Section A</div>
    </div>
    <div class="col-4 border border-danger bg-success">
      <div class="text-center">Section B</div>
    </div>
    <div class="col-4 border border-danger bg-secondary">
      <div class="text-center">Grand Total</div>
    </div>
  </div>
  <div class="row">
    <div class="col-4 border border-danger bg-primary">
      <div class="row text-center">
        <div class="col-4">
          <div>Male</div>
        </div>
        <div class="col-4">
          <div>Femle</div>
        </div>
        <div class="col-4">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-4">
          <div><input type="text" class="male" id="maleA"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="female" id="femaleA"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="total" id="totalA" readonly disabled="disabled"></div>
        </div>
      </div>
    </div>

    <div class="col-4 border border-danger bg-success">
      <div class="row text-center">
        <div class="col-4">
          <div>Male</div>
        </div>
        <div class="col-4">
          <div>Femle</div>
        </div>
        <div class="col-4">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-4">
          <div><input type="text" class="male" id="maleB"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="female" id="femaleB"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="total" id="totalB" readonly disabled="disabled"></div>
        </div>
      </div>
    </div>

    <div class="col-4 border border-danger bg-secondary">
      <div class="row text-center">
        <div class="col-4">
          <div>Male</div>
        </div>
        <div class="col-4">
          <div>Femle</div>
        </div>
        <div class="col-4">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-4">
          <div><input type="text" class="male" id="maletotal" readonly disabled="disabled"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="female" id="femaletotal" readonly disabled="disabled"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="total" id="totalall" readonly disabled="disabled"></div>
        </div>
      </div><br>
    </div><br>
  </div>
</div>

However, you can improve the code quality and extensibility by using common classes on the elements to group them by behaviour. This makes the JS much more succinct and will work for an infinite number of 'Section' blocks. Try this:

jQuery($ => {
  let $inputs = $('.input');
  let $totalMale = $('#overall-male');
  let $totalFemale = $('#overall-female');
  let $totalOverall = $('#overall-total');

  let updateRowTotal = $row => {
    let maleCount = parseInt($row.find('.male').val(), 10) || 0;
    let femaleCount = parseInt($row.find('.female').val(), 10) || 0;
    $row.find('.total').val(maleCount + femaleCount);
  }
  
  let updateOverallTotal = () => {
    let maleCount = 0;    
    $inputs.filter('.male').each((i, el) => maleCount += (parseInt(el.value, 10) || 0));
    $totalMale.val(maleCount);
    
    let femaleCount = 0;
    $inputs.filter('.female').each((i, el) => femaleCount += (parseInt(el.value, 10) || 0));
    $totalFemale.val(femaleCount);
    
    $totalOverall.val(maleCount + femaleCount);
  }

  $inputs.on('input', e => {
    updateRowTotal($(e.currentTarget).closest('.row'));
    updateOverallTotal();
  });
})
input {
  width: 70px;
  text-align-last: center;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

<div style="margin:5%">
  <div class="row">
    <div class="col-4 border border-danger bg-primary">
      <div class="text-center">Section A</div>
    </div>
    <div class="col-4 border border-danger bg-success">
      <div class="text-center">Section B</div>
    </div>
    <div class="col-4 border border-danger bg-secondary">
      <div class="text-center">Grand Total</div>
    </div>
  </div>
  <div class="row">
    <div class="col-4 border border-danger bg-primary">
      <div class="row text-center">
        <div class="col-4">
          <div>Male</div>
        </div>
        <div class="col-4">
          <div>Femle</div>
        </div>
        <div class="col-4">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-4">
          <div><input type="text" class="input male"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="input female"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="total" readonly disabled="disabled"></div>
        </div>
      </div>
    </div>

    <div class="col-4 border border-danger bg-success">
      <div class="row text-center">
        <div class="col-4">
          <div>Male</div>
        </div>
        <div class="col-4">
          <div>Femle</div>
        </div>
        <div class="col-4">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-4">
          <div><input type="text" class="input male"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="input female"></div>
        </div>
        <div class="col-4">
          <div><input type="text" class="total" readonly disabled="disabled"></div>
        </div>
      </div>
    </div>

    <div class="col-4 border border-danger bg-secondary">
      <div class="row text-center">
        <div class="col-4">
          <div>Male</div>
        </div>
        <div class="col-4">
          <div>Femle</div>
        </div>
        <div class="col-4">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-4">
          <div><input type="text" id="overall-male"></div>
        </div>
        <div class="col-4">
          <div><input type="text" id="overall-female"></div>
        </div>
        <div class="col-4">
          <div><input type="text" id="overall-total" readonly disabled="disabled"></div>
        </div>
      </div><br>
    </div><br>
  </div>
</div>

Solution 2:

The change event triggers only on user action. You can either use .trigger is mentioned in the above answer or invoke the final method as below,

$(document).ready(function()
    { 
        $('#maleA,#femaleA').change(function()
        {
            maleA = $('#maleA').val();
            femaleA = $('#femaleA').val();
            allA = Number(maleA)+Number(femaleA);
            $('#totalA').val(allA);
            changeAllTotal();
        }),
        $('#maleB,#femaleB').change(function()
        {
            maleB = $('#maleB').val();
            femaleB = $('#femaleB').val();
            allB = Number(maleB)+Number(femaleB);
            $('#totalB').val(allB);
            changeAllTotal();
        }),

        $('#maleA,#maleB').change(function()
        {
            maleA = $('#maleA').val();
            maleB = $('#maleB').val();
            allmale = Number(maleA)+Number(maleB);
            $('#maletotal').val(allmale);
             changeAllTotal();
        }),
        $('#femaleA,#femaleB').change(function()
        {
        
            femaleA = $('#femaleA').val();
            femaleB = $('#femaleB').val();
            allfemale = Number(femaleA)+Number(femaleB);
            $('#femaletotal').val(allfemale);
            changeAllTotal();
        })
        function changeAllTotal()
        {
            allmale = $('#maletotal').val(); //feed by javascript
            allfemale = $('#femaletotal').val(); //feed by javascript
            alltotal = Number(allmale)+Number(allfemale);
            $('#totalall').val(alltotal);
        }
        
    })

Solution 3:

Admittedly, the following might appear rather condensed. But it shows that the code can be done in a much shorter way enabling it to deal with even wider tables (without the need for ids).

const Asum=DOMArr=>DOMArr.reduce((a,c)=>((a+=+c.value),a),0); // summation utility function
// find arrays of males, females and their summation fields:
const males=$(".male").get(),msum =males.pop(), females=$(".female").get(),fsum=females.pop();
// take care of all summation fields leading to grand total: gtot
let tots = $(".total").get(), gtot=tots.pop();
tots = tots.map(t=>([t,$(t).closest(".row").find("input:not([readonly])").get()]));

$(document).on("input","input",function(){
  [[msum,males],[fsum,females],...tots,[gtot,[msum,fsum]]].forEach(([e,a])=>e.value=Asum(a))
})
input {
  width: 40px;
  text-align-last: center;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

<div style="margin:5%">
  <div class="row">
    <div class="col-3 border border-danger bg-primary">
      <div class="text-center">Section A</div>
    </div>
    <div class="col-3 border border-danger bg-success">
      <div class="text-center">Section B</div>
    </div>
    <div class="col-3 border border-danger bg-primary">
      <div class="text-center">Section C</div>
    </div>
    <div class="col-3 border border-danger bg-secondary">
      <div class="text-center">Grand Total</div>
    </div>
  </div>
  <div class="row">
    <div class="col-3 border border-danger bg-primary">
      <div class="row text-center">
        <div class="col-3">
          <div>Male</div>
        </div>
        <div class="col-3">
          <div>Femle</div>
        </div>
        <div class="col-3">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-3">
          <div><input type="text" class="male" id="maleA"></div>
        </div>
        <div class="col-3">
          <div><input type="text" class="female" id="femaleA"></div>
        </div>
        <div class="col-3">
          <div><input type="text" class="total" id="totalA" readonly disabled="disabled"></div>
        </div>
      </div>
    </div>

    <div class="col-3 border border-danger bg-success">
      <div class="row text-center">
        <div class="col-3">
          <div>Male</div>
        </div>
        <div class="col-3">
          <div>Femle</div>
        </div>
        <div class="col-3">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-3">
          <div><input type="text" class="male" id="maleB"></div>
        </div>
        <div class="col-3">
          <div><input type="text" class="female" id="femaleB"></div>
        </div>
        <div class="col-3">
          <div><input type="text" class="total" id="totalB" readonly disabled="disabled"></div>
        </div>
      </div>
    </div>
    
    <div class="col-3 border border-danger bg-primary">
      <div class="row text-center">
        <div class="col-3">
          <div>Male</div>
        </div>
        <div class="col-3">
          <div>Femle</div>
        </div>
        <div class="col-3">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-3">
          <div><input type="text" class="male"></div>
        </div>
        <div class="col-3">
          <div><input type="text" class="female"></div>
        </div>
        <div class="col-3">
          <div><input type="text" class="total" readonly disabled="disabled"></div>
        </div>
      </div>
    </div>

    <div class="col-3 border border-danger bg-secondary">
      <div class="row text-center">
        <div class="col-3">
          <div>Male</div>
        </div>
        <div class="col-3">
          <div>Femle</div>
        </div>
        <div class="col-3">
          <div>Total</div>
        </div>
      </div>
      <div class="row text-center">
        <div class="col-3">
          <div><input type="text" class="male" id="maletotal" readonly disabled="disabled"></div>
        </div>
        <div class="col-3">
          <div><input type="text" class="female" id="femaletotal" readonly disabled="disabled"></div>
        </div>
        <div class="col-3">
          <div><input type="text" class="total" id="totalall" readonly disabled="disabled"></div>
        </div>
      </div><br>
    </div><br>
  </div>
</div>