ViewLogic モジュール
モジュールパス: /alier_sys/ViewLogic.js
概要
このモジュールでは ViewLogic
クラスを提供しています。
このモジュールは、ProtoViewLogic
モジュールを暗黙にインポートします。
また ProtoViewLogic
モジュールが提供する ProtoViewLogic
クラス は ViewLogic
クラスの基底クラスです。
ViewLogic
クラスは、アプリケーションの以下の役割を担います:
- アプリケーションの状態(データ)の提示
- アプリケーションの状態変更(イベント)の提示
- ユーザインターフェースの提供
- ユーザ入力の制御
これらの役割は、ViewLogic
のメッセージ制御機能とコンテナによって実現されます。
メッセージの制御
メッセージ制御によって、ViewLogic
のインスタンスは以下の役割を果たします:
- アプリケーションの状態変更(イベント)の提示
- ユーザ入力の制御
メッセージ
メッセージとは、Alierフレームワークにおいて、以下のプロパティを持つオブジェクトを指します:
-
id
:string
|null
メッセージの識別子です。
-
code
:string
|null
メッセージの追加の識別子です。
-
param
:any
|null
メッセージが持つパラメータです。
-
origin
:object
メッセージの生成元のオブジェクトです。
-
target
:object
メッセージの送り先のオブジェクトです。 このプロパティは、メッセージの生成時点では設定されません。 代わりに、メッセージの生成元から送り先のオブジェクトがメッセージを受け取った際に設定されます。
メッセージの生成
メッセージの生成には Alier.message()
を使います。
class MyClass {
async doSomething() {
// MyClass のインスタンスからメッセージを生成します。
// Alier.message() の引数は、id, code, param, origin の順で指定します。
const message = Alier.message("someId", "someCode", { param1: 1, param2: 2}, this);
// ここでメッセージを送る
}
}
また、ViewLogic
ではメッセージ生成のための補助的な関数として、message()
メソッドが使えます。
ProtoViewLogic.message()
は、origin
が自動的に対象の ViewLogic
インスタンスになる以外は、Alier.message()
と変わりません。
class MyViewLogic extends ViewLogic {
async doSomething() {
// MyViewLogic のインスタンスからメッセージを生成します。
// ProtoViewLogic.message() の引数は、id, code, param の順で指定します。
// 生成された message には origin として this が暗黙に指定されます。
const message = this.message("someId", "someCode", { param1: 1, param2: 2});
// ここでメッセージを送る
}
}
メッセージの送信
以下のメソッドを使って、対象の ViewLogic
インスタンスに対してメッセージを送信できます。
post()
と broadcast()
の違いは、対象の ViewLogic
とその親へメッセージを伝えるか、対象の ViewLogic
の子孫へメッセージを伝えるかです。
ViewLogic
の親(祖先)と子(子孫)の関係と、メッセージの伝わる経路については#メッセージの伝播の節で、それぞれ説明します。
post()
を使ったメッセージの送信は以下のように書きます:
class MyClass {
// target として ViewLogic のインスタンスを受け取るとします
async doSomething(target) {
// MyClass のインスタンスからメッセージを生成します
const message = Alier.message("someId", "someCode", { param1: 1, param2: 2}, this);
// 生成したメッセージを target に対して送ります。
// メッセージが処理されたかは `post()` が返す `Promise` が履行した際に
// 与えられる論理値によって判別できます。
const consumed = await target.post(message);
}
}
broadcast()
についても同様です。
class MyClass {
// target として ViewLogic のインスタンスを受け取るとします
async doSomething(target) {
// MyClass のインスタンスからメッセージを生成します
const message = Alier.message("someId", "someCode", { param1: 1, param2: 2}, this);
// 生成したメッセージを target の子孫に対して送ります。
// メッセージが処理されたかは `broadcast()` が返す `Promise` が履行した際に
// 与えられる論理値によって判別できます。
const consumed = await target.broadcast(message);
}
}
メッセージの受信
ViewLogic
のインスタンスに対してメッセージが送られると、その ViewLogic
インスタンスに対して messageHandler()
メソッドが呼び出されます。
その際、messageHandler()
は引数として送信されたメッセージを受け取ります。
受け取ったメッセージに対して、その ViewLogic
インスタンスに何らかの処理を行わせるためには、messageHandler()
メソッドをオーバーライドします。
class MyViewLogic extends ViewLogic {
// messageHandler() をオーバーライドします
async messageHandler(message) {
// ここで message を処理します
}
}
messageHandler()
の呼び出しに際し、引数のメッセージには補助的なメソッドが追加されています。例えば、メッセージの id
プロパティの値によって処理を分岐させるために、deliver()
メソッドが使えます。
class MyViewLogic extends ViewLogic {
// messageHandler() をオーバーライドします
async messageHandler(message) {
message.deliver({
// 引数の message は messageHandler に渡されたものと同一です
sendButton: message => {
// 送信ボタンの操作に対する処理を行います
},
// async 関数も指定できます
emailAddressField: async message => {
// メールアドレス欄の操作に対する処理を行います
}
})
}
}
deliver()
の引数として、メッセージの id
プロパティの値に対応した名前のプロパティを持つオブジェクトを与えます。
各プロパティの値は、メッセージを引数に取る関数でなければなりません。
deliver()
メソッドは、引数として与えられたオブジェクトから、メッセージの id
に一致するプロパティ名を探し、プロパティの値に設定された関数を呼び出します。
いずれかの関数の呼び出しに成功した場合、deliver()
は、そのメッセージを処理済みとしてマークします。
メッセージが処理済みかどうかは、post()
や broadcast()
の呼び出し元に、それらの返す Promise
が履行された際の値として伝えられます。
メッセージが処理済みなら、履行した際に true
を与え、未処理なら false
を与えます。
場合によって、deliver()
から関数の呼び出しが行われた際、メッセージを未処理扱いのままにしたいことがあります。
その場合、deliver()
から呼び出される関数から false
を返すことで、メッセージを処理済みとしてマークしないことが伝えられます。
class MyViewLogic extends ViewLogic {
// messageHandler() をオーバーライドします
async messageHandler(message) {
message.deliver({
// 引数の message は messageHandler に渡されたものと同一です
noAction: message => {
// false を返した場合、メッセージは未処理扱いのままになります。
return false;
},
})
}
}
メッセージの伝播
ViewLogic
インスタンスに対して送られたメッセージは、そのメッセージが処理済みとマークされない限り、ViewLogic
の parent
や host
などのプロパティを参照して、別の ViewLogic
インスタンスへ伝播します。
どの ViewLogic
インスタンスへメッセージを伝播させるかは、メッセージの送信の際に呼び出したメソッドによります。
-
post()
を呼び出した場合post()
メソッドの対象となるViewLogic
インスタンスのparent
およびhost
プロパティを参照します。post()
の結果、メッセージが対象のViewLogic
によって処理済みとマークされなかった場合、parent
プロパティから参照される別のViewLogic
インスタンスへメッセージを再送します。parent
プロパティがViewLogic
インスタンスを参照していない場合、host
プロパティから参照されるViewElement
をプロパティから参照する別のViewLogic
インスタンスにメッセージを再送します。いずれの再送先も見つからない場合、メッセージは未処理扱いのままになります。
-
broadcast()
を呼び出した場合broadcast()
メソッドの対象となるViewLogic
インスタンスのparent
以外のプロパティを参照します。broadcast()
の呼び出しに際し、対象のViewLogic
インスタンスのプロパティからViewLogic
インスタンスまたはViewLogic
の配列を参照するプロパティを取得し、それらにメッセージを送ります。メッセージを受信した
ViewLogic
インスタンスは、メッセージを処理済みにマークできなかったなら、自身のparent
以外のプロパティから参照されるViewLogic
インスタンスに対してメッセージを再送します。 以降、これを繰り返します。メッセージを受信した
ViewLogic
インスタンスがparent
以外のプロパティからViewLogic
のインスタンスを参照していない場合、メッセージの再送は起こりません。
ViewLogic
インスタンスからその parent
プロパティを介して参照される ViewLogic
インスタンスを親と呼びます。逆に親から見て、parent
プロパティとして親への参照を持つ ViewLogic
インスタンスを子と呼びます。parent
プロパティを辿って参照できるすべての ViewLogic
インスタンスを総称して祖先(ancestors)と呼び、逆に祖先から見て、parent
プロパティを辿って間接あるいは直接的に自身を参照する ViewLogic
インスタンスを総称して子孫(descendants)と呼びます。
ViewLogic
インスタンスへのプロパティの設定は以下のメソッドによって行われます:
-
parent
プロパティの設定ViewLogic
インスタンスを対象としてrelateViewLogics()
メソッドを呼び出した際、引数に設定されたViewLogic
インスタンスのparent
プロパティに、relateViewLogics()
の対象のViewLogic
インスタンスが設定されます。 -
host
プロパティの設定ViewLogic
インスタンスを引数に、ViewElement.attach()
を呼び出した際、引数に設定されたViewLogic
インスタンスのhost
プロパティに、attach()
の対象のViewElement
インスタンスが設定されます。 -
その他のプロパティの設定
ViewLogic
インスタンスを対象としてrelateViewLogics()
メソッドを呼び出した際、対象のViewLogic
インスタンスに引数で指定した名前で、引数に設定されたViewLogic
インスタンスとViewLogic
の配列を参照するプロパティが設定されます。
コンテナ
コンテナとは、表示するコンテンツをまとめた要素の集まりです。
コンテナは以下の役割を果たします:
- アプリケーションの状態(データ)の提示
- ユーザインターフェースの提供
コンテナの記述と生成
コンテナはHTML文書として記述します。
コンテナの実体はDOMノードであり、HTML文書内の要素として記述されます。 HTML文書から生成されるDOMツリー中のどのノードをコンテナとして使うかは、HTML文書の中でアプリケーション実装者が指定する必要があります。 コンテナとなるノードを示す方法は次の2つです。
- コンテンツをまとめている要素に
id
属性を指定する - コンテンツをまとめている要素をカスタム要素
<alier-container>
として記述する
- HTML文書内に
<alier-container>
が複数ある場合、前順深さ優先探索で最初の<alier-container>
がコンテナとして指定されます。 これはテキストとして先頭から検索して最初に一致した<alier-container>
と同じです。 - HTML文書内に同じ
id
属性値を持つ要素が複数ある場合、指定したid
属性値を持つ要素のうち、前順深さ優先探索で最初に到達した要素がコンテナとして指定されます。 これはテキストとして先頭から検索して最初に一致した、指定のid
属性値を持つ要素と同じです。
記述したHTML文書からコンテナを生成するには、コンテナを持たせたい ViewLogic
のインスタンスに対してViewLogic.loadContainer()
(またはその同期関数版の ViewLogic.loadContainerSync()
)メソッドを呼び出します。
loadContainer()
/ loadContainerSync()
の引数として、HTML文書のファイルパスかHTML文書を表す文字列を与えます(以下の例ではファイルパスを指定しています)。
// ViewLogic クラスをインポートします
const { ViewLogic } = await Alier.import("/alier_sys/ViewLogic.js");
// ViewLogic の派生クラスを定義します
class LoadContainerExample extends ViewLogic {
// コンストラクタから loadContainer() を呼び出し、
// "LoadContainerExample.html" に定義されたコンテナを読み込みます。
constructor() {
super();
// LoadContainerExample.html を読み込み、コンテナを取得します。
// このHTMLファイルは必ずカスタム要素 <alier-container> を含んでいなければなりません。
// <alier-container> はコンテナの構成要素をまとめるルート要素として使われます。
const container = this.loadContainer({ file: "LoadContainerExample.html" });
// コンテナから UI を構成する要素を取得します
const uiElements = this.collectElements(container);
// UI の構成要素をこの ViewLogic インスタンスに関連付けます
this.relateElements(uiElements);
}
}
// LoadContainerExample のインスタンスを生成します
const loadContainerExample = new LoadContainerExample();
ほとんどの場合、コンテナを読み込む際に、下記の順番でメソッドを呼び出す必要があります:
-
ここでコンテナの読み込みと取得を行います。
-
ProtoViewLogic.collectElements()
ここで、取得したコンテナからUIの構成要素を収集します。
-
ProtoViewLogic.relateElements()
ここで、収集したUI要素を、対象の
ViewLogic
インスタンスのプロパティとしてアクセスできるようにします。 またこの時点で、コンテナ内のUI要素への操作を操作した際、対象のViewLogic
インスタンスへ操作の発生を伝えるメッセージが送られるようになります。
コンテナの画面への反映
コンテナの内容は ViewLogic
を ViewElement.attach()
に渡すことで画面に反映されます。
// myViewLogic が持つコンテナを Alier.View に反映します。
// Alier.View はフレームワーク側で用意された ViewElement のインスタンスであり、
// アプリケーション全体の画面として利用されます。
Alier.View.attach(myViewLogic);
上記ではフレームワーク側で用意された Alier.View
を使っていますが、アプリケーション開発者が独自に用意した ViewElement
のインスタンスを使っても同様に行えます。
その場合、アプリケーション開発者は自身の用意した ViewElement
インスタンスをメイン文書(globalThis.document
)に接続させる必要があります。
クラス
Viewlogic
- ユーザとモデルインタフェースを仲介するクラスです。