LoginSignup
6
1

More than 1 year has passed since last update.

[Rails6] 動的フォームの追加ボタンが反応しないで困った(cocoon)

Last updated at Posted at 2021-08-07

はじめに

動的フォームの導入からcocoonというgemに触れて、かなり導入に手間取ったので記事にして整理したいと思う。

手間取った部分

rails5の記事だがこちらの記事を参考に、rails6の環境で導入を試みたところ、link_to_remove_associationlink_to_add_associationのボタンが反応しない事に悩まされた。

結論として

cocoonの導入で問題があった。
こちらの記事を参考して、app/jacascript/src/cocoon.jsを作成して、app/javascript/packs/application.jsrequire("src/cocoon")を追加することで解決した。

cocoonの導入

jqueryこちらを参考に導入。

Gemfile.
gem "cocoon"

Gemfilecocoonを追加。
jquery-railsは追加せず
bundle installをしてgemをインストール。

次にターミナルに移る。

$ yarn add @nathanvda/cocoon 

yarncocoonを追加。

他の方の記事ではyarn add github:nathanvda/cocoon#c24ba53
をコミットIDを指定しているやり方もあったのだが、公式を見てみるとrails6の導入方法が書かれていたので公式の方を叩く。

補足としてyarn add github:nathanvda/cocoon#c24ba53は、公式がrails5までしか対応していないための手段である事と、自分の環境ではzsh: no matches found: github:nathanvda/cocoon#c24ba53と出てしまいコマンドが実行されなかった経緯があった。

公式にある導入の続き。

app/javascripts/packs/application.js
require("jquery")
require("@nathanvda/cocoon")

ここまでが公式にあるrails6に対応したcocoonの導入になる。

これだけではボタンが反応してくれなかったので、下記を追加。
まずターミナルに移動してフォルダとファイルを作成

# srcを作成
mkdir app/javascript/src

# cocoon.jsを作成
touch app/javascript/src/cocoon.js

cocoon.jsの中身を書いていく。

app/javascript/src/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を読み込む記述を追加。

app/javascript/packs/application.js
import 'cocoon-js';
require ("src/cocoon")

中身を理解せずに解決してしまっているので不安なところがあるが、とりあえずこれでlink_to_remove_associationlink_to_add_associationが反応するようになったので参考までに。

参考元

公式ドキュメント
公式issues#555
【Rails】フォームの追加・削除(cocoon)
Rails6でのjQuery導入方法

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1