明るく

暗く

JavaScript

addEventListenerを使用する時の注意点とエラーの解決方法

2024.5.9

2024.5.9

addEventListenerを使用する時の注意点とエラーの解決方法

本記事では、addEventListenerが効かない事例とその解決策について解説します。

特に「addEventListenerでは、getElementsByClassNameが使えない」点に注意が必要です。

「税込金額計算ツール」を例に、エラーと解決策を見ていきましょう。

 

税込計算after

addEventListener メソッドを持っていないと addEventListener が効かなくなる

どのようなときにaddEventListenerでgetElementsByClassNameが使えなくなるのか、「税込金額計算ツール」の実装過程から見ていきます。

※OS環境はMacOS、エディタはVisual Studio Codeを使用

  1. HTML・CSSで、入力ボックス・計算結果の外観を作成します。
    今回は、”item_price”クラス内に金額を入力し、”total_price”クラスで計算値を出力します。

 

<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>税込価格を表示する</title>
 <style>
   input{
     width: 200px;
     font-size: 16px;
     padding: 8px;
     margin-bottom: 8px;
    }
 </style>
</head>
<body>
 <input type="number" class="item_price" placeholder="金額を入力してください">
 <div class="total_price"></div>
</body>
</html>

 

  1. 税込金額を自動計算・表示させるために、以下のJavaScriptを入力します。

 

<script>
  // "item_price" クラスの要素(入力した値)を、変数:inputPrice と定義//
 const inputPrice = document.getElementsByClassName("item_price");
 // "total_price" クラスの要素(税込で表示する値)を、変数:totalPrice と定義//
 const totalPrice = document.getElementsByClassName("total_price");
 // "item_price" クラスに値が入力されたときの処理を、addEventListenerで指定//
 inputPrice.addEventListener('input', function() {
  //入力された値が空白の場合は、空白で返す(NaNと表示されるのを防止)//
  if (inputPrice.value === '') {
      totalPriceDisplay.textContent = '';
      return;
  }
  //税率を設定する(今回は10%)//
  const taxRate = 0.1;
  //変数:totalPriceに、「税込価格:”入力値の税込金額”円」(小数点第2位まで)を代入する=>"total_price"クラスに税込金額を表示//
  totalPrice.textContent = `税込価格: ${(inputPrice.value * (1 + taxRate)).toFixed(2)} 円`;
 });
</script>

 

  1. ボックスに数値を入力しても、なぜか税込金額は表示されません。

 

税込計算before

 

  1. 検証画面でエラー内容を確認したところ、Uncaught TypeError: inputPrice.addEventListener is not a functionが表示されています。

 

addEventListenerエラー

 

「inputPrice は関数ではない」=「addEventListener メソッドを持っていない」ことから、メソッドを呼び出すことができないようです。

エラーが発生した理由:getElementsByClassName で取得した要素は、addEventListener が使えない

document.getElementsByClassName() で取得した inputPrice の値は、関数ではなくHTMLコレクションです。

HTMLコレクションは「HTMLの要素の集合を表す複数オブジェクト」であることから、addEventListener メソッドが存在しません。

一方で、addEventListener は単一の要素に対してイベントリスナーを追加します。

そのため、getElemntsByClassName で取得した要素に対して addEventListener を直接適用できないのです。

コラム:HTMLコレクションの使用例:男女数の自動カウントツール

HTMLコレクションについて、男女数の自動カウントツールを例に解説します。

男女数カウント

 

  1. 以下の通り、HTM・CSSコードを入力します。 liタグより、男性にはmanクラスを、女性にはwomenクラスをそれぞれ設定しています。

 

<ul>
 <!-- 男性にはmanクラスを、女性にはwomenクラスをそれぞれ設定 -->
 <li class="man">山田太郎</li>
 <li class="woman">田中花子</li>
 <li class="man">鈴木次郎</li>
 <li class="woman">佐藤優子</li>
 <li class="woman">渡辺由美</li>
</ul>
<div id="result"></div>

 

  1. 以下の通り、JavaScriptを実装します。
    • manクラスに一致するすべての要素を、変数:menと定義
    • womanクラスに一致するすべての要素を、変数:womenと定義
    • “result”IDのHTML要素に、カウントした数を表示する

 

