質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.61%

  • JavaScript

    15968questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • HTML5

    3889questions

    HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

  • CSS3

    1992questions

    CSS(Cascading Style Sheet)の第3版です。CSS3と略されることが多いです。色やデザインを柔軟に変更することが可能になります。

JavaScriptの一部クリックイベントが登録されない

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 501

okame

score 50

前提・実現したいこと

タイトルの通りで、一部のクリックイベントが正常に登録されず動作しません。

あるシステムを作っていて、その中で管理者を表に一覧で表示する画面があります。
表の一番右には削除ボタンがあって、それをクリックすると確認画面モーダルウィンドウが表示されます。
さらにそのモーダル内の削除するボタンをクリックすると削除を実行する(ajaxでGET投げる→PHP処理)としたいのですが、
いかんせんイベント自体が動作していないようなので先に進みません。

発生している問題・該当のソースコード

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
    <ol class="breadcrumb">
    <li class="active">Home</li>
    <li class="active">管理者一覧</li>
  </ol>

  <nav aria-label="...">
    <ul class="pager">
      <li class="previous"><a onclick="history.back()"><span aria-hidden="true">&larr;</span>戻る</a></li>
    </ul>
  </nav>

  <h4 class="page-header">管理者一覧</h4>

  <div style="width:100%;height:300px;overflow:auto;">
    <table class="table table-striped table-hover">
      <thead>
        <tr>
          <th style="width:3%">#</th>
          <th style="width:10%">ユーザー名</th>
          <th style="width:15%">名前</th>
          <th>備考</th>
          <th style="width:8%">操作</th>
        </tr>
      </thead>
      <tbody>
        {% for manager in managers %}
          <tr>
            <td>{{ manager.id }}</td>
            <td>{{ manager.user_name }}</td>
            <td>{{ manager.last_name }} {{ manager.first_name }}</td>
            <td>{{ manager.remarks }}</td>
            <td>
              <button class="btn btn-default btn-sm" value="{{ manager.id }}">編集</button>
              <button class="btn btn-default btn-sm delete" value="{{ manager.id }}">削除</button>
            </td>
          </tr>
        {% endfor %}
      </tbody>
    </table>
  </div>

  <button class="btn btn-primary button-center">管理者を追加する</button>

  <div id="modalDelete">
    <p></p>
    <button class="btn btn-primary button-center">削除する</button>
  </div>

<script src="https://code.jquery.com/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</body>
</html>
/* モーダルウィンドウで表示する要素(通常時は非表示) */
#modalDelete {
  display: none;
}
/* オーバーレイ要素のスタイル */
#modalOverlay {
  top: 0;
  left: 0;
  width: 100%;
  position: fixed;
  z-index: 97;
  background: #000;
  display: none;
}
/* モーダルウィンドウ全体のスタイル */
#modalWindow {
  margin-top: -200px;
  margin-left: -170px;
  top: 50%;
  left: 50%;
  width: 350px;
  height: 200px;
  position: fixed;
  z-index: 98;
  background: #fff;
  display: none;
}
/* モーダルウィンドウ内の閉じるボタンのスタイル */
#modalWindow > .modalClose {
  top: -15px;
  right: -15px;
  width: 30px;
  height: 30px;
  line-height: 30px;
  color: #fff;
  font-size: 1.5em;
  background: #757575;
  border-radius: 30px;
  box-shadow: 0 0 3px 0 #000;
  position: absolute;
  z-index: 99;
  cursor: pointer;
  text-align: center;
}
/* コンテンツエリアのスタイル */
#modalContents {
  margin: 30px auto;
  padding: 0 20px;
  width: 100%;
  height: 340px;
  box-sizing: border-box;
  overflow-y: auto;
}
#modalContents p {
  padding-bottom: 2em;
  font-size: 1em;
  text-align: center;
  margin-top: 30px;
}
// for manager function
(function () {
  // 削除前確認画面モーダルウィンドウの表示処理
  document.querySelectorAll('.delete').forEach(function (deleteButton) {
    deleteButton.addEventListener('click', function() {
      // 取得前にモーダル内削除ボタンに必要な値を設定
      var modalDeleteButton = document.querySelector('#modalDelete button');
      modalDeleteButton.setAttribute('id', 'execDelete'); // 削除実行時のトリガーとしてidをセット
      // execDelete();
      modalDeleteButton.setAttribute('value', deleteButton.getAttribute('value')); // 削除するmanager.id

      var td = deleteButton.parentNode.parentNode.querySelector('td:nth-child(2)');
      document.querySelector('#modalDelete p').textContent = td.textContent + ' を削除しますか?';

      // モーダルウィンドウ内の表示要素を取得
      var source = document.querySelector('#modalDelete').innerHTML;

      // bodyタグ直前にオーバーレイ要素とモーダルウィンドウ要素を挿入
      var overlay = document.createElement('div');
      overlay.setAttribute('id', 'modalOverlay');

      var modalWindow = document.createElement('div');
      modalWindow.setAttribute('id', 'modalWindow');
      modalWindow.innerHTML = 
        '<div class="modalClose">×</div>'
      + '<div id="modalContents">'
        + source
      + '</div>';

      document.body.appendChild(overlay);
      document.body.appendChild(modalWindow);

      // 非表示にしていたオーバーレイ要素とモーダルウィンドウ要素をフェードイン表示
      var modalElements = document.querySelectorAll('#modalOverlay, #modalWindow');
      modalElements.forEach(function (element, index) {
        element.style.display = 'block';
        element.style.opacity = 0;

        if (index === 0) { // to overlay
          element.style.height = window.innerHeight + 'px';
          element.style.opacity = 0.7;

        } else if (index === 1) {
          element.style.opacity = 1;
        }
      });

      // ブラウザウィンドウの高さ変更に合わせてオーバーレイ要素の高さを調整する
      window.addEventListener('resize', function() {
        var modalOverlay = document.getElementById('modalOverlay');
        if (modalOverlay.style.display === 'block') {
          modalOverlay.style.height = window.innerHeight;
        }
      }, false);

      // モーダル閉じるボタンorオーバーレイをクリック時にモーダルウィンドウを閉じる
      document.querySelectorAll('#modalOverlay, .modalClose').forEach(function (clickElement) {
        clickElement.addEventListener('click', function() {
          document.querySelectorAll('#modalWindow, #modalOverlay').forEach(function (removeElement) {
            // TODO: add fade out animation...
            removeElement.parentNode.removeChild(removeElement);
          });
        }, false);
      });

      // 削除実行イベント
      document.getElementById('execDelete').addEventListener('click', function() {
        console.log('OK'); // <-- モーダル内削除ボタンをクリックしても反応なし。なぜクリックイベントが登録されない??
        var ajax = new XMLHttpRequest();

        ajax.onreadystatechange = function () {
          if(ajax.readyState === XMLHttpRequest.DONE && ajax.status === 200) {
            console.log(ajax.responseText);
          }
        };

        ajax.open('GET', '/admin/staff/delete.php?id=' + this.getAttribute('value'), true);
        ajax.send(null);
      }, false);
    }, false);
  });
})();

