JavaScript: получить количество отредактированных/обновленных входных данных

сценарий

Каждый семестр моим студентам нужно сдавать хотя бы один тест по естественным наукам, физике и истории. Следующая форма дает правильные средние оценки, а также итоговую оценку студента:

document.getElementById('calcBtn').addEventListener('click', function() {
  var scienceTest1 = document.getElementById('scienceTest1').value;
  var scienceTest2 = document.getElementById('scienceTest2').value;
  var scienceTest3 = document.getElementById('scienceTest3').value;
  var physicsTest1 = document.getElementById('physicsTest1').value;
  var physicsTest2 = document.getElementById('physicsTest2').value;
  var physicsTest3 = document.getElementById('physicsTest3').value;
  var historyTest1 = document.getElementById('historyTest1').value;
  var historyTest2 = document.getElementById('historyTest2').value;
  var historyTest3 = document.getElementById('historyTest3').value;
  var scienceAverage = document.getElementById('scienceAverage');
  var physicsAverage = document.getElementById('physicsAverage');
  var historyAverage = document.getElementById('historyAverage');
  var finalGrade = document.getElementById('finalGrade');
  scienceAverage.value = (Number(scienceTest1) + Number(scienceTest2) + Number(scienceTest3)) / 3;
  physicsAverage.value = (Number(physicsTest1) + Number(physicsTest2) + Number(physicsTest3)) / 3;
  historyAverage.value = (Number(historyTest1) + Number(historyTest2) + Number(historyTest3)) / 3;
  finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
});
<form>
  Science: <input type="number" id="scienceTest1">
  <input type="number" id="scienceTest2">
  <input type="number" id="scienceTest3">
  <output id="scienceAverage"></output>
  <br> Physics: <input type="number" id="physicsTest1">
  <input type="number" id="physicsTest2">
  <input type="number" id="physicsTest3">
  <output id="physicsAverage"></output>
  <br> History: <input type="number" id="historyTest1">
  <input type="number" id="historyTest2">
  <input type="number" id="historyTest3">
  <output id="historyAverage"></output>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
  <output id="finalGrade"></output>
</form>

Ответ 1

Похоже, вам нужно проверить значения входных данных действительными числами, прежде чем использовать их в арифметике, которая вычисляет средние значения для каждого курса. Один из способов сделать это - выполнить следующую проверку:

if (!Number.isNaN(Number.parseFloat(input.value))) {
  /* Use input.value in average calculation */
}

Вы также можете изменить свой скрипт и HTML, как показано ниже, что позволит вам обобщить и повторно использовать среднее вычисление для каждого из трех классов, как описано ниже:

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

  /* Generalise the calculation of updates for specified course type */
  const calculateForCourse = (cls) => {

    let total = 0
    let count = 0

    /* Select inputs with supplied cls selector and iterate each element */
    for (const input of document.querySelectorAll('input.${cls}')) {

      if (!Number.isNaN(Number.parseFloat(input.value))) {
      
        /* If input value is non-empty, increment total and count for
        subsequent average calculation */
        total += Number.parseFloat(input.value);
        count += 1;
      }
    }

    /* Cacluate average and return result */
    return { count, average : count > 0 ? (total / count) : 0 }
  }

  /* Calculate averages using shared function for each class type */
  const calcsScience = calculateForCourse('science')
  const calcsPhysics = calculateForCourse('physics')
  const calcsHistory = calculateForCourse('history')
  
  /* Update course averages */
  document.querySelector('output.science').value = calcsScience.average
  document.querySelector('output.physics').value = calcsPhysics.average
  document.querySelector('output.history').value = calcsHistory.average
  
  /* Update course counts */
  document.querySelector('span.science').innerText = 'changed:${calcsScience.count}'
  document.querySelector('span.physics').innerText = 'changed:${calcsPhysics.count}'
  document.querySelector('span.history').innerText = 'changed:${calcsHistory.count}'

  /* Update final grade */
  var finalGrade = document.getElementById('finalGrade');

  finalGrade.value = (calcsScience.average * 5 + calcsPhysics.average * 3 + calcsHistory.average * 2) / 10;
});
<!-- Add class to each of the course types to allow script to distinguish
     between related input and output fields -->
