Stimulus リファレンス: コントローラー

:::note
この記事は[Google翻訳](https://translate.google.co.jp/)の結果を編集したものです。
:::

https://stimulus.hotwired.dev/reference/controllers

コントローラーはStimulusアプリケーションの基本的な組織単位です。

```javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  // …
}
```

コントローラーはアプリケーションで定義するJavaScriptクラスのインスタンスです。各コントローラークラスは`@hotwired/stimulus`モジュールによってエクスポートされた`Contoller`基底クラスを継承します。

# プロパティ

すべてのコントローラーはStimulusアプリケーションインスタンスに属し、HTML要素に関連付けられています。 コントローラークラス内ではコントローラーの以下にアクセスできます。

- `this.application`プロパティを介したアプリケーション
- `this.element`プロパティを介したHTML要素
- `this.identifier`プロパティを介した識別子

# モジュール

ファイルごとに1つずつJavaScriptモジュールでコントローラークラスを定義します。上記の例のように各コントローラークラスをモジュールのデフォルトオブジェクトとしてエクスポートします。

これらのモジュールを`controllers/`ディレクトリに配置します。ファイルに`[identifier]_controller.js`という名前を付けます。`[identifier]`は各コントローラーの識別子に対応します。

# 識別子

識別子はHTMLでコントローラークラスを参照するために使用する名前です。

`data-controller`属性を要素に追加すると、Stimulusは属性の値から識別子を読み取り、対応するコントローラークラスの新しいインスタンスを作成します。

たとえば、この要素には`controllers/reference_controller.js`で定義されたクラスのインスタンスであるコントローラーがあります。

```html
<div data-controller="reference"></div>
```

以下はStimulusがそのコンテキストでコントローラーの識別子を生成する方法の例です。

| コントローラーファイルの名前が... | その識別子は... |
| :-- | :-- |
| clipboard_controller.js | clipboard |
| date_picker_controller.js | date-picker |
| users/list_item_controller.js | users--list-item |
| local-time-controller.js | local-time |

# スコープ

Stimulusがコントローラーを要素に接続すると、その要素とそのすべての子がコントローラーのスコープを構成します。

たとえば以下の`<div>`と`<h1>`はコントローラーのスコープの一部ですが、まわりの`<main>`要素はそうではありません。

```html
<main>
  <div data-controller="reference">
    <h1>Reference</h1>
  </div>
</main>
```

# ネストされたスコープ

ネストされている場合、各コントローラーはネストされているコントローラーのスコープを除いて、独自のスコープのみを認識します。

たとえば以下の`#parent`コントローラーは、そのスコープ内の`item`ターゲットのみを認識し、`#child`コントローラーのターゲットは認識しません。

```html
<ul id="parent" data-controller="list">
  <li data-list-target="item">One</li>
  <li data-list-target="item">Two</li>
  <li>
    <ul id="child" data-controller="list">
      <li data-list-target="item">I am</li>
      <li data-list-target="item">a nested list</li>
    </ul>
  </li>
</ul>
```

# 複数のコントローラー

`data-controller`属性の値はスペースで区切られた識別子のリストです。

```html
<div data-controller="clipboard list-item"></div>
```

ページ上の特定の要素に多くのコントローラーがあるのはよくあることです。上記の例では`<div>`に`clipboard`と`list-item`の2つのコントローラーが接続されています。

同様にページ上の複数の要素が同じコントローラークラスを参照することもよくあります。

```html
<ul>
  <li data-controller="list-item">One</li>
  <li data-controller="list-item">Two</li>
  <li data-controller="list-item">Three</li>
</ul>
```

それぞれの`<li>`には`list-item`コントローラーの独自のインスタンスがあります。

# 命名規則

コントローラークラスのメソッド名とプロパティ名には常にcamelCaseを使用します。

識別子が複数の単語で構成されている場合は単語をkebab-caseで記述します（つまりダッシュを使用して: `date-picker`、`list-item`）。

ファイル名ではアンダースコアまたはダッシュを使用して複数の単語を区切ります（snake_caseまたはkebab-case: `controllers/date_picker_controller.js、controllers/list-item-controller.js`）。

# 登録

Stimulus for Railsを`@hotwired/stimulus-webpack-helpers`パッケージと一緒にインポートマップまたはWebpackと共に使用する場合、アプリケーションは上記の規則に従ってコントローラークラスを自動的に読み込み登録します。

そうでない場合、アプリケーションは各コントローラークラスを手動でロードして登録する必要があります。

## コントローラーを手動で登録する

識別子を使用してコントローラークラスを手動で登録するには、まずクラスをインポートしてからアプリケーションオブジェクトで`Application#register`メソッドを呼び出します。

```javascript
import ReferenceController from "./controllers/reference_controller"

application.register("reference", ReferenceController)
```

モジュールからインポートする代わりにコントローラークラスをインラインで登録することもできます。

```javascript
import { Controller } from "@hotwired/stimulus"

application.register("reference", class extends Controller {
  // …
})
```

## 環境要因に基づく登録の防止

特定の環境要因（特定のユーザー エージェントなど）が満たされた場合にのみコントローラーを登録してロードする場合は、静的`shouldLoad`メソッドを上書きできます。

```javascript
class UnloadableController extends ApplicationController {
  static get shouldLoad() {
    return false
  }
}

// This controller will not be loaded
application.register("unloadable", UnloadableController)
```

## コントローラー登録時のトリガー動作

コントローラーが登録された後に何らかの動作をトリガーしたい場合、静的な`afterLoad`メソッドを追加できます。

```javascript
class SpinnerButton extends Controller {
  static afterLoad(identifier, application) {
    // use the application instance to read the configured 'data-controller' attribute
    const { controllerAttribute } = application.schema

    // update any legacy buttons with the controller's registered identifier
    const updateLegacySpinners = () => {
      document.querySelector(".legacy-spinner-button").forEach((element) => {
        element.setAttribute(controllerAttribute, identifier)
      })
    }

    // called as soon as registered so DOM may not have loaded yet
    if (document.readyState == "loading") {
      document.addEventListener("DOMContentLoaded", updateLegacySpinners)
    } else {
      updateLegacySpinners()
    }
  }
}

// This controller will update any legacy spinner buttons to use the controller
application.register("spinner-button", SpinnerButton)
```

`afterLoad`メソッドはDOMに制御対象の要素が存在しない場合でも、コントローラーが登録されるとすぐに呼び出されます。 コントローラーとStimulusアプリケーションインスタンスを登録するときに使用された識別子で呼び出されます。

# イベントとのクロスコントローラー調整

コントローラーが相互に通信する必要がある場合はイベントを使用する必要があります。`Controller`クラスにはこれを簡単にする`dispatch`と呼ばれる便利なメソッドがあります。最初の引数として`eventName`を取り、コロンで区切られたコントローラーの名前が自動的に前に付けられます。ペイロードは詳細に保持されます。それはこのように動作します：

```javascript
class ClipboardController extends Controller {
  static targets = [ "source" ]

  copy() {
    this.dispatch("copy", { detail: { content: this.sourceTarget.value } })
    navigator.clipboard.writeText(this.sourceTarget.value)
  }
}
```

そして、このイベントを別のコントローラーのアクションにルーティングできます。

```html
<div data-controller="clipboard effects" data-action="clipboard:copy->effects#flash">
  PIN: <input data-clipboard-target="source" type="text" value="1234" readonly>
  <button data-action="clipboard#copy">Copy to Clipboard</button>
</div>
```

`dispatch`は次のように2番目のパラメーターとして追加のオプションを受け入れます。

| オプション | デフォルト | 注釈 |
| :-- | :-- | :-- |
| `detail` | `{}` 空のオブジェクト | [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail)を参照 |
| `target` | `this.element` | [Event.target](https://developer.mozilla.org/en-US/docs/Web/API/Event/target)を参照 |
| `prefix` | `this.identifier` | プレフィックスが`false`の場合（`null`または`false`など）、`eventName`のみが使用されます 文字列値を指定すると指定された文字列とコロンが`eventName`の先頭に追加されます。|
| `bubbles` | `true` | [Event.bubbles](https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles)を参照
| `cancelable` | `true` | [Event.cancelable](https://developer.mozilla.org/en-US/docs/Web/API/Event/cancelable)を参照 |

`dispatch`は生成された[`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent)を返します。これを使用して次のように他のリスナーによってイベントをキャンセルする方法を提供できます。

```javascript
class ClipboardController extends Controller {
  static targets = [ "source" ]

  copy() {
    const event = this.dispatch("copy", { cancelable: true })
    if (event.defaultPrevented) return
    navigator.clipboard.writeText(this.sourceTarget.value)
  }
}
```

```javascript
class EffectsController extends Controller {
  flash(event) {
    // this will prevent the default behaviour as determined by the dispatched event
    event.preventDefault()
  }
}
```

# 他のコントローラを直接呼び出す

何らかの理由でイベントを使用してコントローラー間で通信できない場合は、アプリケーションから`getControllerForElementAndIdentifier`メソッドを介してコントローラーインスタンスにアクセスできます。 これはイベントを使用するより一般的な方法では解決できない固有の問題がある場合にのみ使用する必要がありますが、必要な場合は次のようにします。

```javascript
class MyController extends Controller {
  static targets = [ "other" ]

  copy() {
    const otherController = this.application.getControllerForElementAndIdentifier(this.otherTarget, 'other')
    otherController.otherMethod()
  }
}
```