jsコードの下の方「// 削除実行イベント」とコメントしているところからの
クリックイベント登録コードが該当箇所です。

テスト環境

テスト環境を以下に用意しました。
https://jsbin.com/cunirez/edit?html,css,js,output

以上

皆様のお知恵を拝借できれば幸いです。宜しくお願い致します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • okame

    2017/11/08 16:38

    そうですか...。以前はteratail上に全部記載していたのですが、ある方から「ずらずらコード載せても分かんないからテスト環境用意するのがマナーだよ!」とご指摘頂いてからはJSBINに書くようにしていました。決して怠けているわけではありません。とにかく、少々お待ち下さい。いま編集しますね!

    キャンセル

  • kei344

    2017/11/08 16:46

    https://teratail.com/questions/92611 ここでのコメントのことでしょうか。「検証環境も」と書かれているだけで「長い」と指摘されているわけではないと思います。

    キャンセル

  • okame

    2017/11/08 16:55

    言葉としては書いていませんが、コンテキスト(文脈)からするとそういう意図かなと私は読み取りました。ところでコードをこの質問上に追記しました。いかがでしょうか??

    キャンセル

回答 5

+2

同じid属性値(execDelete)を持つmodalDeleteButtonが複数存在してしまっているからです. 

文法上, HTML文書内のid値は常に一意である必要があります. しかるにあなたのコードでは, 途中でモーダルウィンドウの内容をテンプレートからコピーする過程で, 同一文書内に同じidexecDeleteを持つノードが複数作られてしまっています. 

すると後続のgetElementByIdメソッドは先頭execDeleteノードを見つけてきますが, このノードはテンプレート内のノードであるため, ここにイベントリスナ関数を登録しても意味がないことになります. 

