#はじめに
動的フォームの導入からcocoon
というgemに触れて、かなり導入に手間取ったので記事にして整理したいと思う。
#手間取った部分
rails5の記事だがこちらの記事を参考に、rails6の環境で導入を試みたところ、link_to_remove_association
やlink_to_add_association
のボタンが反応しない事に悩まされた。
#結論として
cocoon
の導入で問題があった。
こちらの記事を参考して、app/jacascript/src/cocoon.js
を作成して、app/javascript/packs/application.js
にrequire("src/cocoon")
を追加することで解決した。
#cocoonの導入
jquery
はこちらを参考に導入。
gem "cocoon"
Gemfile
にcocoon
を追加。
jquery-rails
は追加せず
bundle install
をしてgemをインストール。
次にターミナルに移る。
$ yarn add @nathanvda/cocoon
yarn
にcocoon
を追加。
他の方の記事ではyarn add github:nathanvda/cocoon#c24ba53
をコミットIDを指定しているやり方もあったのだが、公式を見てみるとrails6
の導入方法が書かれていたので公式の方を叩く。
補足としてyarn add github:nathanvda/cocoon#c24ba53
は、公式がrails5
までしか対応していないための手段である事と、自分の環境ではzsh: no matches found: github:nathanvda/cocoon#c24ba53
と出てしまいコマンドが実行されなかった経緯があった。
公式にある導入の続き。
require("jquery")
require("@nathanvda/cocoon")
ここまでが公式にあるrails6
に対応したcocoon
の導入になる。
これだけではボタンが反応してくれなかったので、下記を追加。
まずターミナルに移動してフォルダとファイルを作成
# srcを作成
mkdir app/javascript/src
# cocoon.jsを作成
touch app/javascript/src/cocoon.js
cocoon.jsの中身を書いていく。
(function($) {
var cocoon_element_counter = 0;
var create_new_id = function() {
return (new Date().getTime() + cocoon_element_counter++);
}
var newcontent_braced = function(id) {
return '[' + id + ']$1';
}
var newcontent_underscord = function(id) {
return '_' + id + '_$1';
}
var getInsertionNodeElem = function(insertionNode, insertionTraversal, $this){
if (!insertionNode){
return $this.parent();
}
if (typeof insertionNode == 'function'){
if(insertionTraversal){
console.warn('association-insertion-traversal is ignored, because association-insertion-node is given as a function.')
}
return insertionNode($this);
}
if(typeof insertionNode == 'string'){
if (insertionTraversal){
return $this[insertionTraversal](insertionNode);
}else{
return insertionNode == "this" ? $this : $(insertionNode);
}
}
}
$(document).on('click', '.add_fields', function(e) {
e.preventDefault();
e.stopPropagation();
var $this = $(this),
assoc = $this.data('association'),
assocs = $this.data('associations'),
content = $this.data('association-insertion-template'),
insertionMethod = $this.data('association-insertion-method') || $this.data('association-insertion-position') || 'before',
insertionNode = $this.data('association-insertion-node'),
insertionTraversal = $this.data('association-insertion-traversal'),
count = parseInt($this.data('count'), 10),
regexp_braced = new RegExp('\\[new_' + assoc + '\\](.*?\\s)', 'g'),
regexp_underscord = new RegExp('_new_' + assoc + '_(\\w*)', 'g'),
new_id = create_new_id(),
new_content = content.replace(regexp_braced, newcontent_braced(new_id)),
new_contents = [],
originalEvent = e;
if (new_content == content) {
regexp_braced = new RegExp('\\[new_' + assocs + '\\](.*?\\s)', 'g');
regexp_underscord = new RegExp('_new_' + assocs + '_(\\w*)', 'g');
new_content = content.replace(regexp_braced, newcontent_braced(new_id));
}
new_content = new_content.replace(regexp_underscord, newcontent_underscord(new_id));
new_contents = [new_content];
count = (isNaN(count) ? 1 : Math.max(count, 1));
count -= 1;
while (count) {
new_id = create_new_id();
new_content = content.replace(regexp_braced, newcontent_braced(new_id));
new_content = new_content.replace(regexp_underscord, newcontent_underscord(new_id));
new_contents.push(new_content);
count -= 1;
}
var insertionNodeElem = getInsertionNodeElem(insertionNode, insertionTraversal, $this)
if( !insertionNodeElem || (insertionNodeElem.length == 0) ){
console.warn("Couldn't find the element to insert the template. Make sure your `data-association-insertion-*` on `link_to_add_association` is correct.")
}
$.each(new_contents, function(i, node) {
var contentNode = $(node);
var before_insert = jQuery.Event('cocoon:before-insert');
insertionNodeElem.trigger(before_insert, [contentNode, originalEvent]);
if (!before_insert.isDefaultPrevented()) {
// allow any of the jquery dom manipulation methods (after, before, append, prepend, etc)
// to be called on the node. allows the insertion node to be the parent of the inserted
// code and doesn't force it to be a sibling like after/before does. default: 'before'
var addedContent = insertionNodeElem[insertionMethod](contentNode);
insertionNodeElem.trigger('cocoon:after-insert', [contentNode,
originalEvent]);
}
});
});
$(document).on('click', '.remove_fields.dynamic, .remove_fields.existing', function(e) {
var $this = $(this),
wrapper_class = $this.data('wrapper-class') || 'nested-fields',
node_to_delete = $this.closest('.' + wrapper_class),
trigger_node = node_to_delete.parent(),
originalEvent = e;
e.preventDefault();
e.stopPropagation();
var before_remove = jQuery.Event('cocoon:before-remove');
trigger_node.trigger(before_remove, [node_to_delete, originalEvent]);
if (!before_remove.isDefaultPrevented()) {
var timeout = trigger_node.data('remove-timeout') || 0;
setTimeout(function() {
if ($this.hasClass('dynamic')) {
node_to_delete.detach();
} else {
$this.prev("input[type=hidden]").val("1");
node_to_delete.hide();
}
trigger_node.trigger('cocoon:after-remove', [node_to_delete,
originalEvent]);
}, timeout);
}
});
$(document).on("ready page:load turbolinks:load turbo:load", function() {
$('.remove_fields.existing.destroyed').each(function(i, obj) {
var $this = $(this),
wrapper_class = $this.data('wrapper-class') || 'nested-fields';
$this.closest('.' + wrapper_class).hide();
});
});
})(jQuery);
yarn
にもcocoon-js
を追加
yarn add cocoon-js
最後にcocoon.js
を読み込む記述を追加。
import 'cocoon-js';
require ("src/cocoon")
中身を理解せずに解決してしまっているので不安なところがあるが、とりあえずこれでlink_to_remove_association
とlink_to_add_association
が反応するようになったので参考までに。
#参考元
公式ドキュメント
公式issues#555
【Rails】フォームの追加・削除(cocoon)
Rails6でのjQuery導入方法