Calculate Final Grade: follow-up I'm creating an online calculator to get the average grade of different tests my students take and also the final grade. I wonder if you see any problems in this update or any way to improve it:



var inputs = document.getElementsByTagName('input');
var i;

function calculateAverage(tests) {
  var total = 0;
  var count = 0;
  for (i = 0; i &lt; tests.length; i++) {
    if (tests[i].value) {
      total += Number(tests[i].value);
      count++;
    }
  }
  if (!count) {
    return 'Please enter a grade.';
  }
  return 'Average: ' + (total / count).toFixed(1);
}
document.getElementById('calculator').addEventListener('click', function() {
  var physicsTests = document.getElementById('physics').getElementsByTagName('input');
  var physicsAverage = document.getElementById('physicsAverage');
  physicsAverage.value = calculateAverage(physicsTests);

  var historyTests = document.getElementById('history').getElementsByTagName('input');
  var historyAverage = document.getElementById('historyAverage');
  historyAverage.value = calculateAverage(historyTests);

  var finalGrade = document.getElementById('finalGrade');
  var averagesTotal = physicsAverage.value.slice(9) * 3 + historyAverage.value.slice(9) * 2;
  if (averagesTotal || averagesTotal == 0) {
    finalGrade.value = 'Final grade:' + (averagesTotal / 5).toFixed(1);
  } else {
    finalGrade.value = '';
  }
});
document.getElementById('resetter').addEventListener('click', function() {
  var form = document.getElementById('form');
  var edited = calculateAverage(inputs);
  if (edited != 'Please enter a grade.' &amp;&amp; confirm('Your changes will be lost.\nAre you sure you want to reset?')) {
    form.reset();
  }
});
window.addEventListener('beforeunload', function(event) {
  var edited = calculateAverage(inputs);
  if (edited != 'Please enter a grade.') {
    event.returnValue = 'Your changes may be lost.';
  }
});
&lt;form id="form"&gt;
  &lt;p id="physics"&gt;
    Physics:
    &lt;input type="number"&gt;
    &lt;input type="number"&gt;
    &lt;input type="number"&gt;
    &lt;output id="physicsAverage"&gt;&lt;/output&gt;
  &lt;/p&gt;
  &lt;p id="history"&gt;
    History:
    &lt;input type="number"&gt;
    &lt;input type="number"&gt;
    &lt;input type="number"&gt;
    &lt;output id="historyAverage"&gt;&lt;/output&gt;
  &lt;/p&gt;
  &lt;button type="button" id="calculator"&gt;Calculate&lt;/button&gt;
  &lt;button type="button" id="resetter"&gt;Reset&lt;/button&gt;
  &lt;output id="finalGrade"&gt;&lt;/output&gt;
&lt;/form&gt;




P.S. I've made changes to the previous code to shorten the script and added some new features such as a reset button.

Note

Please review the final product made thanks to the hints I've received in this thread.
I went ahead and refactored your code a lot. However, before I get into that, lets talk about what you did and what you could have done differently.

Feel free to let me know if you want me to expand on something specific.

Logic regarding: function calculateAverage(tests) {}

Calculate average function should not take in parameters a list of HTMLInputElements and should not return a string.

Why? Well it doesn't make sense. calculateAverage should take a list of values and calculate an average.



function calculateAverage(values){
  return values.reduce((a,c)=&gt;a+c,0)/values.length;
}
const avg = calculateAverage([1,2,3]);

console.log(avg);




Because you return a string... later on you have to do this:

physicsAverage.value.slice(9)


Not good ! And can become extremely buggy !

Use querySelectorAll

querySelectorAll is your friend. It allows you to transform this:

document.getElementById('physics').getElementsByTagName('input');


into this:

document.querySelectorAll('#physics &gt; input');


Declare static variables globally:

In your calculator listener callback, you declare a list of variables.

var physicsTests = document.getElementById('physics').getElementsByTagName('input');
var physicsAverage = document.getElementById('physicsAverage');
//etc


But these variables never change. So don't declare them in your callback, move them outside and at the top of your file.

Create generic code !

With the current code you have, a lot of manual labor is needed if you wish to add another subject. In my final solution, everything is built in a way where adding a new subject requires NO changes in the javascript.

To able to do this, use data-* HTML properties. It's super powerful when you need to add some extra info like the coefficients of each subject.



const subjects = document.querySelectorAll("div[data-coef]");

subjects.forEach(subject=&gt;{
  console.log(subject.id, subject.dataset.coef);
});
&lt;div id="physics" data-coef="3"&gt;&lt;/div&gt;
&lt;div id="history" data-coef="2"&gt;&lt;/div&gt;




Another powerful tool is Map. It allows you to easily keep track of specific subjects and other variables. 

Solution:



const inputContainers = document.querySelectorAll('form#form &gt; .subject');

const finalGradeElement = document.getElementById('finalGrade');  
const form = document.getElementById('form');

const m = new Map();

inputContainers.forEach((container,i)=&gt;{
  const output = container.querySelector(".output");
  const inputs = Array.from(container.getElementsByTagName("input"));
  const coef = Number(container.dataset.coef);
  
  const data = Array(inputs.length);
  m.set(i, {
    output, data, coef
  }); 
  
  inputs.forEach((item,j)=&gt;item.addEventListener("input", function(){
    if(!this.value.length &gt; 0){
       data[j] = undefined;
    } else {
       data[j] = Number(this.value);
    }
  }));
});

document.getElementById('calculator')
.addEventListener('click', function() {

  const res = Array.from(m.values()).reduce( (a,{output, data, coef})=&gt;{
  
      const values = data.filter(item=&gt;item!==undefined);
      
      if(values.length &gt; 0){
        const avg = values.reduce((a,c)=&gt;a+c, 0)/values.length;
        a.avgTotal  += avg * coef;
        a.coefTotal += coef;
        
        output.value = `Average: ${avg.toFixed(1)}`;
      } else {
        output.value = "Please enter a number";
      }
      
      return a;
  }, {avgTotal: 0, coefTotal: 0})

  const averagesTotal = res.avgTotal/res.coefTotal;
  
  finalGradeElement.value = `Final Grade: ${averagesTotal.toFixed(1)}`
  
});

function isEdited(){
  return !Array.from(m.values()).every(({data})=&gt;{
    return data.every(i=&gt;i===undefined);
  });
}

document.getElementById('resetter')
.addEventListener('click', function() {
  if (isEdited() &amp;&amp; confirm('Your changes will be lost.\nAre you sure you want to reset?')) {
    form.reset();
    m.clear();
  }
});

window.addEventListener('beforeunload', function(event) {
  if (isEdited()) {
    event.returnValue = 'Your changes may be lost.';
  }
});
form &gt; div {
  display: flex;
  flex-direction: column;
}
&lt;form id="form"&gt;
  &lt;div class="subject" data-coef="3"&gt;
    Physics:
    &lt;input type="number"&gt;
    &lt;input type="number"&gt;
    &lt;input type="number"&gt;
    &lt;output class="output"&gt;&lt;/output&gt;
  &lt;/div&gt;
  &lt;div class="subject" data-coef="2"&gt;
    History:
    &lt;input type="number"&gt;
    &lt;input type="number"&gt;
    &lt;input type="number"&gt;
    &lt;output class="output"&gt;&lt;/output&gt;
  &lt;/div&gt;
  &lt;button type="button" id="calculator"&gt;Calculate&lt;/button&gt;
  &lt;button type="button" id="resetter"&gt;Reset&lt;/button&gt;
  &lt;output id="finalGrade"&gt;&lt;/output&gt;
&lt;/form&gt;