ObservableValue

モジュールパス: /alier_sys/ObservableValue.js

概要

1つの値を監視し、バインドしたオブジェクトに値を伝播するためのクラスです。

特徴

ObservableValue は次の特徴を持っています。

監視可能な型

stringnumberbooleanbigint 型の値を監視することができます。

info

オブジェクトや配列を監視したい場合は ObservableObjectObservableArray でそれらを監視することができます。

バインド

オブジェクトとバインドすることで、そのオブジェクトの任意のプロパティを監視対象の値と同期することができます。

基本的には、次の2つのインターフェースを持つオブジェクトをバインドすることができます。

  • 値を更新する setValue()
  • ソースオブジェクトを保持する onDataBinding()
info

詳しい条件は bindData() で確認できます。

以下のサンプルコードでは、バインド可能なオブジェクトを ObservableValue にバインドします。 バインド可能なオブジェクトを生成するために、以下ではファクトリ関数を用意しています。

// バインド可能なオブジェクトを生成するファクトリ関数
const create_target = (value) => {
    let private_value = value;

    return {
        setValue(newValue) {
            private_value = value;
            this.source?.setValue(private_value);
        },
        onDataBinding(source) {
            this.source = source;
            return true;
        },
        // 値を取得するメソッド
        getValue() {
            return private_value;
        },
    };
};

const target = create_target();

const ov = new ObservableValue("foo");

ov.bindData(target);

同期モード

ObservableValue とバインドしたオブジェクトで値を同期する方法には、単方向と双方向の2種類があります。

単方向モード

バインドしたオブジェクト(バインディングターゲット)に対する変更は監視対象に反映されず、監視対象の値で上書きされます。

監視対象の値を変更した時は双方向モードと同様にバインディングターゲットに変更が反映されます

双方向モード

バインディングターゲットに対する変更は監視対象に反映されます

監視対象の値を変更した時もバインディングターゲットに変更が反映されます

同期モードの指定方法

コンストラクト時とバインド時に同期モードを指定することができます。

コンストラクト時には双方向モードを許可するかの指定し、バインド時には双方向モードでバインドするかを指定します。

双方向モードを許可しない場合、バインド時の指定に限らず単方向モードでバインドされます。

// 双方向モードを許可しない
const ov_oneway = new ObservableValue("ONE", false);

// バインド可能なオブジェクトを生成
const target_a = create_target("Alice");
const target_b = create_target("Bob");

// 単方向を要求し、単方向でバインド
ov_oneway.bindData(target_a, false);

// 双方向を要求するが、許可されていないので単方向でバインドされる
ov_oneway.bindData(target_b, true);

// 双方向を要求した target_b を更新しても値が逆戻りする
target_b.setValue("Even");
console.log(ov_oneway.getValue());
// => "ONE"

双方向モードを許可する場合、バインド時の指定によってどちらのモードでバインドされるかが決まります。

// 双方向モードを許可
const ov_twoway = new ObservableValue("TWO", true);

// バインド可能なオブジェクトを生成
const target_c = create_target("Carol");
const target_d = create_target("Ellen");

// 単方向を要求し、単方向でバインド
ov_twoway.bindData(target_c, false);

// 双方向を要求し、双方向でバインド
ov_twoway.bindData(target_d, true);

// 双方向モードでバインドしているので値が更新される
target_d.setValue("Even");
console.log(ov_twoway.getValue());
// => "Even"

エレメントとのバインド

ObservableValue を活用する場面の1つとしてエレメントとのバインドを紹介します。

ObservableValue とエレメントのプライマリプロパティをバインドすることで、モデルで取得した値をエレメントに反映したり、エレメント同士を同期したりすることができます。

標準のエレメントはそのままではバインドできませんが、makeElementObserver() でバインド可能にすることができます。

const input = document.createElement("input");
// <input> を ObservableValue とバインドできるようにする
makeElementObserver(input);

const ov = new ObservableValue("foo");
// <input> と ObservableValue をバインド
ov.bindData(input);

console.log(input.value);
// => "foo"
// ObservableValue の値が <input> に反映されている

<alier-textfield><alier-slider> など、Alier が提供するカスタムエレメントは ObservableValue とバインド可能となっています。

warning

<alier-view>ViewLogic をアタッチ可能な特別なカスタムエレメントであるため、ObservableValue とバインドできません。

次の例では、<alier-slider> で作成したスライダーの値を0.1秒ごとに増加させ、10秒ごとにリセットしています。

import { setupAlier } from "/alier_sys/AlierFramework.js";
import { ViewLogic } from "/alier_sys/ViewLogic.js";
import { ObservableValue } from "/alier_sys/ObservableValue.js";

