@web-atoms/core
Version:
249 lines (222 loc) • 8.04 kB
text/typescript
import { ArrayHelper, IDisposable } from "./types";
export const symbolHandlers = Symbol.for("handlers");
export const symbolBindable = Symbol.for("bindable");
export type WatchFunction = (target: any, key: string, index?: number, item?: any, oldItem?: any) => void;
export interface IWatchFunctionCollection {
[key: string]: WatchFunction[];
}
export interface IWatchableObject {
[symbolHandlers]?: IWatchFunctionCollection;
[symbolBindable]?: {[key: string]: 0 | 1};
}
export class AtomBinder {
public static refreshValue(target, key) {
const handlers = AtomBinder.get_WatchHandler(target, key);
if (handlers === undefined || handlers == null) {
return;
}
for (const item of handlers) {
item(target, key);
}
if (target.onPropertyChanged) {
target.onPropertyChanged(key);
}
}
public static add_WatchHandler(target, key, handler: WatchFunction) {
if (target == null) {
return;
}
const handlers = AtomBinder.get_WatchHandler(target, key);
handlers.push(handler);
if (Array.isArray(target)) {
return;
}
// get existing property definition if it ha any
const pv = AtomBinder.getPropertyDescriptor(target, key);
// return if it has a getter
// in case of getter/setter, it is responsibility of setter to refresh
// object
if (pv && pv.get) {
return;
}
const tw = target as IWatchableObject;
let bindables = tw[symbolBindable];
if (bindables === undefined) {
bindables = {};
Object.defineProperty(tw, symbolBindable, {
value: bindables,
enumerable: false,
writable: true,
configurable: true
});
}
// if (!tw._$_bindable) {
// tw._$_bindable = {};
// }
if (!bindables[key]) {
bindables[key] = 1;
const o = target[key];
const nk = Symbol.for(key);
target[nk] = o;
const set = function(v: any) {
const ov = this[nk];
// tslint:disable-next-line:triple-equals
if (ov === undefined ? ov === v : ov == v) {
return;
}
this[nk] = v;
AtomBinder.refreshValue(this, key);
};
const get = function() {
return this[nk];
};
if (pv) {
delete target[key];
Object.defineProperty(target, key, {
get,
set,
configurable: true,
enumerable: true
});
} else {
Object.defineProperty(target, key, {
get, set, enumerable: true, configurable: true
});
}
}
}
public static getPropertyDescriptor(target, key: string): PropertyDescriptor {
const pv = Object.getOwnPropertyDescriptor(target, key);
if (!pv) {
const pt = Object.getPrototypeOf(target);
if (pt) {
return AtomBinder.getPropertyDescriptor(pt, key);
}
}
return pv;
}
public static get_WatchHandler(target: IWatchableObject, key: string): WatchFunction[] {
if (target == null) {
return null;
}
let handlers = target[symbolHandlers];
if (handlers === undefined) {
handlers = {};
Object.defineProperty(target, symbolHandlers, {
value: handlers,
enumerable: false,
writable: true,
configurable: true
});
}
// if (!handlers) {
// handlers = {};
// target._$_handlers = handlers;
// }
let handlersForKey = handlers[key];
if (handlersForKey === undefined || handlersForKey == null) {
handlersForKey = [];
handlers[key] = handlersForKey;
}
return handlersForKey;
}
public static remove_WatchHandler(
target: IWatchableObject,
key: string,
handler: WatchFunction) {
if (target == null) {
return;
}
const handlers = target[symbolHandlers];
if (typeof handlers === "undefined") {
return;
}
const handlersForKey = target[symbolHandlers][key];
if (handlersForKey === undefined || handlersForKey == null) {
return;
}
// handlersForKey = handlersForKey.filter( (f) => f !== handler);
ArrayHelper.remove(handlersForKey, (f) => f === handler);
if (!handlersForKey.length) {
handlers[key] = null;
delete handlers[key];
}
}
public static invokeItemsEvent(target: any[], mode: string, index: number, item: any, oldItem?: any) {
const key = "_items";
const handlers = AtomBinder.get_WatchHandler(target as any as IWatchableObject, key);
if (!handlers) {
return;
}
for (const obj of handlers) {
obj(target, mode, index, item, oldItem);
}
AtomBinder.refreshValue(target, "length");
}
public static refreshItems(ary) {
AtomBinder.invokeItemsEvent(ary, "refresh", -1, null);
}
public static add_CollectionChanged(target: any[], handler: WatchFunction): IDisposable {
if (target == null) {
throw new Error("Target Array to watch cannot be null");
}
if (handler == null) {
throw new Error("Target handle to watch an Array cannot be null");
}
const handlers = AtomBinder.get_WatchHandler(target as any as IWatchableObject, "_items");
handlers.push(handler);
return { dispose: () => {
AtomBinder.remove_CollectionChanged(target, handler);
}
};
}
public static remove_CollectionChanged(t: any[], handler: WatchFunction) {
if (t == null) {
return;
}
const target = t as any as IWatchableObject;
const handlers = target[symbolHandlers];
if (typeof handlers === "undefined") {
return;
}
const key = "_items";
const handlersForKey = handlers[key];
if (handlersForKey === undefined || handlersForKey == null) {
return;
}
ArrayHelper.remove(handlersForKey, (f) => f === handler);
if (!handlersForKey.length) {
handlers[key] = null;
delete handlers[key];
}
}
public static watch(item: any, property: string, f: WatchFunction): IDisposable {
AtomBinder.add_WatchHandler(item, property, f);
return {
dispose: () => {
AtomBinder.remove_WatchHandler(item, property, f);
}
};
}
public static clear(a: any[]): any {
a.length = 0;
this.invokeItemsEvent(a, "refresh", -1, null);
AtomBinder.refreshValue(a, "length");
}
public static addItem(a: any[], item: any): any {
const index = a.length;
a.push(item);
this.invokeItemsEvent(a, "add", index, item);
AtomBinder.refreshValue(a, "length");
}
public static removeItem(a: any[], item: any): boolean {
const i = a.findIndex( (x) => x === item);
if (i === -1) {
return false;
}
a.splice(i, 1);
AtomBinder.invokeItemsEvent(a, "remove", i, item);
AtomBinder.refreshValue(a, "length");
return true;
}
}