Rails: Stimulusのカスタムアクションオプションは同時に複数指定できる
Stimulusでは、data-action
のアクションに独自のオプションを登録できます。以下の記事でも示したように、Stimulusのアクションにはkeypress->input#validate:prevent
のようなさまざまなオプションを追加できます。
: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-action
でclick->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
というオプションが指定されていることにお気づきでしょうか?これについても以下の記事で解説しました。
🔗 例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
が何のことだかわからない方へ: 私が執筆中の本(絶賛予約受付中)をお読みいただければもう恐くありません。
それでは、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という小さなパッケージにまとめて公開しています。 👀
概要
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。