<form>
  Science:
  <input type="number" class="science" id="scienceTest1">
  <input type="number" class="science" id="scienceTest2">
  <input type="number" class="science" id="scienceTest3">
  <output id="scienceAverage" class="science"></output>
  <span class="science"></span>
  <br> Physics:
  <input type="number" class="physics" id="physicsTest1">
  <input type="number" class="physics" id="physicsTest2">
  <input type="number" class="physics" id="physicsTest3">
  <output id="physicsAverage" class="physics"></output>
  <span class="physics"></span>
  <br> History:
  <input type="number" class="history" id="historyTest1">
  <input type="number" class="history" id="historyTest2">
  <input type="number" class="history" id="historyTest3">
  <output id="historyAverage" class="history"></output>
  <span class="history"></span>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
  <output id="finalGrade"></output>
</form>

Ответ 2

Хорошим началом является изменение вашего идентификатора на класс, чтобы поместить ваши входные данные в логические группы. Следующим шагом является получение входных данных от определенной группы, значение которой не равно нулю. Мы можем сделать это, выбрав, например, .scienceTest а затем отфильтровав пустые строковые элементы.

Я добавил values вспомогательной функции, чтобы извлечь значения из списка узлов и поместить их в обычный массив.

Мы можем использовать Boolean для проверки пустых строк. Мы также приводим все строки к числам, используя Number. Это делается в функции onlyNumbers.

Далее нам нужно рассчитать средние значения для каждой группы. Это легко, так как у нас есть отфильтрованный список чисел. Все, что мы делаем, это вычисляем сумму и делим на длину массива. Это делается с помощью нашей маленькой функции avrg.

 

document.getElementById('calcBtn').addEventListener('click', function() {
  var scienceTest = getGrades('.scienceTest')
  var physicsTest = getGrades('.physicsTest')
  var historyTest = getGrades('.historyTest')
  
  var scienceAverage = document.getElementById('scienceAverage');
  var physicsAverage = document.getElementById('physicsAverage');
  var historyAverage = document.getElementById('historyAverage');
  
  var finalGrade = document.getElementById('finalGrade');
  
  scienceAverage.value = avrg(scienceTest)
  physicsAverage.value = avrg(physicsTest)
  historyAverage.value = avrg(historyTest)
  
  finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
  
});

function avrg(list) {
	return list.length ? list.reduce((acc, i) => acc + i, 0) / list.length : 0
}

function getGrades(selector) {
	return onlyNumbers(values(document.querySelectorAll(selector)))
}
function onlyNumbers(list) {
		return list.filter(Boolean).map(Number)
}

function values(nodelist) {
		return Array.prototype.map.call(nodelist, (node) => node.value)
}
<form>
  Science: <input type="number" class="scienceTest">
  <input type="number" class="scienceTest">
  <input type="number" class="scienceTest">
  <output id="scienceAverage"></output>
  <br> Physics: <input type="number" class="physicsTest">
  <input type="number" class="physicsTest">
  <input type="number" class="physicsTest">
  <output id="physicsAverage"></output>
  <br> History: <input type="number" class="historyTest">
  <input type="number" class="historyTest">
  <input type="number" class="historyTest">
  <output id="historyAverage"></output>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
  <output id="finalGrade"></output>
</form>

Ответ 3

Вместо того, чтобы все время делить его на 3, вы можете динамически рассчитать это число на основе количества полей ввода, обновленных студентом подряд.

Вот рабочий код:

function getValueAndTotal(element){
  var valueChanged = (element.defaultValue === element.value || element.value === "") ? 0 : 1;  
  return { value: Number(element.value), total: valueChanged };
}