<script>
 //manクラスの要素を取得し、変数:menと定義
 const men = document.getElementsByClassName('man');
 //womanクラスを持つ要素を取得し、変数:womenと定義
 const women = document.getElementsByClassName('woman');
 //id="result"のHTML内に、「男性は "menの要素数" 人、女性は  "womenの要素数" 人です。」を代入//
 document.getElementById('result').innerHTML = `男性は ${(men.length)} 人、女性は ${(women.length)} 人です。`;
</script>

 

  1. すると、名簿の一覧に対する男女の人数を表示できます。

 

男女数カウント

 

HTMLコレクションは「HTMLの要素の集合を表す複数オブジェクト」なので、getElementsByClassName()を用いることで、指定されたクラス名に一致するすべての要素を取得できるのです。

解決策1:document.getElementsByClassName() の代わりにquerySelector を使用する

以下の通り、item_price クラスと total_price クラスの値を取得する際に、querySelector を使用すると、正しく表示されます。

<!DOCTYPE html>
<html lang="ja">

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>税込価格を表示</title>
 <style>
  input {
    width: 200px;
    font-size: 16px;
    padding: 8px;
    margin-bottom: 8px;
   }
  </style>
</head>
<body>
 <input type="number" class="item_price" placeholder="金額を入力してください">
 <div class="total_price"></div>
 <script>
  const inputPrice = document.querySelector(".item_price");
  const totalPrice = document.querySelector(".total_price");
  inputPrice.addEventListener('input', function () {
    if (inputPrice.value === '') {
        totalPrice.textContent = '';
        return;
    }
  const taxRate = 0.1;
  totalPrice.textContent = `税込価格: ${(inputPrice.value * (1 + taxRate)).toFixed(2)} 円`;
  });
  </script>
</body>
</html>

 

税込計算after

 

querySelector() とは、指定されたCSSセレクタに一致する最初のHTML要素を取得するメソッドです。

単一の要素が取得されることから、直接 addEventListener を適用できます。

解決策2:クラスの代わりにIDを取得させる

以下の通り、item_price と total_price をクラスではなく id に設定し、getElementById() で id の要素を取得すると、正しく表示されます。

<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>税込価格を表示</title>
 <style>
  input {
    width: 200px;
    font-size: 16px;
    padding: 8px;
    margin-bottom: 8px;
   }
  </style>
</head>
<body>
 <input type="number" id="item_price" placeholder="金額を入力してください">
 <div id="total_price"></div>
 <script>
  const inputPrice = document.getElementById("item_price");
  const totalPrice = document.getElementById("total_price");
  inputPrice.addEventListener('input', function () {
    if (inputPrice.value === '') {
        totalPrice.textContent = '';
        return;
    }
  const taxRate = 0.1;
  totalPrice.textContent = `税込価格: ${(inputPrice.value * (1 + taxRate)).toFixed(2)} 円`;
  });
  </script>
</body>
</html>

 

税込計算after

 

HTML文書内で要素に付けられるIDは単一であり、重複してはいけません。

もし同じIDが複数の要素で使われている場合、メソッドは最初に見つかった要素を返します。

このように単一の要素が取得されることから、直接 addEventListener を適用できるのです。

addEventListener 使用時に注意したい他のエラー

addEventListener を使用した際に発生するエラーは、他にもあります。
先ほどと同じ「税込金額計算ツール」を例にして、見ていきましょう。

複数の箇所に同じクラスを設定した場合、2つ目以降のクラスでaddEventListenerが動作しない

以下の通り、HTMLで価格を入力する欄を追加したとき、下段のボックスに数値を入力しても税込金額が表示されません。

<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>税込価格を表示</title>
 <style>
  input {
    width: 200px;
    font-size: 16px;
    padding: 8px;
    margin-bottom: 8px;
  }
 </style>
</head>
<body>
 <div class="price">
  <p>価格1</p>
  <input type="number" class="item_price" placeholder="金額を入力してください">
  <div class="total_price"></div>
 </div>
 <div class="price">
   <p>価格2</p>
   <input type="number" class="item_price" placeholder="金額を入力してください">
   <div class="total_price"></div>
 </div>
 <script>
  const inputPrice = document.querySelector(".item_price");
  const totalPrice = document.querySelector(".total_price");
  inputPrice.addEventListener('input', function () {
    if (inputPrice.value === '') {
        totalPrice.textContent = '';
        return;
    }
      const taxRate = 0.1;
      totalPrice.textContent = `税込価格: ${(inputPrice.value * (1 + taxRate)).toFixed(2)} 円`;
  });
 </script>
