新しいモーダルを実装する場合は、メインのドキュメントをこちらでご覧ください。このトピックでは、既存のコントローラーベースのモーダルを新しいコンポーネントベースの API に移行する方法について説明しています。
過去、Discourse ではモーダルをレンダリングするために Ember-Controller ベースの API を使用していました。モーダルを呼び出すには、コントローラーの名前を含む文字列を showModal() に渡す必要がありました。内部では、これは Ember の Route#renderTemplate API を使用しており、Ember 3.x で非推奨となり、Ember 4.x で削除されます。
Discourse が Ember 4.x 以降にアップグレードできるようにするため、モーダル用の新しいコンポーネントベースの API を導入しました。この新しい API は Ember の「宣言的」デザインパターンを採用し、クリーンな DDAU(データは下から、アクションは上へ)セマンティクスを提供することを目指しています。
ステップ 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 引数を呼び出します |
旧式のモーダルコントローラーは「永久」に存在するため、状態のクリーンアップを手動で行う必要がありました。新しいコンポーネントベースの API では、モーダルが表示/非表示になるときにコンポーネントが作成され、破棄されます。多くの場合、これは古いライフサイクルフックが不要になったことを意味します。
まだライフサイクルベースのロジックが必要な場合は、以下の表を使用してください。
| 以前 | 後 |
|---|---|
onShow() |
標準的な Ember コンポーネントのライフサイクル(init() または Ember 修飾子)を使用します |
afterRender |
標準的な Ember コンポーネントのライフサイクル(init() または Ember 修飾子)を使用します |
beforeClose() |
コンポーネントに渡される @closeModal 引数を取り巻くラッパーを作成します。クローズラッパーへの参照を DModal に \u003cDModal @closeModal={{this.myCloseModalWrapper}}\u003e のように渡します。 |
onClose() |
標準的な Ember コンポーネントのライフサイクル(willDestroy() または Ember 修飾子)を使用します |
ステップ 3: テンプレートの更新
\u003cDModalBody\u003e ラッパーを \u003cDModal\u003e に置き換えます。いくつかの新しい属性を追加します。
- 新しい
@closeModal引数を渡す - 明示的なクラスを追加する。以前の動作に合わせるには、コントローラーのファイル名に
-modalを追加します。
例えば、モーダルコントローラーが close-topic.js と呼ばれていた場合、新しい \u003cDModal\u003e の呼び出しは以下のようになります。
\u003cDModal @closeModal={{@closeModal}} class="close-topic-modal"\u003e
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 属性で角括ら構文を使用:\u003cDModal class="blah"\u003e |
@titleAriaElementId |
通常の HTML 属性で角括ら構文を使用:\u003cDModal aria-labelledby="blah"\u003e |
@dismissable, @submitOnEnter, @headerClass |
変更なし |
旧式の \u003cDModalBody\u003e コンポーネントの後にフッターコンテンツがレンダリングされていた場合は、新しい \u003c:footer\u003e 名前付きブロックを使用して、\u003cDModal\u003e 内で導入します。名前付きブロックを使用する場合は、ボディコンテンツを \u003c:body\u003e\u003c/:body\u003e で囲む必要があります。例:
\u003cDModal @closeModal={{@closeModal}}\u003e
\u003c:body\u003e
こんにちは、これがモーダルのコンテンツです
\u003c/:body\u003e
\u003c:footer\u003e
これがフッターコンテンツです。`.modal-footer` ラッパーが自動的に追加されます
\u003c/:footer\u003e
\u003c/DModal\u003e
ステップ 4: showModal 呼び出し箇所の更新
以前、モーダルは showModal API を使用してレンダリングされていました。この API は文字列(コントローラー名)と複数の opts を受け取り、コントローラーのインスタンスを返して操作できました。
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」サービスを読み込む(または getOwner(this).lookup("service:modal") を使用してアクセスし)、show() 関数を呼び出す必要があります。
show() は最初の引数として新しいコンポーネントクラスへの参照を受け取ります。現在サポートされている opt は model のみで、モーダルに必要なすべてのデータ/アクションを渡すために使用できます。
コンポーネントインスタンスへの参照は返されません。代わりに、show() はモーダルが閉じられたときに解決されるプロミスを返します。このプロミスは、@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 opt |
解決策 |
|---|---|
admin |
コンポーネントでは該当なし - 削除 |
templateName |
コンポーネントでは該当なし - 削除 |
title |
\u003cDModal @title={{i18n "blah"}}\u003e に移動 |
titleTranslated |
\u003cDModal @title="blah"\u003e に移動。必要に応じて model から取得したデータに基づいて計算することもできます |
modalClass |
\u003cDModal class="blah"\u003e に移動 |
titleAriaElementId |
\u003cDModal aria-labelledby="blah"\u003e に移動 |
panels |
\u003c:headerBelowTitle\u003e 名前付きブロックを使用して、コンポーネント内でタブを実装する(例) |
model |
変更なし |
ステップ 5: テスト
テストは基本的にそのまま維持されます。最も一般的な問題は以下の通りです。
-
モーダルには名前に基づくデフォルトのクラスがもはや存在しません。クラスはテンプレート内で明示的に指定する必要があります(ステップ 3 の冒頭を参照)
-
モーダルが閉じられたとき、
d-modalラッパーは DOM に残らなくなりました。すべてのモーダルが閉じられているか確認するには、assert.dom('.d-modal').doesNotExist()のようなチェックを使用してください。
利益!
モーダルは以前と同様に動作するはずです。新しい API をさらに活用するためには、showModal 呼び出しを宣言的な戦略に置き換えること、およびモーダルを Glimmer コンポーネントに変換することを検討してください。
例
Discourse コアのいくつかのモーダルを新しい API に変換する例のコミットを以下に示します。
このドキュメントはバージョン管理されています。変更を提案する場合は GitHub でお願いします。
このガイドでは、Ember コントローラーからの移行が最も容易なため、クラシックな Ember コンポーネントを推奨しています。ただし、単純なモーダルの場合や、リファクタリングに時間を費やすことに問題がない場合は、モダンな Glimmer コンポーネントの方が優れています。 ↩︎