document.getElementById('calcBtn').addEventListener('click', function() {
  var scienceTest1 = getValueAndTotal(document.getElementById('scienceTest1'));
  var scienceTest2 = getValueAndTotal(document.getElementById('scienceTest2'));
  var scienceTest3 = getValueAndTotal(document.getElementById('scienceTest3'));

  var physicsTest1 = getValueAndTotal(document.getElementById('physicsTest1'));
  var physicsTest2 = getValueAndTotal(document.getElementById('physicsTest2'));
  var physicsTest3 = getValueAndTotal(document.getElementById('physicsTest3'));

  var historyTest1 = getValueAndTotal(document.getElementById('historyTest1'));
  var historyTest2 = getValueAndTotal(document.getElementById('historyTest2'));
  var historyTest3 = getValueAndTotal(document.getElementById('historyTest3'));

  var scienceAverage = document.getElementById('scienceAverage');
  var physicsAverage = document.getElementById('physicsAverage');
  var historyAverage = document.getElementById('historyAverage');

  var finalGrade = document.getElementById('finalGrade');
  var scienceTotalTests = scienceTest1.total + scienceTest2.total + scienceTest3.total;
  var physicsTotalTests = physicsTest1.total + physicsTest2.total + physicsTest3.total;
  var historyTotalTests = historyTest1.total + historyTest2.total + historyTest3.total;

  scienceAverage.value = (scienceTotalTests === 0 ? 0 : (scienceTest1.value + scienceTest2.value + scienceTest3.value) / scienceTotalTests);
  physicsAverage.value = (physicsTotalTests === 0 ? 0 : (physicsTest1.value + physicsTest3.value + physicsTest3.value) / physicsTotalTests);
  historyAverage.value = (historyTotalTests === 0 ? 0 : (historyTest1.value + historyTest2.value + historyTest3.value) / historyTotalTests);

  finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
});
<form>
  Science: 
    <input type="number" id="scienceTest1" class="scienceTest">
    <input type="number" id="scienceTest2" class="scienceTest">
    <input type="number" id="scienceTest3" class="scienceTest">
    <output id="scienceAverage"></output>
  <br>Physics: 
    <input type="number" id="physicsTest1">
    <input type="number" id="physicsTest2">
    <input type="number" id="physicsTest3">
    <output id="physicsAverage"></output>
  <br>History: 
    <input type="number" id="historyTest1">
    <input type="number" id="historyTest2">
    <input type="number" id="historyTest3">
    <output id="historyAverage"></output>
  <br>
    <input type="button" value="Calculate" id="calcBtn">
    <output id="finalGrade"></output>
</form>

Ответ 4

Это немного некрасиво, но вы можете считать результаты тестов булевыми значениями: если есть какой-либо результат теста, который стоит 1, в противном случае - 0.

Поскольку input.value имеет тип string, преобразование его в логическое значение даст в результате значение false если входное значение пустое (""), или значение true если в нем есть любое число.

Используя OP меньший фрагмент:

document.getElementById('calcBtn').addEventListener('click', function() {
  var test1 = document.getElementById('test1').value;
  var test2 = document.getElementById('test2').value;
  var test3 = document.getElementById('test3').value;
  var testCount = Boolean(test1) + Boolean(test2) + Boolean(test3);
  // alternatively: var testCount = !!test1 + !!test2 + !!test3

  var average = document.getElementById('average');
  average.value = (Number(test1) + Number(test2) + Number(test3)) / testCount;
});
<form>
  <input type="number" id="test1">
  <input type="number" id="test2">
  <input type="number" id="test3">
  <output id="average"></output>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
</form>

Ответ 5

В вашем коде есть два основных момента, о которых вы должны знать:

  1. Вы рассчитываете средние по каждому предмету независимо от их стоимости. Технически, вы хотите принять во внимание предмет, только если он имеет определенное value. В этом случае 0 будет учитываться, но пустое поле не будет (так как студент может технически забить 0 на его/ее испытание)
  2. Вы также вычисляете средневзвешенное значение независимо от их значения (см. Ту же логику, что и выше).

Вместо того, чтобы пытаться исправить ваш код, я на самом деле реорганизовал логику, чтобы все вычисления были абстрагированы в функции, основанные на принципе СУХОЙ (не повторяйтесь). Функции:

  • calculateSubjectAverage, который вычисляет правильное среднее значение для данного предмета. Это будет учитывать 0, но игнорировать пустые поля
  • setSubjectAverage, который установит соответствующий элемент <output>

Наконец, вместо того, чтобы вручную вычислять взвешенное среднее, вы можете легко хранить все эти метаданные в массиве объектов, например:

var subjects = [{
  name: 'science',
  weight: 5
}, {
  name: 'physics',
  weight: 3
}, {
  name: 'history',
  weight: 2
}];

Это позволяет нам фильтровать subjects и вычислять их правильную взвешенную сумму и, следовательно, средневзвешенное значение. Фильтрация необходима, потому что есть вероятность, что полностью пустой предметный балл вернется undefined.

См. Подтверждение концепции ниже:

function calculateSubjectAverage(className) {
  var inputs = document.querySelectorAll('.' + className);
  var scores = Array.prototype.map.call(inputs, function(input) {
    if (input.value === '')
      return;

    return +input.value;
  });

  var count = 0;
  var scoreSum = scores.reduce(function(acc, score) {
    if (isNaN(score))
      return acc;

    count++;
    return acc + score;
  }, 0);
  
  return scoreSum / count;
};

function setSubjectAverage(className, averageScore) {
  if (isNaN(averageScore))
    return;

  document.getElementById(className + 'Average').value = averageScore;
}

