inuinu blog(開発用)

BOT @wagagun の開発ノウハウや、IT向け?の雑記ブログです。

【Ruby on Rails7】cocoonを導入(その3:バリデーション問題解決:Stimulusへ移動編)

この記事では、Ruby on Rails7のgemパッケージcocoonについて、導入・カスタマイズ行います。
今回もバリデーションエラー時に発生する問題の解決策なのですが、もっと根本的に解決する手法を考えていきます。

前回の続き。

inuinu-tech.hatenablog.jp

前回は、すでにcocoonを導入し、Rails7へアップグレードした方向けの内容ですが、今回は、Rails7ではじめてcocoonを導入した方向けの内容になります。

前回のおさらい

一見して解決したように見えるが、取ってつけた感が強い

前回の手法は、すでにcocoonを導入し、Rails7へアップグレードしたのであれば、当座の障害解決として、これでゴールなのかもしれません。
ただ、作法的に、これで良いのか? ちょっとしっくりこない気分はあります。

Stimulusのconnect()内にcocoonのロジックを移動

Stimulusだと、どんな状況でも発火するのであれば、cocoonのロジックをまるごと移動させればいいのでは?

この考え方で上手く動作するのか?確認してみましょう。

各ファイルを変更・追加する

app/javascript/controllers/index.jsを書き換えます。

// app/javascript/controllers/index.js
//import CcReloadController from "controllers/ccreload_controller"
//application.register("ccreload", CcReloadController)
import TaskFormController from "controllers/task_form_controller"
application.register("task_form", TaskFormController)

前回のCcReloadControllerの2行はコメントにし、TaskFormControllerの2行を追加します。

次に、app/views/tasks/_form.html.hamltask_formに変更します。

.div{data: {controller: "task_form"}}

さらに、app/javascript/controllers/task_form_controller.jsを変更します。

import { Controller } from "@hotwired/stimulus"
//import jquery from "jquery"

//import * as common from './共通部品のJSファイル.js'

export default class extends Controller {

  connect() {
    // 共通変数の定義など

    // stimulusにはonloadの概念がなさそう
    //$(window).on("page:load", function(){
      // こちらにonload処理を記述
    //});

    $('子供のID').on('cocoon:before-insert', function() {
      console.log("cocoon:before-insert!");
    })
    .on('cocoon:after-insert', function() {
      console.log("cocoon:after-insert!");
    })
    .on('cocoon:before-remove', function() {
      console.log("cocoon:before-remove!");
    })
    .on('cocoon:after-remove', function() {
      console.log("cocoon:after-remove!");
    });

  } // connect()

} // export default class extends Controller

実行前に、いくつか解説します。
JQueryはimportしなくても大丈夫はなずです。
//import * as common from './共通部品のJSファイル.js'については、あとで解説します。
$(window).on(〜については、connect()時点ですでにロードされていると思いますので、不要です。

●app/javascript/application.js

// app/javascript/application.js
$(document).on('ready page:load turbolinks:load turbo:load', function() {
(以下略)

にあったready page:load turbolinks:load turbo:load的な行も不要になります。

app/javascript/application.jsのロジックは除去する

テストの邪魔になりますので、前々回に追加したapp/javascript/application.jscocoonのロジックは除去しておきましょう。

動作確認

サーバーを起動し、行追加・削除のボタンを押下し、ブラウザのコンソールに文言が表示されたら、動作確認は完了です!

Stimulusのメリットを再認識する

Stimulusを利用すると、自然と、ビュー単位でJavascriptのファイルを分割できるようになります。

ファイルの分割で悩んでいた方には福音かもしれません。

参考:共通箇所を別のファイルへ移動する

ファイルがビュー毎に分割してしまうと、共通的なfunctionがダブってしまうという副作用が生じると思います。
放っておくと、保守が大変そうですね。

こちらは、cocoonの機能でも、Stimulusの機能でもなく、VanillaなJavascriptの仕様ですが、export/importを使って、共通functionを別ファイルへ移動しましょう。

実装例

以下は、app/javascript/controllers/task_form_controller.jsでの実装例です。

import { Controller } from "@hotwired/stimulus"
//import jquery from "jquery"

import * as cmn from './common.js'

export default class extends Controller {

  connect() {
    // 共通変数の定義など

    // stimulusにはonloadの概念がなさそう
    //$(window).on("page:load", function(){
      // こちらにonload処理を記述
      const foo = cmn.hoge();
    //});

    $('子供のID').on('cocoon:before-insert', function() {
      console.log("cocoon:before-insert!");
    })
    .on('cocoon:after-insert', function() {
      console.log("cocoon:after-insert!");
    })
    .on('cocoon:before-remove', function() {
      console.log("cocoon:before-remove!");
    })
    .on('cocoon:after-remove', function() {
      console.log("cocoon:after-remove!");
    });

  } // connect()

} // export default class extends Controller

●app/javascript/controllers/common.js

export function hoge() {
  // 処理
  return 'abc';
} // function

上記例ではcommon.jsをimportし、common.jsの中にあるhoge() を実行しています。

これでかなりスッキリすると思います。

参考:JQueryではなく、Vanillaな書式に変更したい場合

JQueryはちょっとなあ…という方へ、参考までにVanillaなJavascript表記もアップしておきます。

●app/javascript/controllers/task_form_controller.js

import { Controller } from "@hotwired/stimulus"

//import * as cmn from './common.js'

export default class extends Controller {

  connect() {
    // 共通変数の定義など

    // stimulusにはonloadの概念がなさそう
    // こちらにonload処理を記述

    let id_obj = document.querySelector('#tasks'); // どちらか
    let id_obj = document.getElementById('tasks'); // お好みで

    // 追加処理前
    id_obj.addEventListener("cocoon:before-insert",function () {
      console.log("cocoon:before-insert!");
    }); // cocoon:before-insert

    // 追加処理後
    id_obj.addEventListener("cocoon:after-insert",function () {
      console.log("cocoon:after-insert!");
    }); // cocoon:after-insert

    // 削除処理前
    id_obj.addEventListener("cocoon:before-remove",function () {
      console.log("cocoon:before-remove!");
    }); // cocoon:before-remove

    // 削除処理後
    id_obj.addEventListener("cocoon:after-remove",function () {
      console.log("cocoon:after-remove!");
    }); // cocoon:after-remove

  } // connect()

} // export default class extends Controller

結果、ちゃんと動きました。

いくつかのブログを見ると、「addEventListenerではなく、data-actionというdatasetで定義して…」と書いてあったりして、できないのかな?と思いましたが、connect()にaddEventListenerは書けますね。

cocoonはおおもとの部分はJQueryをインストールしないとダメなのかもしれませんが、ユーザー定義の箇所についてはVanilla表記は可能なようです。

ただ、JQueryのようにメソッドチェーンで記述したら怒られました。
できないんですかね?

次回

次回は、cocoonを導入した際につまずきやすい箇所について、解決方法を考えていきます。

inuinu-tech.hatenablog.jp