</body>
</html>

 

2段入力before

 

querySelector() は「最初のHTML要素を取得するメソッド」です。そのためこのエラーは、下段に設定された “item_price” クラスと “total_price” クラスでは無効なのが原因です。

そこで、以下の通りJavaScriptを入力します。

<script>
 const prices = document.querySelectorAll(".price");
 prices.forEach(price => {
    const inputPrice = price.querySelector(".item_price");
    const totalPrice = price.querySelector(".total_price");
    inputPrice.addEventListener('input', function () {
        if (inputPrice.value === '') {
            totalPrice.textContent = '';
            return;
        }
        const taxRate = 0.1;
        totalPrice.textContent = `税込価格: ${(inputPrice.value * (1 + taxRate)).toFixed(2)} 円`;
    });
 });
</script>

まずは、querySelectorAll メソッドで「指定されたCSSセレクターに一致するすべての要素を取得」します。

次に、pricesクラスに含まれる各要素に対して、以下のループ処理を設定します。

  • priceクラス内の”item_price”の要素を取得して、”inputPrice”と定義する
  • priceクラス内の”total_price”の要素を取得して、”totalPrice”と定義する
  • “item_price” クラスに値が入力されたときの処理を、addEventListenerで指定

これでエラーが解消され、価格1と価格2の両方に対して正しい税込価格が表示できるようになります。

 

2段入力after

イベントリスナーが重複した場合、後から書いた処理に上書きされる

以下の通り、ダブルクリックした際に入力金額が消去される動作を2つ追加します。

  1. 入力した価格と税込価格の欄を消去する
  2. 入力した価格を消去したあとに、税込価格の欄に「金額がクリアされました!」と表示する

 

<script>
 const prices = document.querySelectorAll(".price");
 prices.forEach(price => {
    const inputPrice = price.querySelector(".item_price");
    const totalPrice = price.querySelector(".total_price");
    inputPrice.addEventListener('input', function () {
        if (inputPrice.value === '') {
            totalPrice.textContent = '';
            return;
        }
    const taxRate = 0.1;
    totalPrice.textContent = `税込価格: ${(inputPrice.value * (1 + taxRate)).toFixed(2)} 円`;
    });
    //金額が消去された後は、何も表示されない
    inputPrice.addEventListener('dblclick', function () {
        inputPrice.value = '';
        totalPrice.textContent = '';
    });
    //金額が消去された後に、「金額がクリアされました!」と表示
    inputPrice.addEventListener('dblclick', function () {
        inputPrice.value = '';
        totalPrice.textContent = '金額がクリアされました!';
    });
 });
</script>

 

すると、①の設定が②の設定に上書きされてしまいます。

税込計算:ダブルクリック

addEventListener は、イベントリスナーが追加された順番に実行されます。

そのため、先に追加されたリスナーが実行された後に、次に追加されたリスナーが実行されてしまい、その結果上書きされてしまうのです。

これを解決するには、コードを理解しやすくするために、1つのイベントリスナー内で必要な処理をまとめて実行するようにしましょう。

<script>
 const prices = document.querySelectorAll(".price");
 prices.forEach(price => {
    const inputPrice = price.querySelector(".item_price");
    const totalPrice = price.querySelector(".total_price");
    inputPrice.addEventListener('input', function () {
        if (inputPrice.value === '') {
            totalPrice.textContent = '';
            return;
        }
    const taxRate = 0.1;
    totalPrice.textContent = `税込価格: ${(inputPrice.value * (1 + taxRate)).toFixed(2)} 円`;
    });
    inputPrice.addEventListener('dblclick', function () {
        inputPrice.value = '';
        totalPrice.textContent = '';
    });
 });
</script>

まとめ

getElementsByClassName で取得した要素は、HTMLコレクションになるため、addEventListener を呼び出せません。そのため、以下のメソッドでエラーを解消させましょう。

  • querySelector() を使用する
  • クラスではなくidに設定し、getElementById() を使用する

他にも「複数の箇所に同じクラスを設定する場合は、querySelectorAll メソッドを用いてforeach文でループさせる」「イベントリスナーを重複させない」ことも覚えておくと良いです。

addEventListener への理解を深めて、実装バリエーションを増やしましょう。