web-atoms-core
Version:
190 lines (160 loc) • 5.73 kB
text/typescript
import { parsePath, parsePathLists } from "./ExpressionParser";
export interface IAtomComponent {
element: any;
viewModel: any;
localViewModel: any;
data: any;
app: {
callLater: (f: () => void) => void;
};
runAfterInit(f: () => void): void;
setLocalValue(e: any, name: string, value: any): void;
bindEvent(e: any, name: string, handler: any);
bind(e: any, name: string, path: any, twoWays: boolean, converter: any, source?: any);
}
const isEvent = /^event/i;
/**
* Bindings needs to be cloned...
*/
export type bindingFunction<T extends IAtomComponent = IAtomComponent> = (control: T, e?: any) => any;
function oneTime(name: string, b: Bind, control: IAtomComponent, e: any) {
control.runAfterInit(() => {
control.setLocalValue(e, name, b.sourcePath(control, e));
});
}
function event(name: string, b: Bind, control: IAtomComponent, e: any) {
control.runAfterInit(() => {
if (isEvent.test(name)) {
name = name.substr(5);
name = (name[0].toLowerCase() + name.substr(1));
}
control.bindEvent(e, name, (e1) => {
return (b.sourcePath as any)(control, e1);
});
});
}
function oneWay(name: string, b: Bind, control: IAtomComponent, e: any, creator: any) {
if (b.pathList) {
control.bind(e, name, b.pathList , false, () => {
// tslint:disable-next-line: ban-types
return (b.sourcePath as Function).call(creator, control, e);
});
return;
}
if (b.combined) {
const a = {
// it is `this`
t: creator,
// it is first parameter
x: control
};
control.bind(e, name, b.combined , false, () => {
// tslint:disable-next-line: ban-types
return (b.sourcePath as Function).call(creator, control, e);
}, a);
return;
}
if (b.thisPathList) {
control.bind(e, name, b.thisPathList , false, () => {
// tslint:disable-next-line: ban-types
return (b.sourcePath as Function).call(creator, control, e);
}, creator);
return;
}
}
function twoWays(name: string, b: Bind, control: IAtomComponent, e: any, creator: any) {
control.bind(e,
name,
b.thisPathList || b.pathList, (b.eventList as any) || true, null, b.thisPathList ? creator : undefined);
}
function presenter(name: string, b: Bind, control: IAtomComponent, e: any) {
const n = b.name || name;
let c = control.element as any;
while (c) {
if (c.atomControl && c.atomControl[n] !== undefined) {
break;
}
c = c._logicalParent || c.parentElement;
}
((c && c.atomControl) || control)[n] = e;
}
export interface IData<T> extends IAtomComponent {
data: T;
}
export interface IVM<T> extends IAtomComponent {
viewModel: T;
}
export interface ILVM<T> extends IAtomComponent {
localViewModel: T;
}
export interface IBinder<T extends IAtomComponent> {
presenter(name?: string): Bind;
event(handler: (control: T, e?: CustomEvent) => void): any;
oneTime(path: bindingFunction<T>): Bind;
oneWay(path: bindingFunction<T>): Bind;
twoWays(path: bindingFunction<T>, events?: string[]): Bind;
}
export default class Bind {
public static forData<D>(): IBinder<IData<D>> {
return Bind as any;
}
public static forViewModel<D>(): IBinder<IVM<D>> {
return Bind as any;
}
public static forLocalViewModel<D>(): IBinder<ILVM<D>> {
return Bind as any;
}
public static presenter(name?: string): Bind {
return new Bind(presenter, null, name as any);
}
// tslint:disable-next-line: ban-types
public static event<T extends IAtomComponent = IAtomComponent>(
sourcePath: (control: T, e?: CustomEvent) => void): any {
return new Bind(event, sourcePath as any);
}
public static oneTime<T extends IAtomComponent = IAtomComponent>(sourcePath: bindingFunction<T>): Bind {
return new Bind(oneTime, sourcePath);
}
public static oneWay<T extends IAtomComponent = IAtomComponent>(sourcePath: bindingFunction<T>): Bind {
return new Bind(oneWay, sourcePath);
}
public static twoWays<T extends IAtomComponent = IAtomComponent>(
sourcePath: bindingFunction<T>,
events?: string[]): Bind {
const b = new Bind(twoWays, sourcePath, null, events);
if (!(b.thisPathList || b.pathList)) {
throw new Error(`Failed to setup twoWay binding on ${sourcePath}`);
}
return b;
}
public readonly sourcePath: bindingFunction;
public readonly pathList: string[][];
public readonly thisPathList: string[][];
public readonly combined: string[][];
constructor(
public readonly setupFunction: ((name: string, b: Bind, c: IAtomComponent, e: any, self?: any) => void),
sourcePath: bindingFunction,
public readonly name?: string,
public readonly eventList?: string[]
) {
this.sourcePath = sourcePath;
if (!this.sourcePath) {
return;
}
if (Array.isArray(this.sourcePath)) {
this.pathList = this.sourcePath as any;
// this.setupFunction = null;
} else {
const lists = parsePathLists(this.sourcePath);
if (lists.combined.length) {
this.combined = lists.combined;
}
if (lists.pathList.length) {
this.pathList = lists.pathList;
}
if (lists.thisPath.length) {
this.thisPathList = lists.thisPath;
}
}
}
}