web-dev-qa-db-ja.com

JavaScript:編集/更新された入力の数を取得します

シナリオ

私の生徒は毎学期、少なくとも1つの科学、1つの物理学、1つの歴史テストを受ける必要があります。次のフォームは、生徒の最終平均点だけでなく、適切な平均点も示します。

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;

質問

次の単一行のchanged入力フィールドの数を取得する簡単な方法は何ですか?私はあなたの方法を理解してから、私のフォームを複数の行に展開します。

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>
24
Mori

コースごとの平均を計算する算術演算で使用する前に、入力の値が有効な数値であることを確認する必要があるようです。これを行う1つの方法は、次のチェックを使用することです。

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

また、以下に示すようにスクリプトとHTMLを調整することを検討することもできます。これにより、以下に詳述するように、3つのクラスそれぞれの平均計算を一般化して再利用できます。

_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 _);_とテンプレートリテラルを使用して、inputで要素のid要素を抽出する
  • クエリの結果を配列に変換するより読みやすい手段としてのArray.from(inputs)の使用
  • 後続の平均計算のためにinput要素を有効な数値に変換およびフィルタリングするときの_Number.parseFloat_および_Number.isNaN_の使用

お役に立てば幸いです。

17
Dacre Denny

まず、IDをClassに変更して、入力を論理グループにまとめます。次のステップは、nullでない値を持つ特定のグループから入力を取得することです。これを行うには、たとえば.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>

追加更新:コメントのOPの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>
7
Jordan Maduro

常に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>
5
Vishwas R

少し見苦しいですが、テストスコアをブール値と見なすことができます。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
josemigallas
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ソリューションです。あなたはもっと短くすることができますが、私の意見ではこれは直感的です。

4

コードには、注意する必要がある2つの主要なチョークポイントがあります。

  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>
4
Terry

counterを関数に導入します。毎回クリック入力値が ''かどうかを確認します。キャストする前に文字列になるためです。 ternary操作はfalseを返すため、何もしない場合

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

test1=='' then false else カウンターをインクリメント。平均を計算する前に、カウンターが0かどうか、カウンターが1に設定されているかどうかを確認します。ゼロの場合、ゼロによる除算の結果は無限大になり、出力としてNaNが得られ、counterを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>
3
ellipsis

値を_3_で除算しているため、予想よりも結果が少なくなります。

  • コードの `htmlを動的に作成します。
  • 変数(_scienceTest1,scienceTest2....._)を作りすぎないでください。代わりに、ループを使用して値をarrayに保存します
  • このようなNumber(scienceTest1) + Number(scienceTest2) + Number(scienceTest3)) / 3のような記述は、テストを増やすことができ、型エラーの可能性が高くなるため、良くありません。代わりに値を配列に格納し、最後に Array.prototype.reduce() を使用して値を追加します。
  • 値の配列については、配列にプッシュする前に_value !==''_かどうかを確認して、正しい平均が得られるようにする必要があります。

コードは完全に動的であり、任意のsubjectsと任意のテストを使用できます

_//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;
}_
3
Maheer Ali

方法

固定数3で除算するという問題は、配列の長さプロパティを使用してこの値を動的にすることで解決できます。

ステップ1:すべてのサブジェクトを配列に配置し、入力に値がある場合はフィルターします。

ステップ2:被験者ごとに平均値を取得します。

ステップ:重みを使用して最終スコアを計算します。

複数回使用される2つの関数があります。 isTruthyおよびaverage

平均的な関数をスリムにするために、sumaverageに分けました

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>
2
Michael Warner

tests = {}と呼ばれるオブジェクトを作成し、テストをその中に配列として保持して、後で変更できる1つのサブジェクトのみのコードを記述した例を示します。

また、私はid'sの代わりにclassesを使用して、コードと反復性。

pdate:以下のコードを更新して、複数の対象に対してより動的で柔軟になるようにしました。

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>
2
bhansa

すでにいくつかの解決策があります。こちらが私のものです。

コードには最適化するものがあり、次のような単純な構成配列に基づいてHTMLを動的に作成することをお勧めします

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

したがって、サブジェクトまたはテストの数が変更された場合、コードを変更する必要はなく、この構成のみを変更します。私は自分のコード例にコメントして、わかりやすくするために、そこで何が行われるかを試みました。しかし、それは重要な部分ではありません。さらに重要なことは、計算部分で何が起こっているのかを確実に理解する必要があることです。計算は正しいです。その部分から始めます。

name属性をテスト結果の入力に追加し、同じサブジェクトの各入力に同じ名前を付けると、その入力のNodeListを簡単に取得できます。各Node=の値を確認し、それに基づいて計算します。これで、値と学生が各科目に対して行ったテストの数がわかります。

みてみましょう

/* 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>
2
Olafant

変更された入力フィールドの数を取得する簡単な方法は何ですか

inputdata値に等しいカスタム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>
2
Mori

私の推奨は、複数の入力を読み取るために複数の入力フィールドを回避することです。私の場合、入力フィールド内の個々の値を区切るためにセミコロンを使用しています。そうすることで、必要な数の値を入力できます(少なくとも1つの値)。したがって、私のフォームは次のようになります。

<!-- 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;
  });
})();
1
joernneumeyer
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>

上記では、出力は入力タグにクラス属性を追加することで実現されます(同じコントロールグループに同じ名前を付けます)。 2つ目は、このクラスオブジェクトをループして、空でない値またはゼロ以外の値のカウントを取得することです。

1
Asav Vora