document.getElementById('calcBtn').addEventListener('click', function() {
  var subjects = [{
    name: 'science',
    weight: 5
  }, {
    name: 'physics',
    weight: 3
  }, {
    name: 'history',
    weight: 2
  }];
  
  var totalWeight = 0;

  // Go through each subject and calculate & set average score
  // Since we are iterating anyway, might want to calculate totalWeight, too
  subjects.forEach(function(subject) {
    var averageScore = calculateSubjectAverage(subject.name);
    setSubjectAverage(subject.name, averageScore);
    
    // Set average score to object
    subject.average = averageScore;
    
    if (!isNaN(averageScore))
      totalWeight += subject.weight;
  });
  
  // Only compute weighted average from subject with valid averages
  var weightedTotal = subjects.reduce(function(acc, subject) {
    if (isNaN(subject.average))
      return acc;
      
    return acc + subject.average * subject.weight;
  }, 0);
  var weightedAverage = weightedTotal / totalWeight;
  if (!isNaN(weightedTotal / totalWeight))
    document.getElementById('finalGrade').value = weightedTotal / totalWeight;
});
<form>
  Science: <input type="number" class="science">
  <input type="number" class="science">
  <input type="number" class="science">
  <output id="scienceAverage"></output>
  <br> Physics: <input type="number" class="physics">
  <input type="number" class="physics">
  <input type="number" class="physics">
  <output id="physicsAverage"></output>
  <br> History: <input type="number" class="history">
  <input type="number" class="history">
  <input type="number" class="history">
  <output id="historyAverage"></output>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
  <output id="finalGrade"></output>
</form>

Ответ 6

var tests = [
    document.getElementById('test1').value || false,
    document.getElementById('test2').value || false,
    document.getElementById('test3').value || false
];

var average = 0,
    length = 0;

for (var i = 0; i < tests.length; i++) {
    if (tests[i] !== false) {
        average += Number( tests[i] );
        length ++;
    }
}

average = average / length;

Это решение ES5. Можно сделать короче, но это, на мой взгляд, интуитивно понятно.

Ответ 7

Вы делите значение на 3, поэтому оно дает меньший результат, чем ожидалось.

  • Создайте HTML-код вашего кода динамически.
  • Не scienceTest1,scienceTest2..... слишком много переменных (scienceTest1,scienceTest2.....), вместо этого используйте циклы, которые хранят значения в array
  • Писать вот так: Number(scienceTest1) + Number(scienceTest2) + Number(scienceTest3))/3 - плохо, потому что у вас может быть больше тестов и больше шансов на ошибку типа. Вместо этого сохраните значения в массиве и в конце используйте Array.prototype.reduce() чтобы добавить их.
  • Для массива значений вам нужно проверить, если value !=='' перед тем, как вставить его в массив, чтобы оно получило правильное среднее значение.

Код полностью динамический, вы можете иметь любые предметы и любые тесты

//This is list of subjects. You can change it will work same
let subjects = ['science','physics','history'];
let noOfTests = 3;
//add <form> element to body
document.body.innerHTML = '<form></form>'
//getting that form as an element.
let form = document.querySelector('form')

//Creating the HTML dymamically

subjects.forEach(sub =>{
   //setting the title of the subject
   form.innerHTML += sub + ':' + '<br>'; 
   for(let i = 0;i<noOfTests;i++){
     //generating input feilds equal of 'noOfTests' for each subject
     form.innerHTML += '<input type="number" id="${sub}Test${i+1}" /><br>'
   }
   //adding the output element to after addign all inputs.
   form.innerHTML += '<output id="${sub}Average"></output><br>' 
})
//Adding calculate button and finalOuput element.
form.innerHTML += '<br><input type="button" value="Calculate" id="calcBtn">
  <output id="finalGrade"></output>'



document.getElementById('calcBtn').addEventListener('click', function() {
  //'total' is array which will contain average of each subject
  let total = [];
  //looping thorugh each subject in 'subjects' array.
  subjects.forEach(sub => {
    //'vals' will store the values currect subject we are looping
    let vals = []
    
    for(let i = 0;i<noOfTests;i++){
      //getting the value of each input feild of current subject
      
      let val = document.getElementById('${sub}Test${i+1}').value;
      //check if input have a value so we push it into the vals array.
      if(val !== '') vals.push(val);
    }
    //getting average of all values using reduce
    let result = vals.reduce((ac,a) => ac + Number(a),0)/vals.length;
    //adding result(average) to the output of current subject.
    document.getElementById('${sub}Average').innerHTML = result
    //adding the average of current subject of the 'total' array.
    total.push(result);
  })
  //At last find the average of total averages and add it to 'finalGrade'
  total = total.filter(x => !isNaN(x));
  
  document.getElementById('finalGrade').innerHTML = total.reduce((ac,a) => ac + a,0)/total.length;
});
input{
  border-radius:5px;
  padding:3px;
  margin:5px;
  font-size:20px;
}
form{
  font-size:20px;
  font-family:sans-serif;
  text-transform:capitalize;
}