// for manager function
(function () {
  // 削除前確認画面モーダルウィンドウの表示処理
  document.querySelectorAll('.delete').forEach(function (deleteButton) {
    deleteButton.addEventListener('click', function() {
      // 取得前にモーダル内削除ボタンに必要な値を設定
      var modalDeleteButton = document.querySelector('#modalDelete button');
      modalDeleteButton.setAttribute('id', 'execDelete'); // 削除実行時のトリガーとしてidをセット
      // execDelete();
      modalDeleteButton.setAttribute('value', deleteButton.getAttribute('value')); // 削除するmanager.id

      var td = deleteButton.parentNode.parentNode.querySelector('td:nth-child(2)');
      document.querySelector('#modalDelete p').textContent = td.textContent + ' を削除しますか?';

      // モーダルウィンドウ内の表示要素を取得
      var source = document.querySelector('#modalDelete').innerHTML;

/*
//例えばここに次のコードを挿入するととりあえず動く
modalDeleteButton.removeAttribute('id');
*/

      // bodyタグ直前にオーバーレイ要素とモーダルウィンドウ要素を挿入
      var overlay = document.createElement('div');
      overlay.setAttribute('id', 'modalOverlay');

      var modalWindow = document.createElement('div');
      modalWindow.setAttribute('id', 'modalWindow');
      modalWindow.innerHTML = 
        '<div class="modalClose">×</div>'
      + '<div id="modalContents">'
        + source
      + '</div>';//←★★★ここでid値が重複するノードが挿入されてしまっている!

      document.body.appendChild(overlay);
      document.body.appendChild(modalWindow);

      // 非表示にしていたオーバーレイ要素とモーダルウィンドウ要素をフェードイン表示
      var modalElements = document.querySelectorAll('#modalOverlay, #modalWindow');
      modalElements.forEach(function (element, index) {
        element.style.display = 'block';
        element.style.opacity = 0;

        if (index === 0) { // to overlay
          element.style.height = window.innerHeight + 'px';
          element.style.opacity = 0.7;

        } else if (index === 1) {
          element.style.opacity = 1;
        }
      });

      // ブラウザウィンドウの高さ変更に合わせてオーバーレイ要素の高さを調整する
      window.addEventListener('resize', function() {
        var modalOverlay = document.getElementById('modalOverlay');
        if (modalOverlay.style.display === 'block') {
          modalOverlay.style.height = window.innerHeight;
        }
      }, false);

      // モーダル閉じるボタンorオーバーレイをクリック時にモーダルウィンドウを閉じる
      document.querySelectorAll('#modalOverlay, .modalClose').forEach(function (clickElement) {
        clickElement.addEventListener('click', function() {
          document.querySelectorAll('#modalWindow, #modalOverlay').forEach(function (removeElement) {
            // TODO: add fade out animation...
            removeElement.parentNode.removeChild(removeElement);
          });
        }, false);
      });

      // 削除実行イベント
      //NOTE:↓のgetElementByIdは先頭のexecDeleteを参照してしまっているので
      //実際のモーダルウィンドウ上のボタンは無反応となる
      document.getElementById('execDelete').addEventListener('click', function() {
        console.log('OK'); // <-- モーダル内削除ボタンをクリックしても反応なし。なぜクリックイベントが登録されない??
        var ajax = new XMLHttpRequest();

        ajax.onreadystatechange = function () {
          if(ajax.readyState === XMLHttpRequest.DONE && ajax.status === 200) {
            console.log(ajax.responseText);
          }
        };

        ajax.open('GET', '/admin/staff/delete.php?id=' + this.getAttribute('value'), true);
        ajax.send(null);
      }, false);
    }, false);
  });
})();

さて, この問題を解決するには次の何れかを用います.

  1. id属性値が重複しないようにコードを記述する
    付け焼き刃的ですが, テンプレートのソースコードを入手したら, id属性を削除してしまう方法です. こうすると後続のgetElementByIdメソッドが正しいボタンノードを検索してくるはずです.
  2. template要素を使ってモーダルウィンドウ内のコンテンツの内容をメイン文書から分離する
    提示のコードのように何らかのHTML構造の「雛形」が必要な場合は, 専用のtemplate要素を用いると便利です.

参考)
https://developer.mozilla.org/ja/docs/Web/HTML/Element/template

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/08 17:32

    回答ありがとうございます!
    ...すでに同じご指摘を別の方からいただき本事象は解決しベストアンサー設定済みでした。
    ご協力いただき感謝いたします。

    キャンセル

  • 2017/11/08 17:37

    次からは「問題の内容」だけでなく「あなたはどのような考えでそのコードを記述したのか」も書いて下さい. 今回は「モーダルウィンドウのテンプレートを作り, 都度その内容をコピーしている」のがネックとなっているわけで, これがあると無しとではコードリーディングを行う際の効率が違います.

    キャンセル

  • 2017/11/08 17:40

    失礼しました。
    以後気をつけたいと思います。

    キャンセル

checkベストアンサー

+1

まずここが問題です。

      var modalDeleteButton = document.querySelector('#modalDelete button');
      modalDeleteButton.setAttribute('id', 'execDelete'); // 削除実行時のトリガーとしてidをセット
      // execDelete();
      modalDeleteButton.setAttribute('value', deleteButton.getAttribute('value')); // 削除するmanager.id


ここであなたは「雛形として使うほう」の#modalDeleteに入っているボタンにidを割り当てています。その後、#modalWindowの中に埋め込むためにinnerHTMLをsourceとして取り出していますね。

      var source = document.querySelector('#modalDelete').innerHTML;


