RailsでAjaxを使ったら$(document).on('click', ...)が発火しなかった話

# はじめに
Railsとkaminariを使ったページングにAjaxを加えて、「もっと見る」ボタンでページネーションの続きを非同期で実装した時に、読み込み中は画面全体にローディング表示をさせようとしたのですが、なかなか上手くいかず躓きました。残念ながら原因はハッキリしていないのですが、取り敢えず動いたので対処法をメモします。

# 環境など
- Ruby 2.5.3(ローカル環境)/2.3.8(本番環境)
- Rails 5.2.2
- kaminari

# コード
記事一覧を取得し、kaminariを使って10件ずつ表示するようにしています。
##コントローラーとビュー

```ruby:app/controllers/articles_controller.rb
def index
  @articles = Article.order(:created_at).reverse_order.page(params[:page]).per(10)
end
```

```:app/views/articles/index.html.erb
<div id="articles">
  <%= render partial: '/articles/list', locals:{articles: articles} %>
</div>
<% if articles.next_page %>
  <div id="more-article-link">
    <%= link_to_next_page articles, 'さらに10件表示', remote: true, id: 'more-articles' %>
  </div>
<% end %>
```
- `id: article`の直下に部分テンプレートを仕込み、`link_to_next_page`の`remote`を`true`とします。
- `link_to_next_page`は、kaminariのヘルパーメソッドで、ページネーションの次のページを読み込みます。
- `remote: true`は、リンクのリクエストをhtmlからjsに変更するオプションです。

## 部分テンプレート
また、`link_to_next_page`は下記の部分テンプレート(`articles/_list.html.erb`)を読み込むためのリンクとなります。

```:app/views/articles/_list.html.erb
<% articles.each do |article| %>
  <div class="content-list clearfix">
    <h4><%= link_to article.title, article_path(article) %></h4>
    <p><%= article.updated_at %></p>
  </div>
<% end %>
```
## ローディング画面の準備
### ビュー・CSS
続いて、ビューにローディング画面を準備します。

```:app/views/articles/index.html.erb
<!-- ここから追加 -->
<div id="loading">
  <div class="loading-pic"></div>
</div>
<!-- ここまで追加 -->

<div id="articles">
<!-- 以下略 -- >
```
```CSS:app/assets/stylesheets/application.scss
#loading {
  display: none;
  background-color: rgba(255, 255, 255, 0.5);
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
}

.loading-pic{
  display: table-cell;
  text-align: center;
  vertical-align: middle;
  background: url("/images/loading.gif") center center no-repeat;
}
```
上記ビューのうち、`<div id="loading"></div>`で囲われたところがローディング画面であり
通常は`display: none;`としています

### 「さらに10件表示」クリック時にローディング画面を表示

ローディング画面を表示するため、Ajax発火と同時に、以下の処理を加えたいと考えました。

```Javascript:app/assets/javascripts/application.js
// turbolinksによるjQueryの不作動を抑える為の記述
$(document).on('turbolinks:load', function() {
  $('#more-articles').click(function(){
    $('#loading').css('display', 'table');
  });
});
```
※Turbolinksの挙動については、こちらの記事が詳しいです(2019/3/4現在)。
https://qiita.com/syou007/items/fda40f65634bb2fadf36

## 「さらに10件表示」機能の実装

ここで先ほど設定した`remote: true`を使ってjsファイルを呼び出す準備をします。
用意するjsファイルの名前は
`「読み込むhtmlの部分テンプレートファイル名」から「頭の"_"を抜いたもの」.js.erb`
となります。したがって、今回リクエストするjsファイル名は`list.js.erb`とします。

```JavaScript:app/views/articles/list.js.erb
$('#more-articles').remove();
$('#articles').append("<%= j render partial: '/articles/article_list', locals:{articles: @articles} %>");
$('#more-article-link').append("<%= j link_to_next_page @articles, 'さらに10件表示', params: { type: :article }, remote: true, id: 'more-articles' %>")
$('#loading').css('display', 'none');
```

## ここまでの処理まとめ
長くなりましたが、ざっくり処理をまとめると以下の通りです。
1. `link_to_next_page`をクリックすると、`#more-articles`に紐づいたjQueryが発火し、ローディング画面が表示
2. 同時に、`list_js.erb`を呼び出し。`#more-articles`に紐づいた`link_to_next_page`を削除
3. `#article`直下に部分テンプレート挿入
4. `#more-article-link`直下に、新しい`link_to_next_page`が追加(ただし、最終ページが読込済の場合は追加されない)
5. 処理が完了したタイミングで`#loading`のCSSを変更する(=ローディング画面を非表示にする)

# 動かない→$(document).on('click'...)の試行
しかしこの場合、最初に表示されている「さらに10件表示」のリンクをクリックした時は問題なくローディング画面が表示されたのですが、その後Ajaxによって追加された「さらに10件表示」については、**クリックしても追加記事が読み込まれるだけで、ローディング画面が一切出てきませんでした。**

どうやら調べてみると、DOM要素をjQueryで追加した場合、追加要素については通常の記述では読み込めないとのこと。
読み込むためには、下記のような記述が必要だとの情報を得ました。

```JavaScript
// NG例
$('セレクタ').click(function(){
  // クリック時の処理を記述
});

// OK例
$(document).on('click', 'セレクタ', function(){
  // クリック時の処理を記述
});
```

これをもとに、以下のように書き換えてみました。

```Javascript:app/assets/javascripts/application.js
// turbolinksによるjQueryの不作動を抑える為の記述
$(document).on('turbolinks:load', function() {
  $(document).on('click', '#more-articles', function(){
    $('#loading').css('display', 'table');
  });
});
```

ところが、これに変えた途端、そもそも最初の発火すら起こらないようになってしまいました。
誤植を疑ってみるものの、めぼしい原因らしき記述は見当たらず、エラーも出ずに迷宮入りしました。

# 苦肉の策

最終的に、以下のようなコードで動きました。

```JavaScript:app/views/articles/list.js.erb
$('#more-articles').remove();
$('#articles').append("<%= j render partial: '/articles/article_list', locals:{articles: @articles} %>");
$('#more-article-link').append("<%= j link_to_next_page @articles, 'さらに10件表示', params: { type: :article }, remote: true, id: 'more-articles' %>")
$('#loading').css('display', 'none');
// ここから追加
$('#more-articles').click(function(){
  $('#loading').css('display', 'table');
});
```

要するに、リンク先で取得するjsファイルにクリックで発火する処理を仕込み
クリックされるのを待つ状態にしておくと言う、やや強引なやり口で解決しました。

# 最後に
イマイチ原因が不明のまま別の方法を模索することになってしまいましたが、何か見落とし等あるのに気づかれた方がいらっしゃいましたら、是非ご教授いただきたく思います。