Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: Stimulusのカスタムアクションオプションは同時に複数指定できる

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rails: Stimulusのカスタムアクションオプションは同時に複数指定できる

Stimulusでは、data-actionのアクションに独自のオプションを登録できます。以下の記事でも示したように、Stimulusのアクションにはkeypress->input#validate:preventのようなさまざまなオプションを追加できます。

Stimulusで知っておきたい10の機能(翻訳)

:preventの部分がアクションのオプションで、ここではデフォルトのイベントを抑制します(event.preventDefault()を追加するのと同等の効果です)。この他に:stop:selfというオプションも指定できます。

Stimulusでは、この他にもさまざまな独自オプションを作成することで、Stimulusを徹底的に使い倒せるようになります(「控えめなJavaScriptフレームワーク」と呼ばれているStimulusでここまでできると誰が想像したでしょうか?)。

本記事では、皆さんが独自のオプションを生み出すヒントになるよう、この機能でできることをいくつか示したいと思います。

🔗 基本的な使い方

カスタムのアクションオプションはApplication.registerActionOptionメソッドで作成できます。このメソッドは、追加したロジックに応じてtrueまたはfalseを返します。

基本的な使い方は以下のような感じになります。

// app/javascript/controllers/application.js
import { Application } from "@hotwired/stimulus"

const application = Application.start()

application.registerActionOption("fire", ({ event, value }) => {
  // trueかfalseを返す任意のロジックをここに書く
}

trueが返されると、追加したアクションが実行可能になります。使い方は、data-actionclick->controller#action:fireのように指定します。

基本を押さえたところで、今度は実際の利用方法の例をいくつか見てみましょう。

🔗 例1: WhenOutside(外の場合)オプション

「要素の外側をクリックするとその要素を非表示にする」という操作はよく使われます(モーダルやドロップダウンの外側をクリックするなど)。この場合のロジックは以下のようになります。

application.registerActionOption("whenOutside", ({ event, element }) => {
  return !element.contains(event.target);
});

対応するStimulusコントローラは以下のようになるでしょう。

// app/javascript/controllers/dropdown_controller.js
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["menu"];

  show() {
    this.menuTarget.removeAttribute("hidden");
  }

  hide() {
    this.menuTarget.setAttribute("hidden", true);
  }
}

HTMLでは以下のように書きます。

<div data-controller="dropdown">
  <button data-action="dropdown#show:stop">Show</button>

  <ul data-dropdown-target="menu" data-action="click@window->dropdown#hide:whenOutside">
  </ul>
</div>

data-dropdown-target="menu"を持つUI要素の外側をクリックした場合にのみ、そのUI要素が非表示になります。
ここで:stopというオプションが指定されていることにお気づきでしょうか?これについても以下の記事で解説しました。

Stimulusで知っておきたい10の機能(翻訳)

🔗 例2: throttledオプション

以下のdropdown_controller.jsファイルにトグル用のメソッドがあり、あなたは何らかの理由で1000ms経過するたびにそれをトグルしたいとしましょう(理由は聞かないでください!)。

// app/javascript/controllers/dropdown_controller.js
const throttles = new WeakMap();

application.registerActionOption("throttled", ({ element }, { wait = 1000 } = {}) => {
  if (!throttless.has(element)) {
    throttles.set(element, 0);
  }

  const now = Date.now();
  const lastRun = throttles.get(element);

  if (now - lastRun >= wait) {
    throttles.set(element, now);

    return true;
  }

  return false;
});

これは1000ms経過するごとに1回だけtrueを返します。

お知らせ

🚨WeakMapが何のことだかわからない方へ: 私が執筆中の本(絶賛予約受付中)をお読みいただければもう恐くありません。

参考: JavaScript for Rails Developers Book | Rails Designer

それでは、dropdown_controller.jsをtoggle()メソッドで拡張してみましょう。

// app/javascript/controllers/dropdown_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["menu"];

  show() {
    this.menuTarget.removeAttribute("hidden");
  }

  toggle() {
    this.menuTarget.toggleAttribute("hidden");
  }

  hide() {
    this.menuTarget.setAttribute("hidden", true);
  }
}

使い方は以下のとおりです。

<div data-controller="dropdown">
  <button data-action="dropdown#toggle:stop:throttled">表示/非表示切り替え</button>

  <ul data-dropdown-target="menu" data-action="click@window->dropdown#hide:whenOutside">
  </ul>
</div>

上で:stop:throttledのようにstopオプションとthrottledオプションを同時に指定できていることにご注目ください。

🔗 例3: WithMetakeyオプション

別の例として、メタキー(キー)やCtrlキーを押しているときだけドロップダウンを開くようにしたい場合を考えてみましょう。

application.registerActionOption("withMetaKey", ({ event }) => {
  return event.metaKey;
});

HTMLでは以下のように書きます。

<div data-controller="dropdown">
  <button data-action="dropdown#toggle:stop:throttled:withMetaKey">Show & Hide</button>

  <ul hidden data-clicker-target="menu" data-action="click@window->dropdown#hide:whenOutside">
    <li>Menu Item</li>
  </ul>
</div>

これで、コマンドキーを押しながらクリックしたときだけドロップダウンが表示されるようになります。

他にもあると思いますか?はい、あるんです。

🔗 例4: WithConfirm

ドロップダウンを表示する前に確認ダイアログを表示したい場合は、以下のように書けます。

application.registerActionOption("withConfirm", ({ element }) => {
  return confirm(element.dataset.confirm || "Are you sure?");
});

HTML側では以下のように書きます。

<div data-controller="dropdown">
  <button data-action="dropdown#toggle:stop:throttled:withMetaKey:withConfirm">Show</button>

  <ul hidden data-clicker-target="menu" data-action="click@window->dropdown#hide:whenOutside">
    <li>Menu Item</li>
  </ul>
</div>

上のカスタムアクションでは:stop:throttled:withMetaKey:withConfirmのようにすべてのオプションを指定していますが、これらはすべて動きます!
つまりこういうふうにカスタムアクションに複数のオプションを指定可能であることがポイントなのです(この例では実用的ではありませんが)。

なお、お気づきかと思いますが、私はアクションを読むときに理解しやすくするために、カスタムアクションオプションに以下のような名前をつけるようにしています。

  • click@window->dropdown#hide:whenOutside;
  • dropdown#show:withConfirm.

どんな命名にするかは、もちろん各人の好み次第です。

こうした例では、hideWithKeyのようなアクションをいちいち個別に作成しなければならなくなりがちですが、カスタムアクションオプションならその必要はありません。皆さんも実際にカスタムアクションオプションを使ってみれば、いろんな使い方が見えてくるはずです。

本記事に追加して広めるのにふさわしいカスタムアクションオプションを既にお作りでしたら、ぜひ私までお知らせください。

❗なお、私は選り抜きのカスタムアクションオプションをstimulus-fxという小さなパッケージにまとめて公開しています。 👀

Rails-Designer/stimulus-fx - GitHub

関連記事

Railsフロントエンド: ホットキーをStimulusで追加する(翻訳)

Rails: モデルのdivタグをTurbo StreamとStimulusで動的に差し替える(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。