ここでコピーされているsourceにはidとしてexecDeleteが割り当て済のbuttonがコピーされていることになります。そして、このまま#modalWindowをdocument.bodyにappendすると、button#execDeleteがコード上に2つ存在する状態になります。
もうおわかりですね?idが同じエレメントがdocument上に2つあれば、getElementByIdで得られるエレメントは「先に見つかったほう」です。つまりあなたがボタンにclickを登録しようとしているのはずっと「雛形」のほうのボタンに登録しているのです。

そもそもidがだぶること自体非推奨な状況ですが、すぐ直したいならdocument.getElementById('execDelete')のかわりにdocument.querySelector('#modalWindow #execDelete')とすれば、雛形からコピーされたほうの#execDeleteボタンにclickイベントが登録されます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/08 17:21

    回答ありがとうございます!
    そして、ご指摘いただいた箇所を修正し無事に解決しましたー!!自分では絶対気づけなかったと思います。。。

    本当に助かりました☆m(_ _)m

    キャンセル

+1

統合環境でテストするか、もう少し簡単な仕組みでテストされる方がよいでしょう。
またテストしたブラウザも記載が必要です。
(たとえばIEはNodeListに対してforEachが発行できなかったりします)
とりあえずはローテクでconsole.logをベタベタ貼るだけでもやらないよりはマシです。

見た感じ提示されたソースもテンプレかなにかわかりませんが挙動が確認できません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/08 16:54

    回答ありがとうございます!
    テストした環境載せ忘れましたm(_ _)m 追記します!

    ちなみにテスト環境では動作が再現されるのですが、JSBinで挙動が確認できないということでしょうか?

    [テスト環境手順]
    ①プレビュー画面のテーブル一番右側の「削除」ボタンを押す
    ②モーダルウィンドウが表示されるので、その中の「削除する」ボタンをクリックすると、何も動作しない(window.alert()を仕込んでいるのに)。

    キャンセル

  • 2017/11/08 16:57

    ちなみに質問前に、ローテクでconsole.log()をベタベタ貼って、質問の説明にも書いている「// 削除実行イベント」以下のコードのみ正常に動作していないことを確認済みです。

    キャンセル

  • 2017/11/08 17:04

    とりあえずjsの
    「(function () {」はじまり「})();」おわりの部分を

    window.addEventListener('DOMContentLoaded', function(e){
    });
    に切り替えてみてはどうでしょう

    キャンセル

  • 2017/11/08 17:25

    別の方の回答にて本事象は解決しました。
    ご協力いただきありがとうございました!

    キャンセル

  • 2017/11/08 17:25

    別の方の回答にて本事象は解決しました。
    ご協力いただきありがとうございました!

    キャンセル

+1

document.getElementById('execDelete')にイベントを定義しようとしている段階では、document.getElementById('execDelete')自体が存在しないためイベントが定義出来ず、削除も実行されない、という状況のようです。

モーダルを開いてから(document.getElementById('execDelete')が出現してから)、同削除イベントを定義してみるのはどうでしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/08 16:51

    回答ありがとうございます!
    私もそれを疑ったので、既にモーダル開いた後の箇所に該当コード記載済みです。
    それでも解消しないという状況だったのでteratailで質問させていただきました。

    キャンセル

0

とりあえず、下記の書き方では後から追加された項目にはイベントがつきません。後から追加された要素のイベントを取り扱う方法をまず調査されたほうが良いと思います。

document.querySelectorAll('.delete').forEach(function (deleteButton) {
  deleteButton.addEventListener('click', function() {});
});

jQueryで説明されたものが多いような。

【jQueryのclickとbindとliveとdelegateとonの違い - Qiita】
https://qiita.com/smzk/items/5eed5a90c4b32ca8b23a

【実践、jQuery - .on()と.off()を使いこなす 1 | CodeGrid】
https://app.codegrid.net/entry/practical-jquery-1

【jQueryを使わずjavascriptだけで書き直した際の記法メモ - Qiita】
https://qiita.com/moriyaman/items/3b3f7878f8ecc2b76372#liveon-event


修正履歴:「(モーダルのボタンについても同じ問題です)」という部分を削除

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/08 17:23

    別の方の回答にて本事象は解決しました。
    お手数おかけしましたがご協力いただきありがとうございました!

    キャンセル

  • 2017/11/08 17:25

    とりあえずイベントの扱いも見直したほうがよいですよ。

    キャンセル

  • 2017/11/08 17:30

    わかりました!
    ご丁寧に追加コメントありがとうございます!!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    15968questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • HTML5

    3889questions

    HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

  • CSS3

    1992questions

    CSS(Cascading Style Sheet)の第3版です。CSS3と略されることが多いです。色やデザインを柔軟に変更することが可能になります。