前提
個人開発でcocoonというgemを使うことにした。入力フォームを作る際に、項目を簡単に増減できるお手軽gemだ。
なんやかんやあって項目を増減することはできたが、callbackを実装するところで手間取った。自身のためにもすこし記録を残しておく。
やりたいこと
gemはさくさく動くが、増減する項目数について制限を設けたかった。項目が0になってしまうのは捨てたいし、10個20個と追加できてしまうと見栄えが悪くなる。
cocoonはcallbackを用意している。
Callbacks (upon insert and remove of items)
On insertion or removal the following events are triggered:
- cocoon:before-insert: called before inserting a new nested child, can be canceled
- cocoon:after-insert: called after inserting
- cocoon:before-remove: called before removing the nested child, can be canceled
- cocoon:after-remove: called after removal
Callbacks (upon insert and remove of items)--cocoon
項目の増減前にそれぞれイベントをつくることができ、いいかんじに制限してくれる。
いいな!やってみよ!
と思ったけどなぜかイベントが起こらなかった。
調査
いろいろと調べることにした。cocoonは一応動作はしているので、gemがインストールされていないとか、環境設定が悪いとかそんなことではなさそうだった。まずは公式のissueから探っていった。
https://github.com/nathanvda/cocoon/issues/275
https://github.com/nathanvda/cocoon/issues/353
これが一番参考になりそうだった。↓
開発者のnathanvdaさんがいろいろと回答していた。
from your code, I thought it could be a turbolinks problem? (using the ready event only, but in that case the click-event would also not be captured)
there is, by coincidence, not another event-handler that captures it first and stops propagation?
railsのturbolinksにまつわる問題なのでは?callbackイベントの前に別のイベントが入って邪魔をしているのでは?
質問者から回答があった。
dskvr commented on 21 Dec 2016
I feel like an idiot.
jQuery was being included twice, I didn't confirm this was the exact because I need to move like the wind at this point, but what I speculate is that the listeners were bound on a different instance of jQuery than Cocoon was firing the events from. How I didn't notice this before I have no idea. It took an accident and some snooping in adjacent partials to notice the discrepancy. (I hit my head against the table at the cafe for a good 10 minutes.)
カフェで10分ほど頭をゴンゴンやっていたらしい。そこではない。「ちょっと勘違いしてたわ。jQueryを二重に読み込んでいたよ。なんできづかなかったかというと、ほにゃらら...」ちょっと英語は苦手だ。
stack over flowでもcocoonで検索してみた。「[cocoon-gem] callback」でいっぱいでてくる。
驚きなのが大体の回答欄で開発者のひとが答えていることだった。このひとすごいな。
だいたいポイントは一緒だった。
- 単純なスペルミス
- callbackを使うには要素をdivで囲んでおく必要がある
- turbolinksの問題かもしれないので記載方法に注意する
修正
回答を見ながら自分のコードを修正していった。viewファイルはそのままにした。
<!-- 略 -->
<%= form_with model: @report, local: true do |f| %>
<p>登録日:<%= f.date_field :reported_on , value:Date.today%></p>
<div id="report_items">
<%= f.fields_for :report_items do |item| %>
<%= render "report_item_fields", f: item %>
<% end %>
<div class="links">
<%= link_to_add_association "追加", f, :report_items %>
</div>
</div>
<p>コメント:<%= f.text_field :content %></p>
<%= f.submit "登録" %>
<% end %>
<!-- 略 -->
JSファイルを修正した。
//$(document).ready(function() { 修正前
$(document).on("page:load turbolinks:load", function() { //修正後
//...以下略
turbolinksが読み込まれた後にイベントを出すようにした。これでもうまくいかなかった。
スペルミスはとくになかった。その証拠に
$(document).on("page:load turbolinks:load", function() {
$('#report_items').on('click', function() {
console.log("click");
});
//...略
としたら動いた。クリックイベントは動いていた。
ちょっとよくわからない。
真因
さきのgithubのissueで誰かが言っていたことを思い出した。
jQuery was being included twice
jQueryが二重に呼び出されているかもしれない。そもそもRails6はJSをどのように読み込んでいるのか。
ここが参考になった。私はそれまでapplication.jsとは別のファイル(home.js)を作成し、そこにさきのcallbackを書いていた。
変更前
app/javascript/packs/
- application.js
- home.js
そしてviewで以下のように設定していた。
<!-- 略-->
<head>
<!-- 略-->
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'home' , 'defer':'true' %>
</head>
<!-- 略-->
これがいけなかったのかもしれない。参考記事の方法1のように、application.jsにhome.jsの参照を追加させてみる。
変更後
app/javascript
- packs
- application.js
- home.js (フォルダ移動)
<!-- 略-->
<head>
<!-- 略-->
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<!-- 略-->
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("jquery")
require("@nathanvda/cocoon")
require ('home') //追加
結果
callbackが発火するようになった。よかった。
問題が起こったら公式を確認する、issueをみるというのは良いアプローチなんだなあと思った。