Ответ 8

Введите счетчик для функции. После каждого клика проверяйте, являются ли входные значения '' или нет, поскольку это будет строка перед приведением. Если это ничего не делать, троичная операция вернет false

 test1!=''?num++:false;

Если test1=='' то false, иначе счетчик увеличивается. Перед вычислением среднего значения проверьте, равен ли счетчик 0, установлен ли он на единицу. Если он равен нулю, результатом деления на ноль будет бесконечность, и мы получим NaN в качестве выхода, а счетчик, установленный в 1, даст 0.

document.getElementById('calcBtn').addEventListener('click', function() {
let num=0;
  var test1 = document.getElementById('test1').value;
  test1!=''?num++:false;
  var test2 = document.getElementById('test2').value;
  test2!=''?num++:false;
  var test3 = document.getElementById('test3').value;
  test3!=''?num++:false;
  var average = document.getElementById('average');
  num==0?num++:false;
  average.value = (Number(test1) + Number(test2) + Number(test3)) / num;
});
<form>
  <input type="number" id="test1">
  <input type="number" id="test2">
  <input type="number" id="test3">
  <output id="average"></output>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
</form>

Ответ 9

Уже есть несколько решений. Вот мой.

В вашем коде есть что оптимизировать, и я думаю, что это хорошая идея - динамически создавать HTML на основе простого массива конфигурации, такого как

const subjects = [{
  name: 'science',
  numberOfTests: 3
}, {
  name: 'physics',
  numberOfTests: 2
}, {
  name: 'history',
  numberOfTests: 3
}];

поэтому, если изменяются темы или количество тестов, вам не нужно менять код, а только эту конфигурацию. Я попытался прокомментировать мой пример кода, чтобы понять, что там делается. Но это не важная часть. Более важно то, что вам нужно понять, что происходит в части расчета, чтобы быть уверенным, что расчет правильный. Итак, я начну с этой части.

Если вы добавите атрибуты имени к своим входным данным результата теста и дадите каждому входу одного и того же субъекта одно и то же имя, вы можете легко получить NodeList этих входных данных, проверить значения для каждого узла и рассчитать на его основе. Итак, вы знаете значения и количество тестов, которые студент сдал по каждому предмету.

Давай посмотрим

/* EventListener for the calculate button */
btn.addEventListener('click', function(e) {
  e.preventDefault(); // don't submit the form
  var totalAvSum = 0; // var for the total of all subject average totals 

  /* for all subjects in your configuration array */
  subjects.forEach(function(subject) {

    /* NodeList of all inputs with Name subject.name+'Test' */
    let subjResInputs = document.getElementsByName(subject.name+'Test');
    let testTotal = 0; // sum of test results
    let testCnt = 0; // number of tests the student took
    let tval; // value of input

    /* for each input of the subject */
    Array.prototype.map.call(subjResInputs, function(t){
      tval = (t.value * 1); // make sure, value is treated as number
      if (tval > 0) { // only if there is a value
        testTotal += tval; // add test result
        testCnt += 1; // increase test count
      }
    });

    /* calculate average and show it in output */
    totalAvSum += (testTotal/testCnt);
    document.getElementById(subject.name+'Average').textContent = (testTotal/testCnt);
  });

  /* after calculating average per subject show total average */
  document.getElementById('totalAverage').textContent = totalAvSum/subjects.length;    
});

Я использую document.getElementsByName() здесь, чтобы получить NodeList элементов ввода для каждого предмета. Затем я использую array.map(), чтобы пройти по этому списку. Чтобы проверить, имеет ли вход значение, я умножаю значение на 1 (что дает число) и вычисляю только, если результат больше 0.

Остальное - динамический материал для HTML.

/* This is your configuration.
   The form will be created based on that configuration.
   So you don't need to change anything in the code if 
   subjects or number of tests change. */
const subjects = [{
    name: 'science',
    numberOfTests: 3
  }, {
    name: 'physics',
    numberOfTests: 2
  }, {
    name: 'history',
    numberOfTests: 3
  }];

