新しい Modal を実装する場合は、メインのドキュメントをこちらでご覧ください。本トピックでは、既存のコントローラーベースの Modal を新しいコンポーネントベースの API に移行する方法を説明しています。
過去、Discourse は Modal のレンダリングに Ember のコントローラーベースの API を使用していました。Modal を呼び出すには、コントローラーの名前を含む文字列を showModal() に渡していました。内部では、これは Ember の Route#renderTemplate API を利用していましたが、これは Ember 3.x で非推奨となり、Ember 4.x で削除される予定です。
Discourse が Ember 4.x 以降にアップグレードできるようにするため、Modal 用の新しいコンポーネントベースの API を導入しました。この新しい API は Ember の「宣言的」な設計パターンを採用し、クリーンな DDAU(Data Down, Actions Up)セマンティクスを提供することを目的としています。
ステップ 1: ファイルの移動
コントローラーの JS ファイルとテンプレートファイルを /components/modal ディレクトリに移動します。これにより、それらは「コロケーションコンポーネント」となり、他の JS モジュールと同様にインポート可能になります。
ステップ 2: JS ファイルの更新
次に、コンポーネントの JS 定義を @ember/controller ではなく @ember/component を継承するように更新します [1]。ModalFunctionality ミックスインを削除し、その関数の使用箇所は以下の表に従って更新してください:
| 変更前 | 変更後 |
|---|---|
flash() と clearFlash() |
コンポーネント内に flash プロパティを作成し、<DModal> の @flash 引数に渡します。デフォルトでは、アラートは alert クラス('error' クラスのコピー)でスタイル設定されますが、@flashType 引数を使用して上書きできます。 |
showModal() |
discourse/lib/show-modal から showModal 関数をインポートします |
closeModal アクション |
コンポーネントに自動的に渡される closeModal 引数を呼び出します |
旧スタイルの Modal コントローラーは「永遠に」存在し続けたため、状態の手動クリーンアップが必要でした。新しいコンポーネントベースの API では、コンポーネントは Modal の表示/非表示時に作成・破棄されます。多くの場合、これは従来のライフサイクルフックが不要になることを意味します。
ライフサイクルベースのロジックがまだ必要な場合は、以下の表を使用してください:
| 変更前 | 変更後 |
|---|---|
onShow() |
標準的な Ember コンポーネントのライフサイクル(init() または Ember モディファイア)を使用します |
afterRender |
標準的な Ember コンポーネントのライフサイクル(init() または Ember モディファイア)を使用します |
beforeClose() |
コンポーネントに渡される @closeModal 引数のラッパーを作成します。クローズラッパーの参照を DModal に渡します。例: <DModal @closeModal={{this.myCloseModalWrapper}}> |
onClose() |
標準的な Ember コンポーネントのライフサイクル(willDestroy() または Ember モディファイア)を使用します |
ステップ 3: テンプレートの更新
<DModalBody> ラッパーを <DModal> に置き換えます。いくつかの新しい属性を追加します:
- 新しい
@closeModal引数をそのまま渡します - 明示的なクラスを追加します。旧来の動作と一致させるには、コントローラーのファイル名に
-modalを追加します。
例えば、Modal コントローラーが close-topic.js と呼ばれていた場合、新しい <DModal> の呼び出しは次のようになります:
<DModal @closeModal={{@closeModal}} class="close-topic-modal">
DModalBody の呼び出しに他の引数が含まれている場合は、以下の表に基づいて更新してください:
| 変更前 | 変更後 |
|---|---|
@title="title_key" |
@title={{i18n "title_key"}} |
@rawTitle="translated title" |
@title="translated title" |
@subtitle="subtitle_key" |
@subtitle={{i18n "subtitle_key"}} |
@rawSubtitle="translated subtitle" |
@subtitle="translated subtitle" |
@class |
@bodyClass |
@modalClass |
通常の HTML 属性を使用してアングルブラケット構文を使用: <DModal class="blah"> |
@titleAriaElementId |
通常の HTML 属性を使用してアングルブラケット構文を使用: <DModal aria-labelledby="blah"> |
@dismissable, @submitOnEnter, @headerClass |
変更なし |
旧来の <DModalBody> コンポーネントの後にフッターコンテンツがレンダリングされていた場合、新しい <:footer> 名前付きブロックを使用して <DModal> 内に導入してください。名前付きブロックを使用する場合、本文コンテンツは <:body></:body> でラップする必要があります。例:
<DModal @closeModal={{@closeModal}}>
<:body>
Hello world, this is the content of the modal
</:body>
<:footer>
This is the footer content. A `.modal-footer` wrapper will be added
automatically
</:footer>
</DModal>
ステップ 4: showModal の呼び出し箇所を更新
以前は、Modal は showModal API を使用してレンダリングされ、文字列(コントローラー名)と複数のオプションを受け取りました。これは、操作可能なコントローラーのインスタンスを返しました:
import showModal from "discourse/lib/show-modal";
export default class extends Component {
showMyModal() {
const controller = showModal("my-modal", {
title: "My Modal Title",
modalClass: "my-modal-class",
model: { topic: this.topic },
});
controller.set("updateTopic", this.updateTopic);
});
}
新しいコンポーネントベースの Modal をレンダリングするには、「modal」サービス(または getOwner(this).lookup("service:modal") のような方法でアクセスする)をインジェクションし、show() 関数を呼び出す必要があります。
show() は、新しいコンポーネントクラスの参照を最初の引数として受け取ります。サポートされる唯一のオプションは ‘model’ で、Modal に必要なすべてのデータ/アクションを渡すために使用できます。
コンポーネントインスタンスの参照は返されません。代わりに、show() は Modal がクローズされたときに解決されるプロミスを返します。プロミスは、@closeModal に渡されたデータで解決されます。
import MyModal from "discourse/components/my-modal";
import { service } from "@ember/service";
export default class extends Component {
@service modal;
showMyModal() {
this.modal.show(MyModal, {
model: { topic: this.topic, updateTopic: this.updateTopic },
});
});
}
あるいは、メインの DModal ドキュメントに記載されている宣言的 API に移行することもできます。
旧オプションの機能は、次のように再現できます:
旧 showModal オプション |
解決策 |
|---|---|
admin |
コンポーネントには該当しません - 削除してください |
templateName |
コンポーネントには該当しません - 削除してください |
title |
<DModal @title={{i18n "blah"}}> に移動します |
titleTranslated |
<DModal @title="blah"> に移動します。必要に応じて、model のデータに基づいて計算することもできます |
modalClass |
<DModal class="blah"> に移動します |
titleAriaElementId |
<DModal aria-labelledby="blah"> に移動します |
panels |
コンポーネント内でタブを実装するために <:headerBelowTitle> 名前付きブロックを使用します (例) |
model |
変更なし |
ステップ 5: テスト
テストは基本的に同じままで大丈夫です。最も一般的な問題は以下の通りです:
-
Modal はもはや名前に基づくデフォルトのクラスを持たなくなりました。クラスはテンプレート内で明示的に指定する必要があります(ステップ 3 の冒頭参照)
-
Modal がクローズされたとき、
d-modalラッパーはもはや DOM に残らなくなりました。すべての Modal がクローズされていることを確認するには、assert.dom('.d-modal').doesNotExist()のようなチェックを使用してください
完了!
これにより、Modal は以前と同じように動作するはずです。新しい API の利点をさらに活かすには、showModal の呼び出しを宣言的戦略に置き換える こと、および Modal を Glimmer コンポーネントに変換することを検討してください。
例
以下は、Discourse コアの一部の Modal を新しい API に変換するデモとなるコミットの例です:
このドキュメントはバージョン管理されています - 変更提案は GitHub でお願いします。
このガイドでは、Ember コントローラーからの最も簡単な移行パスを提供するため、Classic Ember Components(クラシック Ember コンポーネント)が推奨されています。ただし、シンプルな Modal の場合、またはリファクタリングに時間を費やすことに抵抗がない場合は、モダンな Glimmer コンポーネントの方がより良い選択です。 ↩︎