ObservableArray: bindData()
引数で渡されたオブジェクトを、対象の ObservableArray
のバインディングターゲットとして登録します。バインディングターゲットとなるオブジェクトは、対象の ObservableArray
との間で配列操作および配列要素の変更が同期されます。
構文
observableArray.bindData(target) => boolean
observableArray.bindData(target, twoWay) => boolean
引数
-
target
:object
対象の
ObservableArray
とデータ同期されるオブジェクト(バインディングターゲット)です。バインディングターゲットは以下のプロパティを持つ必要があります。-
target.syncComponents(operation)
:(operation: object) => void
同期処理のために呼び出される関数です。
-
operation
:object
同期処理のための差分情報を持ったオブジェクトです。
-
operation.kind
:ObservableArray.OperationKind.SORT | ObservableArray.OperationKind.SPLICE
バインディングターゲットに対して行う配列操作の種別です。
SORT
は並べ替え、SPLICE
は要素の削除および挿入を表します。 -
operation.from
:object
バインディングターゲットまたはバインディングソースへの参照です。このプロパティはターゲットまたはソースのどちらが操作されたかを示します。
-
operation.indexMap
:({ from: number, to: number }[]?)
配列要素の置換を表すオブジェクトの配列です。このプロパティは
operation.kind
がSORT
の場合にのみ設定されます。 -
operation.startIndex
:number?
配列要素の削除または挿入の開始位置を示す整数です。このプロパティは
operation.kind
がSPLICE
の場合にのみ設定されます。 -
operation.deleteCount
:number?
削除される配列要素の個数を示す整数です。このプロパティは
operation.kind
がSPLICE
の場合にのみ設定されます。 -
operation.insertedItems
:object[]?
挿入される要素の配列です。このプロパティは
operation.kind
がSPLICE
の場合にのみ設定されます。
-
-
-
target.onDataBinding(source)
:(source: ObservableArray) => void
bindData()
によってtarget
が同期対象に設定された際に呼び出されるコールバック関数です。-
source
:ObservableArray
バインディングソースへの参照です
-
-
-
twoWay
:boolean
対象の
ObservableArray
(バインディングソース)と与えられたオブジェクト(バインディングターゲット)を双方向に同期するかどうかを表すboolean
です。true
ならバインディングソースとバインディングターゲットは双方向に同期され、false
ならバインディングソースからバインディングターゲットへの単方向の同期のみが行われます。対象のObservableArray
のプロパティtwoWay
がfalse
なら単方向バインディングを要求した場合のみtarget
をバインディングターゲットとして登録し、プロパティtwoWay
がtrue
なら単方向・双方向いずれの場合でもtarget
をバインディングターゲットとして登録します。引数twoWay
が指定されなかった場合、プロパティtwoWay
の値が代わりに使用されます。
返値: boolean
登録に成功したかどうかです。true
ならバインディングターゲットの登録に成功したか、既に自身に登録済みであることを示し、false
なら何らかの理由で登録に失敗したことを示します。
例
// ObservableObject のバインディングターゲットの簡易な実装
const BindingTarget = class {
curateValues() {
const valueMap = Object.create(null);
for (const k in this) {
if (!Object.prototype.hasOwnProperty.call(this, k)) { continue; }
valueMap[k] = this[k];
}
return valueMap;
}
reflectValues(valueMap) {
const updatedValues = Object.create(null);
const currentValues = this.curateValues();
for (const k in currentValues) {
if (!Object.prototype.hasOwnProperty.call(currentValues, k)) { continue; }
if (!(k in valueMap)) { continue; }
const candidate = valueMap[k];
const currentValue = currentValues[k];
if (!Number.isNaN(candidate) && candidate !== currentValue) {
this[k] = candidate;
updatedValues[k] = candidate;
}
}
const source = this.source;
if (typeof source?.reflectValues === "function") {
let updated = false;
for (const _ in updatedValues) {
updated = true;
break;
}
return updated ? source.reflectValues(updatedValues) : updatedValues;
} else {
return updatedValues;
}
}
onDataBinding(source) {
if (typeof source?.reflectValues === "function" && typeof source?.curateValues === "function") {
this.source = source;
this.reflectValues(source.curateValues());
}
}
};
// ObservableArray のバインディングターゲットの簡易な実装
const BindingTargetArray = class extends Array {
#ctor;
constructor(ctor) {
super();
this.#ctor = ctor;
}
get(index) {
return this.at(index);
}
splice(startIndex, deleteCount, ...insertedItems) {
const mappedItems = insertedItems.map(x => new this.#ctor(x));
const removedItems = super.splice(startIndex, deleteCount, ...mappedItems);
this.syncComponents({
from : this,
kind : ObservableArray.OperationKind.SPLICE,
startIndex : startIndex,
deleteCount : deleteCount,
insertedItems: mappedItems
});
return removedItems;
}
sort(compare) {
const compare_ = typeof compare === "function" ? compare : (x, y) => ((x > y) - (x < y));
const indexMap = [];
const valuesWithIndices = [...this.entries()];
valuesWithIndices.sort(([, x], [, y]) => compare_(x, y));
for (const [to, [from, fromValue]] of valuesWithIndices.entries()) {
if (from < to) {
indexMap.push({ from, to });
this[from] = this[to];
this[to] = fromValue;
}
}
this.syncComponents({
from : this,
kind : ObservableArray.OperationKind.SORT,
indexMap: indexMap
});
return this;
}
syncComponents(operation) {
if (operation == null || this.source == null) { return; }
if (operation.from === this) {
this.source.syncComponents(operation);
} else {
switch (operation.kind) {
case ObservableArray.OperationKind.SORT: {
const indexMap = operation.indexMap;
for (const { from , to } of indexMap) {
const fromValue = this[from];
this[from] = this[to];
this[to] = fromValue;
}
break;
}
case ObservableArray.OperationKind.SPLICE: {
const { startIndex, deleteCount, insertedItems } = operation;
const mappedItems = insertedItems.map(x => new this.#ctor(x.curateValues()));
this.splice(startIndex, deleteCount, ...mappedItems);
break;
}
default:
break;
}
}
}
onDataBinding(source) {
if (typeof source?.syncComponents !== "function") { return; }
Object.defineProperties(this, {
source: {
configurable: true,
writable : false,
enumerable : false,
value : source
}
});
}
};
// BindingTarget を継承するユーザ定義のデータクラス
const MyData = class extends BindingTarget {
constructor({x, y}) {
super();
this.x = x;
this.y = y;
}
};
const MyArray = class extends BindingTargetArray {
constructor() {
super(MyData);
}
};
const twoWayArray = new ObservableArray({ y: "foo" }, { twoWay: true, initCount: 4 });
const targetArray = new MyArray();
// bindData で twoWayArray に対して双方向バインディングを要求する
twoWayArray.bindData(targetArray, true);
// twoWayArray の要素に値を設定する
twoWayArray[0].reflectValues({ y: "A" });
twoWayArray[1].reflectValues({ y: "B" });
twoWayArray[2].reflectValues({ y: "C" });
twoWayArray[3].reflectValues({ y: "D" });
// targetArray に値の変更が反映されていることを確認する
console.log([...targetArray].map(x => x.curateValues()));
// ==> [ { y: "A" }, { y: "B" }, { y: "C" }, { y: "D" } ]
// targetArray の要素に値を設定する
targetArray.get(0).reflectValues({ y: "E" });
targetArray.get(1).reflectValues({ y: "F" });
targetArray.get(2).reflectValues({ y: "G" });
targetArray.get(3).reflectValues({ y: "H" });
// twoWayArray に値の変更が反映されていることを確認する
console.log([...twoWayArray].map(x => x.curateValues()));
// ==> [ { y: "E" }, { y: "F" }, { y: "G" }, { y: "H" } ]
// targetArray の末尾に 4 要素追加
targetArray.splice(4, 0, { y: "I" }, { y: "J" }, { y: "K" }, { y: "L" });
// twoWayArray に要素の追加が反映されていることを確認
console.log([...twoWayArray].map(x => x.curateValues()));
// ==> [ { y: "E" }, ..., { y: "I" }, { y: "J" }, { y: "K" }, { y: "L" } ]
// twoWayArray の先頭に 4 要素追加
twoWayArray.splice(0, 0, 4);
// targetArray に要素の追加が反映されていることを確認
console.log([...twoWayArray].map(x => x.curateValues()));
// ==> [ { y: "foo" }, { y: "foo" }, { y: "foo" }, { y: "foo" }, { y: "E" }, ... ]