/* this functioncreates the form table */
function createFormTable() {
  var tr, td, txt, outp, btn, frmTbl;
    // frmTbl = document.getElementById('formTable');

  /* create table */
  frmTbl = document.createElement('table');
  frmTbl.setAttribute('id', 'formTable'); // set id to 'formTable'

  /* create table head */
  tr = document.createElement('tr');
  td = document.createElement('th');
  txt = document.createTextNode('subject');
  td.appendChild(txt);
  tr.appendChild(td);

  td = document.createElement('th');
  txt = document.createTextNode('test results');
  td.appendChild(txt);
  tr.appendChild(td); 

  td = document.createElement('th');
  txt = document.createTextNode('arith. mean');
  td.appendChild(txt);
  tr.appendChild(td);  

  /* add table head to table */
  frmTbl.appendChild(tr);

  /* create table row for each subject 
     the table row object is created in function createSubjectRow
     and here added to the table */
  subjects.forEach(function(subject) {
    frmTbl.appendChild(createSubjectRow(subject)); // add tr to table
  });

  /* row with total average */
  /* create tr element */
  tr = document.createElement('tr');

  td = document.createElement('th'); // td for text total
  td.setAttribute('colspan', 2);
  td.style.textAlign = 'right';
  txt = document.createTextNode('total'); // textNode
  td.appendChild(txt); // add textNode to td
  tr.appendChild(td); // add td to tr
  frmTbl.appendChild(tr); // add tr to table

  td = document.createElement('td'); // td for total average output  
  outp = document.createElement('output'); // create output element
  outp.setAttribute('id', 'totalAverage'); // set id
  td.appendChild(outp); // add output to td
  tr.appendChild(td); // add td to tr
  frmTbl.appendChild(tr); // add tr to table

  /* button */
  btn = document.createElement('button');
  btn.setAttribute('id', 'calcBtn');
  txt = document.createTextNode('calculate');
  btn.appendChild(txt);
  // document.getElementById('gradesForm').appendChild(btn);

  /* add button to last row in table */
  tr = document.createElement('tr');
  td = document.createElement('th'); // td for button
  td.setAttribute('colspan', 3);
  td.appendChild(btn); // add button to td
  tr.appendChild(td); // add td to tr
  frmTbl.appendChild(tr); // add tr to table

  /* EventListener for the calculate button */
  btn.addEventListener('click', function(e) {
    e.preventDefault(); // don't submit the form
    var totalAvSum = 0; // var for the total of all subject average totals 

    /* for all subjects in your configuration array */
    subjects.forEach(function(subject) {
      /* NodeList of all inputs with Name subject.name+'Test' */
      let subjResInputs = document.getElementsByName(subject.name+'Test');
      let testTotal = 0;
      let testCnt = 0;
      let tval;
      /* for each input of the subject */
      Array.prototype.map.call(subjResInputs, function(t){
        tval = (t.value * 1); // make sure, value is treated as number
        if (tval > 0) { // only if there is a value
          testTotal += tval; // add test result
          testCnt += 1; // increase test count
        }
      });
      /* calculate average and show it in output */
      totalAvSum += (testTotal/testCnt);
      document.getElementById(subject.name+'Average').textContent = (testTotal/testCnt);
    });
    /* after calculating average per subject
       show total average */
    document.getElementById('totalAverage').textContent = totalAvSum/subjects.length;    
  });

  return frmTbl;
}

function createSubjectRow(s) {
  var tr, td, txt, inp, outp; 

  /* create tr element */
  tr = document.createElement('tr');

  /* create td elements for subject s */
  td = document.createElement('td'); // td for subject name
  txt = document.createTextNode(s.name); // textNode
  td.appendChild(txt); // add textNode to td
  tr.appendChild(td); // add td to tr

  td = document.createElement('td'); // td for subject test results
  for (var i = 0; i < s.numberOfTests; i += 1) {
    inp = document.createElement('input'); // create input
    inp.setAttribute('type', 'number');  // set input type
    // inp.setAttribute('id', s.name + 'Test' + i); // set id
    /* set name attribute of input to subject name + 'Test'
       all test result inputs for the same subject will have the same name */
    inp.setAttribute('name', s.name + 'Test');
    inp.setAttribute('step', 0.1); // in case, you give grades like 3.5
    inp.setAttribute('min', 1);
    inp.setAttribute('max', 100);
    td.appendChild(inp); // add input to td
  }
  tr.appendChild(td); // add td to tr

  td = document.createElement('td'); // td for average output  
  outp = document.createElement('output'); // create output element
  outp.setAttribute('id', s.name + 'Average'); // set id
  td.appendChild(outp); // add output to td
  tr.appendChild(td); // add td to tr

  return tr; // return the resulting table row object
}

