Help us understand the problem. What is going on with this article?

jqueryで追加された要素に対してイベントを設定するのにつまづいたこと。

はじめに

プログラミングスクールの課題で、Railsを用いたチャットアプリを作成した際にjavascriptの処理で詰まったことがあったので、同じことで悩んでいる人もいるのではないかと思い記録に残します。

行なった内容

チャットグループを作成または編集する際、インクリメンタルサーチでユーザーの検索を行い、出現した名前の[追加]ボタンを押すことでメンバー一覧の箇所に選択したユーザーの名前が表示されるという流れを実装した。
chatgif.gif

環境

Rails 5.0.7.2
Ruby 2.5.1

詰まってしまった内容

上記のGifは正常に動作している状態なのですが、私は下の状態になってしまい困り果てていました。
chatspaceerror.gif

一度しかクリックしていないのにtestuserという値が大量に出てきました。しかも表示されるのが2個の時や3個の時もあったのです。これは困りました。
デベロッパツールでコンソールを見てもエラーらしきものは出てきておらず、原因がよくわかりません。

該当コード

以下は該当コードを抜粋したものです。修正前の記述です。

sample.html.haml
.chat-group-form__field
  .chat-group-form__field--left
    = f.label :グループ名, class: 'chat-group-form__label'
  .chat-group-form__field--right
    = f.text_field :name, class: 'chat__group_name chat-group-form__input', placeholder: 'グループ名を入力してください'
.chat-group-form__field.clearfix
  .chat-group-form__field--left
    = f.label :チャットメンバーを追加, class: 'chat-group-form__label', for: 'chat_group_チャットメンバーを追加'
  .chat-group-form__field--right
    = f.text_field :member, class: 'chat-group-form__input', id: 'user-search-field', placeholder: '追加したいユーザー名を入力してください', type: 'text', value: ''
  .chat-group-search-user
.chat-group-form__field.clearfix
  .chat-group-form__field--left
    %label.chat-group-form__label{for: "chat_group_チャットメンバー"} チャットメンバー
  .chat-group-form__field--right
    %input{name: 'group[user_ids][]', type: 'hidden', value: "#{current_user.id}"}
    .chat-group-users.current-user
      = current_user.name
    .chat-group-users.js-add-user
      - @group.users.each do |user|
        - unless user == current_user
          .chat-group-user.clearfix.js-chat-member
            %input{name: 'group[user_ids][]', type: 'hidden', value: "#{user.id}"}
            %p.chat-group-users__name  
              = user.name
            .user-search-remove.chat-group-user__btn.chat-group-user__btn--remove.js-remove-btn{data: {user: {id: "#{user.id}", name: "#{user.name}"}}}
              削除

インクリメンタルサーチ部分の記述は今回は割愛します。

sample.js
$(document).on('turbolinks:load', function(){

  //追加したいテンプレートを定義
  function appendAddUserToHTML(user) {
    var html = `<div class='chat-group-user clearfix js-chat-member'>
                  <input name='group[user_ids][]' type='hidden' value='${user.id}'>
                  <p class='chat-group-user__name'>${user.name}</p>
                  <div class='user-search-remove chat-group-user__btn chat-group-user__btn--remove js-remove-btn'>削除</div>
                </div>`
    $('.js-add-user').append(html);
  }

  //こちらで追加された要素のイベントを指定している
  $(document).on("click", ".chat-group-user__btn--add", function(e){
    e.preventDefault();
    $(this.parentNode).remove();
    var user = $(this).data('user-id');
    $.ajax({
      type: 'GET',
      url: '/users/find',
      data: {user_id: user},
      dataType: 'json'
    })
    .done(function(user){
      appendAddUserToHTML(user);
    })
    .fail(function(){
      alert('ユーザーを追加できませんでした')
    })
  })

  $(document).on("click", ".chat-group-user__btn--remove", function(e){
    $(this.parentNode).remove();
  })
});

追加された要素に対してのイベントについては、色々と調べてみると、$(document).on(イベント名, 追加された要素のクラス名またはid, function(){})という書き方にするとちゃんとイベントが発火するとありました。参考にしたリンクは下に貼らせていただきます。

なぜ一度にたくさんの要素が追加されてしまったのか

ここでまた先ほどの問題に戻ります。
自分の中で一つ思いついたのは一度のクリックでイベントが複数回同時に発生しているのではないかということです。

そこで先ほどのjsファイルに

sample.js
$(document).on("click", ".chat-group-user__btn--add", function(e){
  e.preventDefault();

  console.log("done")

  $(this.parentNode).remove();
  var user = $(this).data('user-id');
  $.ajax({
    type: 'GET',
    url: '/users/find',
    data: {user_id: user},
    dataType: 'json'
  })
  .done(function(user){
    appendAddUserToHTML(user);
  })
  .fail(function(){
    alert('ユーザーを追加できませんでした')
  })
})

このようにconsole.logで検証してみることに。すると...
スクリーンショット 2019-12-06 23.49.41.png
console.logが一度のイベントで二回表示されています。これで仮説が立ちました。

間違っていたところ

仮説は立ったものの肝心の原因がわからず...
悩んだ末、質問をさせていただいたところ、$(document).on(イベント名, 追加された要素のクラス名またはid, function(){})のセレクタに問題があるのではないかとのことでした。

そもそも自分はdocumentについてよくわかっていなかったというのもあるのですが、セレクタにdocumentを指定すると誤作動を起こす可能性があるそうです。
ちゃんと調べてみると、documentというのはそのページ全てのDOMの親のようなもので、それを指定してしまうとあまりにセレクタの範囲が広いため、同じクラスなどがあった場合に誤作動をしてしまうと解釈しました。

解決方法

以上のことを踏まえて、以下のような変更を行いました。

sample.js
$('.chat-group-search-user').on("click", ".chat-group-user__btn--add", function(e){
  e.preventDefault();
  $(this.parentNode).remove();
  var user = $(this).data('user-id');
  $.ajax({
    type: 'GET',
    url: '/users/find',
    data: {user_id: user},
    dataType: 'json'
  })
  .done(function(user){
    appendAddUserToHTML(user);
  })
  .fail(function(){
    alert('ユーザーを追加できませんでした')
  })
})

セレクタを追加される要素の直近の親要素を指定してあげることで、無事解決しました。

まとめ

今回つまづいたことでわかったことは、セレクタには追加要素の親要素を指定してあげること、追加要素に対して安易にdocumentを使うと想定外の動きをするということ、自分の場合追加要素から一番近く、最初から存在している親要素を指定してあげると誤作動なくうまくいくということです。

もしこの記事が自分と同じような問題に悩んでいる人の力になれば嬉しい限りです。また、当方実務経験のない初学者のため何か間違いなどあればご指摘いただけると大変嬉しいです。
ここまでご覧くださりありがとうございます。

参考にさせていただいた記事

https://qiita.com/ayies128/items/5d044bc08b9308767f4c
https://qiita.com/negi/items/6ec0d3cedba499eac81a

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away