##// END OF EJS Templates
corrected tear down logic handling in observables. Added support for observable query results
corrected tear down logic handling in observables. Added support for observable query results

File last commit:

r110:1a190b3a757d v1.4.0 default
r110:1a190b3a757d v1.4.0 default
Show More
tsx.ts
187 lines | 6.5 KiB | video/mp2t | TypeScriptLexer
import { Constructor } from "@implab/core-amd/interfaces";
import { HtmlRendition } from "./tsx/HtmlRendition";
import { WidgetRendition } from "./tsx/WidgetRendition";
import { isElementNode, isWidget, isWidgetConstructor, Rendition } from "./tsx/traits";
import { FunctionRendition } from "./tsx/FunctionRendition";
import Stateful = require("dojo/Stateful");
import _WidgetBase = require("dijit/_WidgetBase");
import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
import { WatchRendition } from "./tsx/WatchRendition";
import { Observable, observe, OrderUpdate, Subscribable } from "./observable";
import djAttr = require("dojo/dom-attr");
import djClass = require("dojo/dom-class");
import { AnimationAttrs, WatchForRendition } from "./tsx/WatchForRendition";
export function createElement<T extends Constructor | string | ((props: object) => Element)>(elementType: T, ...args: unknown[]): Rendition {
if (typeof elementType === "string") {
const ctx = new HtmlRendition(elementType);
if (args)
args.forEach(x => ctx.visitNext(x));
return ctx;
} else if (isWidgetConstructor(elementType)) {
const ctx = new WidgetRendition(elementType);
if (args)
args.forEach(x => ctx.visitNext(x));
return ctx;
} else if (typeof elementType === "function") {
const ctx = new FunctionRendition(elementType as (props: unknown) => Element);
if (args)
args.forEach(x => ctx.visitNext(x));
return ctx;
} else {
throw new Error(`The element type '${String(elementType)}' is unsupported`);
}
}
export interface EventDetails<T = unknown> {
detail: T;
}
export interface EventSelector {
selectorTarget: HTMLElement;
target: HTMLElement;
}
export type DojoMouseEvent<T = unknown> = MouseEvent & EventSelector & EventDetails<T>;
type StatefulProps<T> = T extends Stateful<infer A> ? A :
T extends _WidgetBase ? T : never;
/**
* Observers the property and calls render callback each change.
*
* @param target The target object which property will be observed.
* @param prop The name of the property.
* @param render The callback which will be called every time the value is changed
* @returns Rendition which is created instantly
*/
export function watch<W extends _WidgetBase, K extends keyof W>(
target: W,
prop: K,
render: (model: W[K]) => unknown
): Rendition;
/**
* Observers the property and calls render callback each change.
*
* @param target The target object which property will be observed.
* @param prop The name of the property.
* @param render The callback which will be called every time the value is changed
* @returns Rendition which is created instantly
*/
export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
target: T,
prop: K,
render: (model: StatefulProps<T>[K]) => unknown
): Rendition;
export function watch<V>(subj: Subscribable<V>, render: (model: V) => unknown): Rendition;
export function watch(
...args: [Stateful, string, (model: unknown) => unknown] |
[Subscribable<unknown>, (model: unknown) => unknown]
) {
if (args.length === 3) {
const [target, prop, render] = args;
return new WatchRendition(
render,
observe(({next}) => {
const h = target.watch(
prop,
(_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
);
next(target.get(prop));
return () => h.remove();
})
);
} else {
const [subj, render] = args;
return new WatchRendition(render, subj);
}
}
export const watchFor = <T>(source: T[] | Subscribable<OrderUpdate<T>>, render: (item: T, index: number) => unknown, opts: AnimationAttrs = {}) => {
return new WatchForRendition({
...opts,
subject: source,
component: render
});
};
export const prop: {
<T extends Stateful, K extends string & keyof StatefulProps<T>>(target: T, name: K): Observable<StatefulProps<T>[K]>;
<T extends _WidgetBase, K extends keyof T>(target: T, name: K): Observable<T[K]>;
} = (target: Stateful, name: string) => {
return observe(({next}) => {
const h = target.watch(
name,
(_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
);
next(target.get(name));
return () => h.remove();
});
};
export const attach = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
export const bind = <K extends string, T>(attr: K, subj: Subscribable<T>) => {
let h = { unsubscribe() { } };
return (el: Element | { set(name: K, value: T): void; } | undefined) => {
if (el) {
if (isElementNode(el)) {
h = subj.subscribe({
next: value => djAttr.set(el, attr, value)
});
} else {
h = subj.subscribe({
next: value => el.set(attr, value)
});
}
} else {
h.unsubscribe();
}
};
};
export const toggleClass = (className: string, subj: Subscribable<boolean>) => {
let h = { unsubscribe() { } };
return (elOrWidget: HTMLElement | _WidgetBase | undefined) => {
const el = isWidget(elOrWidget) ? elOrWidget.domNode : elOrWidget;
if (el) {
h = subj.subscribe({
next: v => djClass.toggle(el, className, v)
});
} else {
h.unsubscribe();
}
};
};
export const all = <T, A extends JSX.Ref<T>[]>(...cbs: A): JSX.Ref<T> => (arg: T | undefined) => cbs.forEach(cb => cb(arg));
/** Decorates the method which will be registered as the handle for the specified event.
* This decorator can be applied to DjxWidgetBase subclass methods.
*
* ```
* @on("click")
* _onClick(eventObj: MouseEvent) {
* // ...
* }
* ```
*/
export const on = <E extends string>(...eventNames: E[]) =>
<K extends string,
T extends DjxWidgetBase<object, { [p in E]: EV }>,
EV extends Event
>(
target: T,
key: K,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
) => {
const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
};