document.getElementById('gradesForm').appendChild(createFormTable());
  #formTable td {
    border: solid 1px #000;
    padding: 6px;
    border-spacing: 3px;
  }
  #formTable th {
    border: none;
    font-size:0.9em;
    text-align: left;
  }
  input[type="number"] {
    width: 4em;
    border: solid 1px #999;
    margin: 0 3px;
  }
<form id="gradesForm">
</form>

Ответ 10

Вы можете создать объект под названием tests = {} и сохранить свои тесты в виде массива внутри него, чтобы привести пример. Я написал некоторый код только для одной темы ниже, которую вы можете изменить позже.

Кроме того, я использовал классы вместо идентификаторов, чтобы уменьшить сложность кода и повторяемость.

Обновление: обновлен код ниже, чтобы быть более динамичным и гибким для нескольких предметов.

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

  // create a tests object and have subject specific test inside it
  var tests = {
    "Science": [],
    "Physics": [],
    "History": []
  };

  //looping over all subjects 
  for (var key in tests) {

    // this can be looped as well if you have multiple subjects
    var test = document.getElementsByClassName(key);
    for (i = 0; i < test.length; i++) {
      if (test[i].value != null || test[i].value != "") {
        tests[key].push(Number(test[i].value));
      }
    }

    // populate average by using reduce function 
    document.getElementById(key).value = tests[key].reduce((prev, curr) => prev + curr) / tests[key].length;
  }
});
<form>
  <fieldset>
    <legend>Science</legend>
    <input type="number" class="Science">
    <input type="number" class="Science">
    <input type="number" class="Science">
    <output id="Science"></output>
  </fieldset>

  <fieldset>
    <legend>Physics</legend>
    <input type="number" class="Physics">
    <input type="number" class="Physics">
    <input type="number" class="Physics">
    <output id="Physics"></output>
  </fieldset>

  <fieldset>
    <legend>History</legend>
    <input type="number" class="History">
    <input type="number" class="History">
    <input type="number" class="History">
    <output id="History"></output>
  </fieldset>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
</form>

Ответ 11

метод

Проблему деления на фиксированное число 3 можно решить с помощью свойства Длина массива и сделать это значение динамическим.

Шаг 1: Поместите все объекты в массивы и отфильтруйте, если входные значения имеют значения.

Шаг 2: Получить среднее значение по предметам.

Шаг 3: Рассчитайте итоговую оценку с весами.

Есть две функции, которые используются несколько раз. isTruthy и average.

Чтобы уменьшить среднюю функцию, я разбил ее на sum и average

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

  // Helper Functions

  function isTruthy (score) {
    return !!score
  }
  
  function sum (scores) {
    var total = 0;
    for (var counter=0; counter<scores.length; counter++) {
      total += (Number(scores[counter]) || 0);
    }
    return total
  }
  
  function average (scores) {
    return (sum(scores) / scores.length) || 0
  }
  
  // Step 1
  var scienceScores = [
    document.getElementById('scienceTest1').value,
    document.getElementById('scienceTest2').value,
    document.getElementById('scienceTest3').value
  ].filter(isTruthy)
  
  var physicsScores = [
    document.getElementById('physicsTest1').value,
    document.getElementById('physicsTest2').value,
    document.getElementById('physicsTest3').value
  ].filter(isTruthy)
  
  var historyScores = [
    document.getElementById('historyTest1').value,
    document.getElementById('historyTest2').value,
    document.getElementById('historyTest3').value
  ].filter(isTruthy)
  
  var scienceAverage = document.getElementById('scienceAverage');
  var physicsAverage = document.getElementById('physicsAverage');
  var historyAverage = document.getElementById('historyAverage');
  
  var finalGrade = document.getElementById('finalGrade');
  
  // Step 2
  scienceAverage.value = average(scienceScores);
  physicsAverage.value = average(physicsScores);
  historyAverage.value = average(historyScores);
  
  // Step 3
  finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
});
<form>
  Science: <input type="number" id="scienceTest1">
  <input type="number" id="scienceTest2">
  <input type="number" id="scienceTest3">
  <output id="scienceAverage"></output>
  <br> Physics: <input type="number" id="physicsTest1">
  <input type="number" id="physicsTest2">
  <input type="number" id="physicsTest3">
  <output id="physicsAverage"></output>
  <br> History: <input type="number" id="historyTest1">
  <input type="number" id="historyTest2">
  <input type="number" id="historyTest3">
  <output id="historyAverage"></output>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
  <output id="finalGrade"></output>
</form>

Ответ 12

Какой простой подход для получения количества измененных полей ввода

