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 インスタンスに対して送られたメッセージは、そのメッセージが処理済みとマークされない限り、ViewLogicparenthost などのプロパティを参照して、別の 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();

ほとんどの場合、コンテナを読み込む際に、下記の順番でメソッドを呼び出す必要があります:

  1. ViewLogic.loadContainer()

    ここでコンテナの読み込みと取得を行います。

  2. ProtoViewLogic.collectElements()

    ここで、取得したコンテナからUIの構成要素を収集します。

  3. ProtoViewLogic.relateElements()

    ここで、収集したUI要素を、対象の ViewLogic インスタンスのプロパティとしてアクセスできるようにします。 またこの時点で、コンテナ内のUI要素への操作を操作した際、対象の ViewLogic インスタンスへ操作の発生を伝えるメッセージが送られるようになります。

コンテナの画面への反映

コンテナの内容は ViewLogicViewElement.attach() に渡すことで画面に反映されます。

//  myViewLogic が持つコンテナを Alier.View に反映します。
//  Alier.View はフレームワーク側で用意された ViewElement のインスタンスであり、
//  アプリケーション全体の画面として利用されます。
Alier.View.attach(myViewLogic);

上記ではフレームワーク側で用意された Alier.View を使っていますが、アプリケーション開発者が独自に用意した ViewElement のインスタンスを使っても同様に行えます。 その場合、アプリケーション開発者は自身の用意した ViewElement インスタンスをメイン文書(globalThis.document)に接続させる必要があります。

クラス


Viewlogic

  • ユーザとモデルインタフェースを仲介するクラスです。