JavaScript: получить количество отредактированных / обновленных входных данных - PullRequest
0 голосов
/ 04 марта 2019

Сценарий

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

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>

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

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;

Вопрос

Какой простой способ получить изменение числа поля ввода в следующей строке?Я попытаюсь понять ваш метод, а затем разрабатываю форму в несколько строк.

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 average = document.getElementById('average');
  average.value = (Number(test1) + Number(test2) + Number(test3)) / 3;
});
<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>

Ответы [ 14 ]

0 голосов
/ 06 марта 2019
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.Вы можете сделать короче, но это, на мой взгляд, интуитивно понятно.

0 голосов
/ 04 марта 2019

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

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

Вместо того, чтобы пытаться исправить ваш код, я фактически реорганизовал логику так, чтобы все вычисления абстрагировались в функции, основанные на DRY (неповторяйся) принцип.Это следующие функции:

  • 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>
0 голосов
/ 04 марта 2019

Хорошим началом является изменение вашего идентификатора на класс, чтобы поместить ваши входные данные в логические группы.Следующим шагом является получение входных данных от определенной группы, значение которой не равно нулю.Мы можем сделать это, выбрав, например, .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>

Обновление: упрощенный пример

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 average = document.getElementById('average');
  // Put all field values in array, Filter empty values out, cast values to Number
  var rowValues = [test1, test2, test3].filter(Boolean).map(Number)

  console.log('Number of changed fields', rowValues.length)

  // calculate average by reducing the array to the sum of its remaining values then divide by array length
  average.value = rowValues.reduce((sum, grade) => sum + grade, 0) / rowValues.length;
});
<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>

Обновление дополнительно: на основе примера jsfiddle ОП в комментариях

document.getElementById('calculator').addEventListener('click', function() {
  var physicsAverage = document.getElementById('physicsAverage'),
    historyAverage = document.getElementById('historyAverage');

  physicsAverage.value = calculateAverageById('physics')
  historyAverage.value = calculateAverageById('history');
});

function calculateAverageById(id) {
	// Get all inputs under Id
  var inputs = document.getElementById(id).getElementsByTagName('input')

  var values =
    Array.prototype.slice.call(inputs) // From HTMLCollection to Array
    .map(e => e.value.trim()) // Return all .value from input elements
    .filter(Boolean) // Filter out any empty strings ""
    .map(Number) // convert remaining values to Numbers
  return (values.length) ? // if length is greater then 0
    values.reduce((sum, grade) => sum + grade, 0) / values.length // Return average
    :
    'No assessment made!' // else return this message
}
    <form>
  <p id="physics">
    Physics:
    <input type="number">
    <input type="number">
    <input type="number">
    <output id="physicsAverage"></output>
  </p>
  <p id="history">
    History:
    <input type="number">
    <input type="number">
    <input type="number">
    <output id="historyAverage"></output>
  </p>
  <button type="button" id="calculator">Calculate</button>
</form>
0 голосов
/ 04 марта 2019

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

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>

Обновление

Чтобы расширить первый ответ, ознакомьтесь с документацией во фрагменте ниже, отвечающей на обновление вашего вопроса:

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 average = document.getElementById('average');
  
  /* This variable counts the number of inputs that have changed */
  var changesDetected = 0;
  
  /* If value of test1 field "not equals" the empty string, then 
  we consider this a "changed" field, so we'll increment our 
  counter variable accordinly */
  if(test1 != '') {
    changesDetected = changesDetected + 1;
  }
  /* Apply the same increment as above for test2 field */
  if(test2 != '') {
    changesDetected = changesDetected + 1;
  }
  /* Apply the same increment as above for test3 field */
  if(test3 != '') {
    changesDetected = changesDetected + 1;
  }
  
  /* Calculate average from changesDetected counter.
  We need to account for the case where no changes
  have been detected to prevent a "divide by zero" */
  if(changesDetected != 0) {
    average.value = (Number(test1) + Number(test2) + Number(test3)) / changesDetected;
  }
  else {
    average.value = 'Cannot calculate average'
  }
  
  /* Show a dialog to box to display the number of fields changed */
  alert("Detected that " + changesDetected + " inputs have been changed")
});
<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>

Обновление 2

Предыдущее Обновление можно упростить с помощью следующего цикла:

document.getElementById('calcBtn').addEventListener('click', function() {
  
  let changesDetected = 0;
  let total = 0;
  const ids = ['test1', 'test2', 'test3'];
  
  for(const id of ids) {
    const value = document.getElementById(id).value;
    if(value != '') {
      changesDetected += 1;
      total += Number(value);
    }
  }
  
  var average = document.getElementById('average');
  
  if(changesDetected != 0) {
    average.value = total / changesDetected;
  }
  else {
    average.value = 'Cannot calculate average'
  }
    
  alert("Detected that " + changesDetected + " inputs have been changed")
});
<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>

Обновление 3

Другой краткий подход на основе вашего JSFiddle будет следующим:

document.getElementById('calculator').addEventListener('click', function() {
  var physicsAverage = document.getElementById('physicsAverage'),
    historyAverage = document.getElementById('historyAverage');

  physicsAverage.value = calculateAverageById('physics')
  historyAverage.value = calculateAverageById('history');
});

function calculateAverageById(id) {
  /* Get all input descendants of element with id */
  const inputs = document.querySelectorAll(`#${id} input`);

  /* Get all valid grade values from selected input elements */
  const grades = Array.from(inputs)
    .map(input => Number.parseFloat(input.value))
    .filter(value => !Number.isNaN(value));

  /* Return average of all grades, or fallback message if no valid grades present */
  return grades.length ? (grades.reduce((sum, grade) => (sum + grade), 0) / grades.length) : 'No assessment made!'
}
<form>
  <p id="physics">
    Physics:
    <input type="number">
    <input type="number">
    <input type="number">
    <output id="physicsAverage"></output>
  </p>
  <p id="history">
    History:
    <input type="number">
    <input type="number">
    <input type="number">
    <output id="historyAverage"></output>
  </p>
  <button type="button" id="calculator">Calculate</button>
</form>

Основными отличиями здесь являются:

  • использование document.querySelectorAll( # $ {id} ввода ); сшаблонный литерал для извлечения input элементов элемента с id
  • использованием Array.from(inputs) для более удобочитаемого средства преобразования результата запроса в массив
  • использование Number.parseFloat и Number.isNaN при преобразовании и фильтрации элементов input в действительные числовые значения для последующего вычисления среднего значения

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...