Мы можем назначить каждому input собственный атрибут data равный input значению, а затем исключить пустые при их подсчете. Следующий универсальный скрипт может использоваться для любого количества курсов:

var form = document.querySelector('form');

function calculateAverage(fieldset) {
  var total = 0;
  var inputs = fieldset.querySelectorAll('input');
  for (var input of inputs) {
    total += Number(input.value);
    input.dataset.value = input.value;
  }
  return total / fieldset.querySelectorAll('input:not([data-value=""])').length;
}

function displayAverages() {
  var fieldsets = form.querySelectorAll('fieldset');
  for (var fieldset of fieldsets) {
    var avg = calculateAverage(fieldset);
    var output = fieldset.querySelector('output');
    if (isNaN(avg)) {
      output.value = 'Please enter a grade.';
    } else {
      output.value = 'Average: ' + avg.toFixed(1);
    }
  }
}

form.querySelector('button').addEventListener('click', displayAverages);
body {
  display: flex;
}

fieldset {
  margin: 0 0 16px;
}

input {
  width: 4em;
}

output {
  display: block;
  height: 1em;
  margin: 8px 0 0 2px;
}
<form>
  <fieldset>
    <legend>Physics</legend>
    <input type="number">
    <input type="number">
    <input type="number">
    <output></output>
  </fieldset>
  <fieldset>
    <legend>History</legend>
    <input type="number">
    <input type="number">
    <input type="number">
    <output></output>
  </fieldset>
  <button type="button">Calculate</button>
</form>

Ответ 13

document.getElementById('calcBtn').addEventListener('click', function() {
var testcount = [];
var count = 0;
testcount = Array.prototype.slice.call(document.getElementsByClassName('test1'))
for(var i=0;i<testcount.length;i++)
{
 if(Number(testcount[i].value) > 0)
 {
  count=count+1;
  }
 
}
  var test1 = document.getElementById('test1').value;
  var test2 = document.getElementById('test2').value;
  var test3 = document.getElementById('test3').value;
  var average = document.getElementById('average');
  average.value = (Number(test1) + Number(test2) + Number(test3)) / count;
});
<form>
  <input type="number" class="test1" id="test1">
  <input type="number" class="test1" id="test2">
  <input type="number" class="test1" id="test3">
  <output id="average"></output>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
</form>

Ответ 14

Моя рекомендация состоит в том, чтобы избежать нескольких полей ввода для чтения в нескольких вводах. В моем случае я использую точки с запятой для разделения отдельных значений внутри поля ввода. Таким образом, я могу ввести столько значений, сколько захочу (хотя бы одно значение). Поэтому моя форма выглядит следующим образом:

<!-- form.html -->
<form>
  Science: <input type="text" id="scienceTest">
  <output id="scienceAverage"></output>
  <br> Physics: <input type="text" id="physicsTest">
  <output id="physicsAverage"></output>
  <br> History: <input type="text" id="historyTest">
  <output id="historyAverage"></output>
  <br>
  <input type="button" value="Calculate" id="calcBtn">
  <output id="finalGrade"></output>
</form>
<script src="script.js"></script>

И мой JavaScript выглядит так:

// script.js
(function() {
  var scienceTest = document.getElementById('scienceTest');
  var physicsTest = document.getElementById('physicsTest');
  var historyTest = document.getElementById('historyTest');
  var scienceAverage = document.getElementById('scienceAverage');
  var physicsAverage = document.getElementById('physicsAverage');
  var historyAverage = document.getElementById('historyAverage');
  var finalGrade = document.getElementById('finalGrade');

  function sumArray(sum, item) {
    return sum + item;
  }

  document.getElementById('calcBtn').addEventListener('click', function() {
    // fetch the string of the input and split into its separate numbers
    var scienceGradeStrings = scienceTest.value.split(";");
    var physicsGradeStrings = physicsTest.value.split(";");
    var historyGradeStrings = historyTest.value.split(";");

    // calculate the averages
    scienceAverage.value = scienceGradeStrings
      // convert the grades from strings to numbers
      .map(Number)
      // sum all grades together
      .reduce(sumArray, 0)
      // calculate the average grade
      / scienceGradeStrings.length;
    physicsAverage.value = physicsGradeStrings.map(Number).reduce(sumArray, 0) / physicsGradeStrings.length;
    historyAverage.value = historyGradeStrings.map(Number).reduce(sumArray, 0) / historyGradeStrings.length;
    finalGrade.value = (scienceAverage.value * 5 + physicsAverage.value * 3 + historyAverage.value * 2) / 10;
  });
})();