class SliderCounter extends ViewLogic {
    constructor() {
        super();

        // alier-slider を含むコンテンツを用意
        const text = `
            <alier-container>
                <alier-slider id="slider" min="0" max="99"></alier-slider>
            </alier-container>
        `;

        // コンテンツを読み込み
        this.relateElements(this.collectElements(this.loadContainer({ text })));

        // alier-slider と ObservableValue をバインド
        this.ov = new ObservableValue(0);
        this.ov.bindData(this.slider);

        // alier-slider に反映させるカウント
        let count = 0;
        setInterval(() => {
            // カウントを ObservableValue に反映させることで、
            // バインドした alier-slider にもカウントを伝播させる
            this.ov.setValue(count);

            // カウント増加、またはリセット
            if (count < 99) {
                count++;
            } else {
                count = 0;
            }
        }, 100);
    }
}

export default function main() {
    setupAlier();
    Alier.View.attach(new SliderCounter());
}

エレメント同士の同期

次に、エレメント同士を同期するサンプルコードを見ていきます。

このサンプルでは、<alier-textfield> に文字を入力すると <alier-text> にその値が同期され、入力した文字が表示されます。

import { setupAlier } from "../alier_sys/AlierFramework.js";
import { ViewLogic } from "../alier_sys/ViewLogic.js";
import { ObservableValue } from "../alier_sys/ObservableValue.js";

class FieldSyncText extends ViewLogic {
    constructor() {
        super();

        // alier-textfield と alier-text を含むコンテンツを用意
        const text = `
            <alier-container>
                <alier-textfield id="field"></alier-textfield>
                <alier-text id="viewer"></alier-text>
            </alier-container>
        `;

        // コンテンツを読み込み
        this.relateElements(this.collectElements(this.loadContainer({ text })));

        // 双方向を許可した ObservableValue を生成
        this.ov = new ObservableValue("", true);
        // alier-textfield と双方向モードでバインド
        this.ov.bindData(this.field);
        // alier-text と単方向モードでバインド
        this.ov.bindData(this.viewer, false);
    }
}

export default function main() {
    setupAlier();
    Alier.View.attach(new FieldSyncText());
}

この例では、以下のような順番で更新が入っています。

  1. <alier-textfield> で入力した値が ObservableValue に反映される
  2. ObservableValue の変更がターゲットにブロードキャストされる
  3. ObservableValue の値が <alier-text> に反映される

コンストラクタ

構文

new ObservableValue(value, allowsTwoWay) => ObservableValue

引数

  • value: string | number | boolean | bigint

    同期対象となる初期値です。

  • allowsTwoWay: boolean (省略可)

    双方向のバインドを許可するか否かです。true なら双方向バインディングを許可し、false なら許可しません。 コンストラクト後、この値はメンバ変数 allowsTwoWay として参照できます。

    省略した場合は false となります。

返値: ObservableValue

生成された ObservableValue のプロキシです。値を更新すると同期設定されたターゲットオブジェクトの値も更新されます。

監視対象の値は getValue() で、双方向モードが許可されているかは allowsTwoWay で取得することができます。

import { ObservableValue } from "/alier_sys/ObservableValue.js";

const source1 = new ObservableValue("foo", true);
const source2 = new ObservableValue("bar", false);
const source3 = new ObservableValue("foobar");

console.log(source1.getValue());
// => foo
console.log(source2.getValue());
// => bar
console.log(source3.getValue());
// => foobar

console.log(source1.allowsTwoWay);
// => true
console.log(source2.allowsTwoWay);
// => false
console.log(source3.allowsTwoWay);
// => false

解説

生成されたオブジェクトは引数 value を値として持ちます。

双方向バインディングを許すかは同名のプロパティ allowsTwoWay から確認できます。 bindData() でオブジェクトをバインドした時、allowsTwoWayfalse なら単方向バインディングのみが可能で、true なら双方向バインディングも可能となります。

メソッド


bindData()

  • 引数で渡されたオブジェクトをバインディングターゲットとして登録します。

setValue()

  • 監視対象の値を更新します。

getValue()

  • 監視対象の値を取得します。

プロパティ


allowsTwoWay

このプロパティは読み取り専用です。

双方向バインディングが許可されているかどうかです。

単方向のみが許可されている場合、バインド元となるソース、つまり ObservableValue からバインドされたターゲットへの反映のみが有効となっています。 単方向のみが許可されている状態でターゲットの値を更新した場合、ソースの値で逆戻りします。

boolean

双方向が許可されている場合は true、そうでない場合は false です。

コンストラクタで双方向を許可するか指定します。

const ov_oneway = new ObservableValue(1, false);
console.log(ov_oneway.allowsTwoWay);
// => false

const ov_twoway = new ObservableValue(2, true);
console.log(ov_twoway.allowsTwoWay);
// => true