##// END OF EJS Templates
Testing nested watch, release candidate
cin -
r101:bb6b1db1b430 v1.3
parent child
Show More
@@ -0,0 +1,1
1 symbols=local No newline at end of file
@@ -1,8 +1,7
1 group=org.implab.implabjs
1 group=org.implab.implabjs
2 version=
2 version=
3 author=Implab team
3 author=Implab team
4 description=Create HyperText with Typescript, integrate Dojo1 widgets in your .tsx scripts.
4 description=Create HyperText with Typescript, integrate Dojo1 widgets in your .tsx scripts.
5 license=BSD-2-Clause
5 license=BSD-2-Clause
6 repository=http://hg.code.sf.net/p/implabjs/djx
6 repository=http://hg.code.sf.net/p/implabjs/djx
7 npmScope=implab
7 npmScope=implab
8 symbols=pack No newline at end of file
@@ -1,34 +1,34
1 import { IDestroyable } from "@implab/core-amd/interfaces";
1 import { IDestroyable } from "@implab/core-amd/interfaces";
2
2
3 export interface Sink<T> {
3 export interface Sink<T> {
4 next: (value: T) => void;
4 next: (value: T) => void;
5 error: (e: unknown) => void;
5 error: (e: unknown) => void;
6 complete: () => void;
6 complete: () => void;
7 }
7 }
8
8
9 export type Consumer<T> = Partial<Sink<T>>;
9 export type Consumer<T> = Partial<Sink<T>>;
10
10
11 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
11 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
12
12
13 export interface Observable<T> {
13 export interface Observable<T> {
14 on(consumer: Partial<Sink<T>>): IDestroyable;
14 on(consumer: Partial<Sink<T>>): IDestroyable;
15 }
15 }
16
16
17 const noop = () => {};
17 const noop = () => {};
18
18
19 const sink = <T>(consumer: Consumer<T>) => {
19 const sink = <T>(consumer: Consumer<T>) => {
20 const { next = noop, error = noop, complete = noop } = consumer;
20 const { next = noop, error = noop, complete = noop } = consumer;
21 let done = false;
21 let done = false;
22
22
23 return {
23 return {
24 next: (value: T) => done && next(value),
24 next: (value: T) => !done && next(value),
25 error: (e: unknown) => done && (done = true, error(e)),
25 error: (e: unknown) => !done && (done = true, error(e)),
26 complete: () => done && (done = true, complete())
26 complete: () => !done && (done = true, complete())
27 };
27 };
28 }
28 }
29
29
30 export const observe = <T>(producer: Producer<T>) : Observable<T> => ({
30 export const observe = <T>(producer: Producer<T>) : Observable<T> => ({
31 on: (consumer: Consumer<T>) => ({
31 on: (consumer: Consumer<T>) => ({
32 destroy: producer(sink(consumer)) ?? noop
32 destroy: producer(sink(consumer)) ?? noop
33 })
33 })
34 });
34 });
@@ -1,114 +1,116
1 import { Constructor } from "@implab/core-amd/interfaces";
1 import { Constructor } from "@implab/core-amd/interfaces";
2 import { HtmlRendition } from "./tsx/HtmlRendition";
2 import { HtmlRendition } from "./tsx/HtmlRendition";
3 import { WidgetRendition } from "./tsx/WidgetRendition";
3 import { WidgetRendition } from "./tsx/WidgetRendition";
4 import { isWidgetConstructor, Rendition } from "./tsx/traits";
4 import { isWidgetConstructor, Rendition } from "./tsx/traits";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
6 import Stateful = require("dojo/Stateful");
6 import Stateful = require("dojo/Stateful");
7 import _WidgetBase = require("dijit/_WidgetBase");
7 import _WidgetBase = require("dijit/_WidgetBase");
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
9 import { WatchRendition } from "./tsx/WatchRendition";
9 import { WatchRendition } from "./tsx/WatchRendition";
10 import { observe } from "./observable";
10 import { observe } from "./observable";
11
11
12 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
12 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
13 if (typeof elementType === "string") {
13 if (typeof elementType === "string") {
14 const ctx = new HtmlRendition(elementType);
14 const ctx = new HtmlRendition(elementType);
15 if (args)
15 if (args)
16 args.forEach(x => ctx.visitNext(x));
16 args.forEach(x => ctx.visitNext(x));
17
17
18 return ctx;
18 return ctx;
19 } else if (isWidgetConstructor(elementType)) {
19 } else if (isWidgetConstructor(elementType)) {
20 const ctx = new WidgetRendition(elementType);
20 const ctx = new WidgetRendition(elementType);
21 if (args)
21 if (args)
22 args.forEach(x => ctx.visitNext(x));
22 args.forEach(x => ctx.visitNext(x));
23
23
24 return ctx;
24 return ctx;
25 } else if (typeof elementType === "function") {
25 } else if (typeof elementType === "function") {
26 const ctx = new FunctionRendition(elementType as (props: any) => Element);
26 const ctx = new FunctionRendition(elementType as (props: any) => Element);
27 if (args)
27 if (args)
28 args.forEach(x => ctx.visitNext(x));
28 args.forEach(x => ctx.visitNext(x));
29
29
30 return ctx;
30 return ctx;
31 } else {
31 } else {
32 throw new Error(`The element type '${elementType}' is unsupported`);
32 throw new Error(`The element type '${elementType}' is unsupported`);
33 }
33 }
34 }
34 }
35
35
36 export interface EventDetails<T = any> {
36 export interface EventDetails<T = any> {
37 detail: T;
37 detail: T;
38 }
38 }
39
39
40 export interface EventSelector {
40 export interface EventSelector {
41 selectorTarget: HTMLElement;
41 selectorTarget: HTMLElement;
42 target: HTMLElement;
42 target: HTMLElement;
43 }
43 }
44
44
45 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
45 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
46
46
47 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
47 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
48
48
49
49
50 /**
50 /**
51 * Observers the property and calls render callback each change.
51 * Observers the property and calls render callback each change.
52 *
52 *
53 * @param target The target object which property will be observed.
53 * @param target The target object which property will be observed.
54 * @param prop The name of the property.
54 * @param prop The name of the property.
55 * @param render The callback which will be called every time the value is changed
55 * @param render The callback which will be called every time the value is changed
56 * @returns Rendition which is created instantly
56 * @returns Rendition which is created instantly
57 */
57 */
58 export function watch<W extends _WidgetBase, K extends keyof W>(
58 export function watch<W extends _WidgetBase, K extends keyof W>(
59 target: W,
59 target: W,
60 prop: K,
60 prop: K,
61 render: (model: W[K]) => any
61 render: (model: W[K]) => any
62 ): Rendition;
62 ): Rendition;
63 /**
63 /**
64 * Observers the property and calls render callback each change.
64 * Observers the property and calls render callback each change.
65 *
65 *
66 * @param target The target object which property will be observed.
66 * @param target The target object which property will be observed.
67 * @param prop The name of the property.
67 * @param prop The name of the property.
68 * @param render The callback which will be called every time the value is changed
68 * @param render The callback which will be called every time the value is changed
69 * @returns Rendition which is created instantly
69 * @returns Rendition which is created instantly
70 */
70 */
71 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
71 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
72 target: T,
72 target: T,
73 prop: K,
73 prop: K,
74 render: (model: StatefulProps<T>[K]) => any
74 render: (model: StatefulProps<T>[K]) => any
75 ): Rendition;
75 ): Rendition;
76 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
76 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
77 target: T,
77 target: T,
78 prop: K,
78 prop: K,
79 render: (model: StatefulProps<T>[K]) => any
79 render: (model: StatefulProps<T>[K]) => any
80 ) {
80 ) {
81
81 return new WatchRendition(
82 return new WatchRendition(
82 render,
83 render,
83 observe(({next}) => {
84 observe(({next}) => {
84 const h = target.watch(
85 const h = target.watch(
85 prop,
86 prop,
86 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
87 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
87 );
88 );
89 next(target.get(prop));
88 return () => h.remove();
90 return () => h.remove();
89 })
91 })
90 )
92 )
91 }
93 }
92
94
93 /** Decorates the method which will be registered as the handle for the specified event.
95 /** Decorates the method which will be registered as the handle for the specified event.
94 * This decorator can be applied to DjxWidgetBase subclass methods.
96 * This decorator can be applied to DjxWidgetBase subclass methods.
95 *
97 *
96 * ```
98 * ```
97 * @on("click")
99 * @on("click")
98 * _onClick(eventObj: MouseEvent) {
100 * _onClick(eventObj: MouseEvent) {
99 * // ...
101 * // ...
100 * }
102 * }
101 * ```
103 * ```
102 */
104 */
103 export const on = <E extends string>(...eventNames: E[]) =>
105 export const on = <E extends string>(...eventNames: E[]) =>
104 <K extends string,
106 <K extends string,
105 T extends DjxWidgetBase<any, { [p in E]: EV }>,
107 T extends DjxWidgetBase<any, { [p in E]: EV }>,
106 EV extends Event
108 EV extends Event
107 >(
109 >(
108 target: T,
110 target: T,
109 key: K,
111 key: K,
110 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
112 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
111 ): any => {
113 ): any => {
112 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
114 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
113 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
115 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
114 };
116 };
@@ -1,33 +1,30
1 import { argumentNotNull } from "@implab/core-amd/safe";
1 import { argumentNotNull } from "@implab/core-amd/safe";
2 import { getItemDom } from "./render";
2 import { getItemDom } from "./render";
3 import { RenditionBase } from "./RenditionBase";
3 import { RenditionBase } from "./RenditionBase";
4 import { IScope } from "./Scope";
5
4
6 export class FunctionRendition extends RenditionBase<Node> {
5 export class FunctionRendition extends RenditionBase<Node> {
7 private _component: (...args: any[]) => any;
6 private _component: (...args: any[]) => any;
8
7
9 private _node: Node | undefined;
8 private _node: Node | undefined;
10
9
11 constructor(component: (...args: any[]) => any) {
10 constructor(component: (...args: any[]) => any) {
12 super();
11 super();
13 argumentNotNull(component, "component");
12 argumentNotNull(component, "component");
14
13
15 this._component = component;
14 this._component = component;
16 }
15 }
17
16
18 protected _create(attrs: object, children: any[], scope: IScope) {
17 protected _create(attrs: object, children: any[]) {
19 const _attrs: any = attrs || {};
18 const _attrs: any = attrs || {};
20 const _children = children.map(x => getItemDom(x, scope));
19 const _children = children.map(x => getItemDom(x));
21 this._node = getItemDom(
20 this._node = getItemDom(
22 this._component.call(null, { ..._attrs, children: _children }),
21 this._component.call(null, { ..._attrs, children: _children }));
23 scope
24 );
25 }
22 }
26
23
27 protected _getDomNode() {
24 protected _getDomNode() {
28 if (!this._node)
25 if (!this._node)
29 throw new Error("The instance of the widget isn't created");
26 throw new Error("The instance of the widget isn't created");
30 return this._node;
27 return this._node;
31 }
28 }
32
29
33 }
30 }
@@ -1,39 +1,51
1 import dom = require("dojo/dom-construct");
1 import djDom = require("dojo/dom-construct");
2 import djAttr = require("dojo/dom-attr");
2 import { argumentNotEmptyString } from "@implab/core-amd/safe";
3 import { argumentNotEmptyString } from "@implab/core-amd/safe";
3 import { RenditionBase } from "./RenditionBase";
4 import { RenditionBase } from "./RenditionBase";
4 import { placeAt } from "./traits";
5 import { placeAt } from "./traits";
5 import { IScope } from "./Scope";
6 import { IScope } from "./Scope";
6 import { getItemDom } from "./render";
7 import { getItemDom, renderHook } from "./render";
7
8
8 export class HtmlRendition extends RenditionBase<HTMLElement> {
9 export class HtmlRendition extends RenditionBase<Element> {
9 elementType: string;
10 elementType: string;
10
11
11 _element: HTMLElement | undefined;
12 _element: Element | undefined;
12
13
13 constructor(elementType: string) {
14 constructor(elementType: string) {
14 argumentNotEmptyString(elementType, "elementType");
15 argumentNotEmptyString(elementType, "elementType");
15 super();
16 super();
16
17
17 this.elementType = elementType;
18 this.elementType = elementType;
18 }
19 }
19
20
20 _addChild(child: unknown, scope: IScope): void {
21 _addChild(child: unknown, scope: IScope): void {
21 if (!this._element)
22 if (!this._element)
22 throw new Error("The HTML element isn't created");
23 throw new Error("The HTML element isn't created");
23 placeAt(getItemDom(child, scope), this._element);
24 placeAt(getItemDom(child), this._element);
24 }
25 }
25
26
26 _create(attrs: object, children: unknown[], scope: IScope) {
27 _create({ xmlns, ref, ...attrs }: { xmlns?: string, ref?: JSX.Ref<Element> }, children: unknown[], scope: IScope) {
27 this._element = dom.create(this.elementType, attrs);
28
29 if (xmlns) {
30 this._element = document.createElementNS(xmlns, this.elementType);
31 djAttr.set(this._element, attrs);
32 } else {
33 this._element = djDom.create(this.elementType, attrs);
34 }
28
35
29 children.forEach(v => this._addChild(v, scope));
36 children.forEach(v => this._addChild(v, scope));
37
38 const element = this._element;
39
40 if (ref)
41 renderHook(() => ref(element));
30 }
42 }
31
43
32 _getDomNode() {
44 _getDomNode() {
33 if (!this._element)
45 if (!this._element)
34 throw new Error("The HTML element isn't created");
46 throw new Error("The HTML element isn't created");
35
47
36 return this._element;
48 return this._element;
37 }
49 }
38
50
39 }
51 }
@@ -1,55 +1,55
1 import { id as mid } from "module";
1 import { id as mid } from "module";
2 import { TraceSource } from "@implab/core-amd/log/TraceSource";
2 import { TraceSource } from "@implab/core-amd/log/TraceSource";
3 import { argumentNotNull } from "@implab/core-amd/safe";
3 import { argumentNotNull } from "@implab/core-amd/safe";
4 import { getItemDom, render } from "./render";
4 import { getScope, render } from "./render";
5 import { RenditionBase } from "./RenditionBase";
5 import { RenditionBase } from "./RenditionBase";
6 import { IScope, Scope } from "./Scope";
6 import { Scope } from "./Scope";
7 import { Observable } from "../observable";
7 import { Observable } from "../observable";
8 import { destroy } from "./traits";
8
9
9 const trace = TraceSource.get(mid);
10 const trace = TraceSource.get(mid);
10
11
11 export class WatchRendition<T> extends RenditionBase<Node> {
12 export class WatchRendition<T> extends RenditionBase<Node> {
12 private readonly _component: (arg: T) => unknown;
13 private readonly _component: (arg: T) => unknown;
13
14
14 private _node: Node;
15 private _node: Node;
15
16
16 private readonly _scope = new Scope();
17 private readonly _scope = new Scope();
17
18
18 private readonly _subject: Observable<T>;
19 private readonly _subject: Observable<T>;
19
20
20 constructor(component: (arg: T) => unknown, subject: Observable<T>) {
21 constructor(component: (arg: T) => unknown, subject: Observable<T>) {
21 super();
22 super();
22 argumentNotNull(component, "component");
23 argumentNotNull(component, "component");
23
24
24 this._component = component;
25 this._component = component;
25
26
26 this._subject = subject;
27 this._subject = subject;
27
28
28 this._node = document.createComment("WatchRendition placeholder");
29 this._node = document.createComment("WatchRendition placeholder");
29 }
30 }
30
31
31 protected _create(attrs: object, children: any[], scope: IScope) {
32 protected _create(attrs: object, children: any[]) {
33 const scope = getScope();
32 scope.own(this._scope);
34 scope.own(this._scope);
33 scope.own(this._subject.on({ next: this._onValue }));
35 scope.own(this._subject.on({ next: this._onValue }));
34 }
36 }
35
37
36 private _onValue = (value: T) =>
38 private _onValue = (value: T) =>
37 void this._render(value).catch( e => trace.error(e));
39 void this._render(value).catch( e => trace.error(e));
38
40
39 private async _render(value: T) {
41 private async _render(value: T) {
40 const prevNode = this._node;
41 this._scope.clean();
42 this._scope.clean();
42
43 const [refNode, ...rest] = await render(this._component(value), this._node, "replace", this._scope);
43 this._node = await render(this._component(value), this._scope);
44 this._node = refNode;
44
45 this._scope.own(() => rest.forEach(destroy));
45 this.placeAt(prevNode, "replace");
46 }
46 }
47
47
48 protected _getDomNode() {
48 protected _getDomNode() {
49 if (!this._node)
49 if (!this._node)
50 throw new Error("The instance of the widget isn't created");
50 throw new Error("The instance of the widget isn't created");
51 return this._node;
51 return this._node;
52 }
52 }
53
53
54
54
55 }
55 }
@@ -1,129 +1,134
1 import { argumentNotNull } from "@implab/core-amd/safe";
1 import { argumentNotNull } from "@implab/core-amd/safe";
2 import { RenditionBase } from "./RenditionBase";
2 import { RenditionBase } from "./RenditionBase";
3 import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits";
3 import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits";
4 import registry = require("dijit/registry");
4 import registry = require("dijit/registry");
5 import ContentPane = require("dijit/layout/ContentPane");
5 import ContentPane = require("dijit/layout/ContentPane");
6 import { IScope } from "./Scope";
6 import { IScope } from "./Scope";
7 import { getItemDom, getScope } from "./render";
7 import { getItemDom, getScope, renderHook } from "./render";
8
8
9 // tslint:disable-next-line: class-name
9 // tslint:disable-next-line: class-name
10 export interface _Widget {
10 export interface _Widget {
11 domNode: Node;
11 domNode: Node;
12
12
13 containerNode?: Node;
13 containerNode?: Node;
14
14
15 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
15 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
16 startup?(): void;
16 startup?(): void;
17
17
18 addChild?(widget: any, index?: number): void;
18 addChild?(widget: unknown, index?: number): void;
19 }
19 }
20
20
21 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
21 export type _WidgetCtor = new (attrs: {}, srcNode?: string | Node) => _Widget;
22
22
23 export class WidgetRendition extends RenditionBase<Node> {
23 export class WidgetRendition extends RenditionBase<Node> {
24 readonly widgetClass: _WidgetCtor;
24 readonly widgetClass: _WidgetCtor;
25
25
26 _instance: _Widget | undefined;
26 _instance: _Widget | undefined;
27
27
28 constructor(widgetClass: _WidgetCtor) {
28 constructor(widgetClass: _WidgetCtor) {
29 super();
29 super();
30 argumentNotNull(widgetClass, "widgetClass");
30 argumentNotNull(widgetClass, "widgetClass");
31
31
32 this.widgetClass = widgetClass;
32 this.widgetClass = widgetClass;
33 }
33 }
34
34
35 _addChild(child: any, scope: IScope): void {
35 _addChild(child: unknown, scope: IScope): void {
36 const instance = this._getInstance();
36 const instance = this._getInstance();
37
37
38 if (instance.addChild) {
38 if (instance.addChild) {
39 if (child instanceof WidgetRendition) {
39 if (child instanceof WidgetRendition) {
40 // layout containers add custom logic to addChild methods
40 // layout containers add custom logic to addChild methods
41 instance.addChild(child.getWidgetInstance(scope));
41 instance.addChild(child.getWidgetInstance(scope));
42 } else if (isWidget(child)) {
42 } else if (isWidget(child)) {
43 instance.addChild(child);
43 instance.addChild(child);
44 } else {
44 } else {
45 const childDom = getItemDom(child, scope);
45 const childDom = getItemDom(child);
46 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
46 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
47
47
48 if (w) {
48 if (w) {
49 instance.addChild(w);
49 instance.addChild(w);
50 } else {
50 } else {
51 if (!instance.containerNode)
51 if (!instance.containerNode)
52 throw new Error("Failed to add DOM content. The widget doesn't have a containerNode");
52 throw new Error("Failed to add DOM content. The widget doesn't have a containerNode");
53
53
54 // the current widget isn't started, it's children shouldn't start too
54 // the current widget isn't started, it's children shouldn't start too
55 placeAt(getItemDom(child,scope), instance.containerNode, "last");
55 placeAt(getItemDom(child), instance.containerNode, "last");
56 }
56 }
57 }
57 }
58 } else {
58 } else {
59 if (!instance.containerNode)
59 if (!instance.containerNode)
60 throw new Error("The widget doesn't have neither addChild nor containerNode");
60 throw new Error("The widget doesn't have neither addChild nor containerNode");
61
61
62 // the current widget isn't started, it's children shouldn't start too
62 // the current widget isn't started, it's children shouldn't start too
63 placeAt(getItemDom(child, scope), instance.containerNode, "last");
63 placeAt(getItemDom(child), instance.containerNode, "last");
64 }
64 }
65 }
65 }
66
66
67 protected _create(attrs: any, children: any[], scope: IScope) {
67 protected _create({ref, ...attrs}: {ref?: JSX.Ref<_Widget>}, children: unknown[], scope: IScope) {
68 if (this.widgetClass.prototype instanceof ContentPane) {
68 if (this.widgetClass.prototype instanceof ContentPane) {
69 // a special case for the ContentPane this is for
69 // a special case for the ContentPane this is for
70 // the compatibility with this heavy widget, all
70 // compatibility with that heavy widget, all
71 // regular containers could be easily manipulated
71 // regular containers could be easily manipulated
72 // through `containerNode` property or `addChild` method.
72 // through `containerNode` property or `addChild` method.
73
73
74 // render children to the DocumentFragment
74 // render children to the DocumentFragment
75 const content = document.createDocumentFragment();
75 const content = document.createDocumentFragment();
76 children.forEach(child => content.appendChild(getItemDom(child, scope)));
76 children.forEach(child => content.appendChild(getItemDom(child)));
77
77
78 // set the content property to the parameters of the widget
78 // set the content property to the parameters of the widget
79 const _attrs = { ...attrs, content };
79 const _attrs = { ...attrs, content };
80 this._instance = new this.widgetClass(_attrs);
80 this._instance = new this.widgetClass(_attrs);
81 } else {
81 } else {
82 this._instance = new this.widgetClass(attrs);
82 this._instance = new this.widgetClass(attrs);
83 children.forEach(x => this._addChild(x, scope));
83 children.forEach(x => this._addChild(x, scope));
84 }
84 }
85
85
86 if (ref) {
87 const instance = this._instance;
88 renderHook(() => ref(instance));
89 }
90
86 }
91 }
87
92
88 private _getInstance() {
93 private _getInstance() {
89 if (!this._instance)
94 if (!this._instance)
90 throw new Error("The instance of the widget isn't created");
95 throw new Error("The instance of the widget isn't created");
91 return this._instance;
96 return this._instance;
92 }
97 }
93
98
94 protected _getDomNode() {
99 protected _getDomNode() {
95 if (!this._instance)
100 if (!this._instance)
96 throw new Error("The instance of the widget isn't created");
101 throw new Error("The instance of the widget isn't created");
97 return this._instance.domNode;
102 return this._instance.domNode;
98 }
103 }
99
104
100 /** Overrides default placeAt implementation. Calls placeAt of the
105 /** Overrides default placeAt implementation. Calls placeAt of the
101 * widget and then starts it.
106 * widget and then starts it.
102 *
107 *
103 * @param refNode A node or id of the node where the widget should be placed.
108 * @param refNode A node or id of the node where the widget should be placed.
104 * @param position A position relative to refNode.
109 * @param position A position relative to refNode.
105 */
110 */
106 placeAt(refNode: string | Node, position?: DojoNodePosition) {
111 placeAt(refNode: string | Node, position?: DojoNodePosition) {
107 this.ensureCreated(getScope());
112 this.ensureCreated(getScope());
108 const instance = this._getInstance();
113 const instance = this._getInstance();
109 if (typeof instance.placeAt === "function") {
114 if (typeof instance.placeAt === "function") {
110 instance.placeAt(refNode, position);
115 instance.placeAt(refNode, position);
111
116
112 // fix the dojo startup behavior when the widget is placed
117 // fix the dojo startup behavior when the widget is placed
113 // directly to the document and doesn't have any enclosing widgets
118 // directly to the document and doesn't have any enclosing widgets
114 const parentWidget = instance.domNode.parentNode ?
119 const parentWidget = instance.domNode.parentNode ?
115 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
120 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
116 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
121 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
117 instance.startup();
122 instance.startup();
118 } else {
123 } else {
119 // the widget doesn't have a placeAt method, strange but whatever
124 // the widget doesn't have a placeAt method, strange but whatever
120 super.placeAt(refNode, position);
125 super.placeAt(refNode, position);
121 }
126 }
122 }
127 }
123
128
124 getWidgetInstance(scope?: IScope) {
129 getWidgetInstance(scope?: IScope) {
125 this.ensureCreated(scope || getScope());
130 this.ensureCreated(scope || getScope());
126 return this._getInstance();
131 return this._getInstance();
127 }
132 }
128
133
129 }
134 }
@@ -1,60 +1,104
1 import { IScope, Scope } from "./Scope";
1 import { TraceSource } from "@implab/core-amd/log/TraceSource";
2 import { destroy, isNode, isRendition, isWidget, Rendition } from "./traits";
2 import { isPromise } from "@implab/core-amd/safe";
3 import { id as mid } from "module";
4 import { Scope } from "./Scope";
5 import { autostartWidgets, collectNodes, DojoNodePosition, isDocumentFragmentNode, isNode, isRendition, isWidget, placeAt } from "./traits";
6
7 const trace = TraceSource.get(mid);
3
8
4 let _scope = Scope.dummy;
9 let _scope = Scope.dummy;
5
10
6 const beginRender = async () => {
11 let renderCount = 0;
12
13 const hooks: (() => void)[] = [];
14
15 const guard = (cb: () => unknown) => {
16 try {
17 const result = cb()
18 if (isPromise(result)) {
19 const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret);
20 result.then(warn, warn);
21 }
22 } catch (e) {
23 trace.error(e);
24 }
7 }
25 }
8
26
27 /**
28 * Schedules rendering micro task
29 * @returns Promise
30 */
31 const beginRender = () => {
32 renderCount++;
33 return Promise.resolve();
34 }
35
36 /**
37 * Completes render operation
38 */
9 const endRender = () => {
39 const endRender = () => {
40 if (!--renderCount) {
41 hooks.forEach(guard);
42 hooks.length = 0;
43 }
44 }
45
46 export const renderHook = (hook: () => void) => {
47 if (renderCount)
48 hooks.push(hook);
49 else
50 guard(hook);
10 }
51 }
11
52
12 /** Returns the current scope */
53 /** Returns the current scope */
13 export const getScope = () => _scope;
54 export const getScope = () => _scope;
14
55
15 /** Schedules the rendition to be rendered to the DOM Node
56 /** Schedules the rendition to be rendered to the DOM Node
16 * @param rendition The rendition to be rendered
57 * @param rendition The rendition to be rendered
17 * @param scope The scope
58 * @param scope The scope
18 */
59 */
19 export const render = async (rendition: unknown, scope = Scope.dummy) => {
60 export const render = async (rendition: unknown, refNode: Node, position: DojoNodePosition = "last", scope = Scope.dummy) => {
20 await beginRender();
61 await beginRender();
21 const prev = _scope;
62 const prev = _scope;
22 _scope = scope;
63 _scope = scope;
23 try {
64 try {
24 const node = getItemDom(rendition, scope);
65 const domNode = getItemDom(rendition);
25 scope.own(() => destroy(node));
66 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode];
26 return node;
67 placeAt(domNode, refNode, position);
68 startupPending.forEach(autostartWidgets);
69
70 return startupPending;
27 } finally {
71 } finally {
28 _scope = prev;
72 _scope = prev;
29 endRender();
73 endRender();
30 }
74 }
31 }
75 }
32
76
33 /** Renders DOM element for different types of the argument. */
77 /** Renders DOM element for different types of the argument. */
34 export const getItemDom = (v: unknown, scope: IScope) => {
78 export const getItemDom = (v: unknown) => {
35 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
79 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
36 // primitive types converted to the text nodes
80 // primitive types converted to the text nodes
37 return document.createTextNode(v.toString());
81 return document.createTextNode(v.toString());
38 } else if (isNode(v)) {
82 } else if (isNode(v)) {
39 // nodes are kept as is
83 // nodes are kept as is
40 return v;
84 return v;
41 } else if (isRendition(v)) {
85 } else if (isRendition(v)) {
42 // renditions are instantiated
86 // renditions are instantiated
43 return v.getDomNode(scope);
87 return v.getDomNode();
44 } else if (isWidget(v)) {
88 } else if (isWidget(v)) {
45 // widgets are converted to it's markup
89 // widgets are converted to it's markup
46 return v.domNode;
90 return v.domNode;
47 } else if (typeof v === "boolean" || v === null || v === undefined) {
91 } else if (typeof v === "boolean" || v === null || v === undefined) {
48 // null | undefined | boolean are removed, converted to comments
92 // null | undefined | boolean are removed, converted to comments
49 return document.createComment(`[${typeof v} ${String(v)}]`);
93 return document.createComment(`[${typeof v} ${String(v)}]`);
50 } else if (v instanceof Array) {
94 } else if (v instanceof Array) {
51 // arrays will be translated to document fragments
95 // arrays will be translated to document fragments
52 const fragment = document.createDocumentFragment();
96 const fragment = document.createDocumentFragment();
53 v.map(item => getItemDom(item, scope))
97 v.map(item => getItemDom(item))
54 .forEach(node => fragment.appendChild(node));
98 .forEach(node => fragment.appendChild(node));
55 return fragment;
99 return fragment;
56 } else {
100 } else {
57 // bug: explicit error otherwise
101 // bug: explicit error otherwise
58 throw new Error("Invalid parameter: " + v);
102 throw new Error("Invalid parameter: " + v);
59 }
103 }
60 }
104 }
@@ -1,215 +1,214
1 import { IDestroyable } from "@implab/core-amd/interfaces";
1 import { IDestroyable } from "@implab/core-amd/interfaces";
2 import { isDestroyable } from "@implab/core-amd/safe";
2 import { isDestroyable } from "@implab/core-amd/safe";
3 import _WidgetBase = require("dijit/_WidgetBase");
3 import _WidgetBase = require("dijit/_WidgetBase");
4 import registry = require("dijit/registry");
4 import registry = require("dijit/registry");
5 import { IScope } from "./Scope";
6
5
7 interface _WidgetBaseConstructor {
6 interface _WidgetBaseConstructor {
8 new <A = {}, E extends { [k in keyof E]: Event } = {}>(params?: Partial<_WidgetBase<E> & A>, srcNodeRef?: dojo.NodeOrString): _WidgetBase<E> & dojo._base.DeclareCreatedObject;
7 new <A = {}, E extends { [k in keyof E]: Event } = {}>(params?: Partial<_WidgetBase<E> & A>, srcNodeRef?: dojo.NodeOrString): _WidgetBase<E> & dojo._base.DeclareCreatedObject;
9 prototype: _WidgetBase<any>;
8 prototype: _WidgetBase<any>;
10 }
9 }
11
10
12 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
11 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
13
12
14 export type DojoNodeLocation = [Node, DojoNodePosition];
13 export type DojoNodeLocation = [Node, DojoNodePosition];
15
14
16 export interface Rendition<TNode extends Node = Node> {
15 export interface Rendition<TNode extends Node = Node> {
17 getDomNode(scope?: IScope): TNode;
16 getDomNode(): TNode;
18
17
19 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
18 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
20 }
19 }
21
20
22 /**
21 /**
23 * @deprecated use Rendition
22 * @deprecated use Rendition
24 */
23 */
25 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
24 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
26
25
27 export interface IRecursivelyDestroyable {
26 export interface IRecursivelyDestroyable {
28 destroyRecursive(): void;
27 destroyRecursive(): void;
29 }
28 }
30
29
31 export const isNode = (el: unknown): el is Node => !!(el && (el as Node).nodeName && (el as Node).nodeType);
30 export const isNode = (el: unknown): el is Node => !!(el && (el as Node).nodeName && (el as Node).nodeType);
32
31
33 export const isElementNode = (el: unknown): el is Element => isNode(el) && el.nodeType === 1;
32 export const isElementNode = (el: unknown): el is Element => isNode(el) && el.nodeType === 1;
34
33
35 export const isTextNode = (el: unknown): el is Text => isNode(el) && el.nodeType === 3;
34 export const isTextNode = (el: unknown): el is Text => isNode(el) && el.nodeType === 3;
36
35
37 export const isProcessingInstructionNode = (el: unknown): el is ProcessingInstruction => isNode(el) && el.nodeType === 7;
36 export const isProcessingInstructionNode = (el: unknown): el is ProcessingInstruction => isNode(el) && el.nodeType === 7;
38
37
39 export const isCommentNode = (el: unknown): el is Comment => isNode(el) && el.nodeType === 8;
38 export const isCommentNode = (el: unknown): el is Comment => isNode(el) && el.nodeType === 8;
40
39
41 export const isDocumentNode = (el: unknown): el is Document => isNode(el) && el.nodeType === 9;
40 export const isDocumentNode = (el: unknown): el is Document => isNode(el) && el.nodeType === 9;
42
41
43 export const isDocumentTypeNode = (el: unknown): el is DocumentType => isNode(el) && el.nodeType === 10;
42 export const isDocumentTypeNode = (el: unknown): el is DocumentType => isNode(el) && el.nodeType === 10;
44
43
45 export const isDocumentFragmentNode = (el: any): el is DocumentFragment => isNode(el) && el.nodeType === 11;
44 export const isDocumentFragmentNode = (el: any): el is DocumentFragment => isNode(el) && el.nodeType === 11;
46
45
47 export const isWidget = (v: unknown): v is _WidgetBase => !!(v && "domNode" in (v as _WidgetBase));
46 export const isWidget = (v: unknown): v is _WidgetBase => !!(v && "domNode" in (v as _WidgetBase));
48
47
49 export const isRendition = (v: unknown): v is Rendition => !!(v && typeof (v as Rendition).getDomNode === "function");
48 export const isRendition = (v: unknown): v is Rendition => !!(v && typeof (v as Rendition).getDomNode === "function");
50
49
51 /**
50 /**
52 * @deprecated use isRendition
51 * @deprecated use isRendition
53 */
52 */
54 export const isBuildContext = isRendition;
53 export const isBuildContext = isRendition;
55
54
56 export const isPlainObject = (v: object) => {
55 export const isPlainObject = (v: object) => {
57 if (typeof v !== "object")
56 if (typeof v !== "object")
58 return false;
57 return false;
59
58
60 const vp = Object.getPrototypeOf(v);
59 const vp = Object.getPrototypeOf(v);
61 return !vp || vp === Object.prototype;
60 return !vp || vp === Object.prototype;
62 }
61 }
63
62
64 export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor =>
63 export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor =>
65 typeof v === "function" && v.prototype && (
64 typeof v === "function" && v.prototype && (
66 "domNode" in v.prototype ||
65 "domNode" in v.prototype ||
67 "buildRendering" in v.prototype
66 "buildRendering" in v.prototype
68 );
67 );
69
68
70
69
71 /** Tests whether the specified node is placed in visible dom.
70 /** Tests whether the specified node is placed in visible dom.
72 * @param {Node} node The node to test
71 * @param {Node} node The node to test
73 */
72 */
74 export const isInPage = (node: Node) => node === document.body ? false : document.body.contains(node);
73 export const isInPage = (node: Node) => node === document.body ? false : document.body.contains(node);
75
74
76 export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable =>
75 export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable =>
77 !!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function");
76 !!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function");
78
77
79
78
80
79
81 /** Destroys DOM Node with all contained widgets.
80 /** Destroys DOM Node with all contained widgets.
82 * If the specified node is the root node of a widget, then the
81 * If the specified node is the root node of a widget, then the
83 * widget will be destroyed.
82 * widget will be destroyed.
84 *
83 *
85 * @param target DOM Node or widget to destroy
84 * @param target DOM Node or widget to destroy
86 */
85 */
87 export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => {
86 export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => {
88 if (isRecursivelyDestroyable(target)) {
87 if (isRecursivelyDestroyable(target)) {
89 target.destroyRecursive();
88 target.destroyRecursive();
90 } else if (isDestroyable(target)) {
89 } else if (isDestroyable(target)) {
91 target.destroy();
90 target.destroy();
92 } else if (isNode(target)) {
91 } else if (isNode(target)) {
93 if (isElementNode(target)) {
92 if (isElementNode(target)) {
94 const w = registry.byNode(target);
93 const w = registry.byNode(target);
95 if (w) {
94 if (w) {
96 w.destroyRecursive();
95 w.destroyRecursive();
97 } else {
96 } else {
98 emptyNode(target);
97 emptyNode(target);
99 const parent = target.parentNode;
98 const parent = target.parentNode;
100 if (parent)
99 if (parent)
101 parent.removeChild(target);
100 parent.removeChild(target);
102 }
101 }
103 }
102 }
104 }
103 }
105 }
104 }
106
105
107 /** Empties a content of the specified node and destroys all contained widgets.
106 /** Empties a content of the specified node and destroys all contained widgets.
108 *
107 *
109 * @param target DOM node to empty.
108 * @param target DOM node to empty.
110 */
109 */
111 export const emptyNode = (target: Node) => {
110 export const emptyNode = (target: Node) => {
112 registry.findWidgets(target).forEach(destroy);
111 registry.findWidgets(target).forEach(destroy);
113
112
114 for(let c; c = target.lastChild;){ // intentional assignment
113 for (let c; c = target.lastChild;) { // intentional assignment
115 target.removeChild(c);
114 target.removeChild(c);
116 }
115 }
117 }
116 }
118
117
119 /** This function starts all widgets inside the DOM node if the target is a node
118 /** This function starts all widgets inside the DOM node if the target is a node
120 * or starts widget itself if the target is the widget. If the specified node
119 * or starts widget itself if the target is the widget. If the specified node
121 * associated with the widget that widget will be started.
120 * associated with the widget that widget will be started.
122 *
121 *
123 * @param target DOM node to find and start widgets or the widget itself.
122 * @param target DOM node to find and start widgets or the widget itself.
124 */
123 */
125 export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => {
124 export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => {
126 if (isNode(target)) {
125 if (isNode(target)) {
127 if (isElementNode(target)) {
126 if (isElementNode(target)) {
128 const w = registry.byNode(target);
127 const w = registry.byNode(target);
129 if (w) {
128 if (w) {
130 if (w.startup)
129 if (w.startup)
131 w.startup();
130 w.startup();
132 } else {
131 } else {
133 registry.findWidgets(target, skipNode).forEach(x => x.startup());
132 registry.findWidgets(target, skipNode).forEach(x => x.startup());
134 }
133 }
135 }
134 }
136 } else {
135 } else {
137 if (target.startup)
136 if (target.startup)
138 target.startup();
137 target.startup();
139 }
138 }
140 }
139 }
141
140
142 /** Places the specified DOM node at the specified location.
141 /** Places the specified DOM node at the specified location.
143 *
142 *
144 * @param node The node which should be placed
143 * @param node The node which should be placed
145 * @param refNodeOrId The reference node where the created
144 * @param refNodeOrId The reference node where the created
146 * DOM should be placed.
145 * DOM should be placed.
147 * @param position Optional parameter, specifies the
146 * @param position Optional parameter, specifies the
148 * position relative to refNode. Default is "last" (i.e. last child).
147 * position relative to refNode. Default is "last" (i.e. last child).
149 */
148 */
150 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => {
149 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => {
151 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
150 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
152 if (!ref)
151 if (!ref)
153 return;
152 return;
154
153
155 const parent = ref.parentNode;
154 const parent = ref.parentNode;
156
155
157 if (typeof position == "number") {
156 if (typeof position == "number") {
158 if (ref.childNodes.length <= position) {
157 if (ref.childNodes.length <= position) {
159 ref.appendChild(node);
158 ref.appendChild(node);
160 } else {
159 } else {
161 ref.insertBefore(node, ref.childNodes[position]);
160 ref.insertBefore(node, ref.childNodes[position]);
162 }
161 }
163 } else {
162 } else {
164 switch (position) {
163 switch (position) {
165 case "before":
164 case "before":
166 parent && parent.insertBefore(node, ref);
165 parent && parent.insertBefore(node, ref);
167 break;
166 break;
168 case "after":
167 case "after":
169 parent && parent.insertBefore(node, ref.nextSibling);
168 parent && parent.insertBefore(node, ref.nextSibling);
170 break;
169 break;
171 case "first":
170 case "first":
172 ref.insertBefore(node,ref.firstChild);
171 ref.insertBefore(node, ref.firstChild);
173 break;
172 break;
174 case "last":
173 case "last":
175 ref.appendChild(node);
174 ref.appendChild(node);
176 break;
175 break;
177 case "only":
176 case "only":
178 emptyNode(ref);
177 emptyNode(ref);
179 ref.appendChild(node);
178 ref.appendChild(node);
180 break;
179 break;
181 case "replace":
180 case "replace":
182 if (parent)
181 if (parent)
183 parent.replaceChild(node, ref);
182 parent.replaceChild(node, ref);
184 destroy(ref);
183 destroy(ref);
185 break;
184 break;
186 }
185 }
187 }
186 }
188 }
187 }
189
188
190 /** Collects nodes from collection to an array.
189 /** Collects nodes from collection to an array.
191 *
190 *
192 * @param collection The collection of nodes.
191 * @param collection The collection of nodes.
193 * @returns The array of nodes.
192 * @returns The array of nodes.
194 */
193 */
195 export const collectNodes = (collection: HTMLCollection) => {
194 export const collectNodes = (collection: HTMLCollection) => {
196 const items = [];
195 const items = [];
197 for (let i = 0, n = collection.length; i < n; i++) {
196 for (let i = 0, n = collection.length; i < n; i++) {
198 items.push(collection[i]);
197 items.push(collection[i]);
199 }
198 }
200 return items;
199 return items;
201 };
200 };
202
201
203 /** Starts widgets if the node contained in the document or in the started widget.
202 /** Starts widgets if the node contained in the document or in the started widget.
204 *
203 *
205 * @param node The node to start.
204 * @param node The node to start.
206 */
205 */
207 export const autostartWidgets = (node: Node) => {
206 export const autostartWidgets = (node: Node) => {
208 if (node.parentNode) {
207 if (node.parentNode) {
209 const parentWidget = registry.getEnclosingWidget(node.parentNode);
208 const parentWidget = registry.getEnclosingWidget(node.parentNode);
210 if (parentWidget && parentWidget._started)
209 if (parentWidget && parentWidget._started)
211 return startupWidgets(node);
210 return startupWidgets(node);
212 }
211 }
213 if (isInPage(node))
212 if (isInPage(node))
214 startupWidgets(node);
213 startupWidgets(node);
215 }; No newline at end of file
214 };
@@ -1,80 +1,81
1 /// <reference path="./css-plugin.d.ts"/>
1 /// <reference path="./css-plugin.d.ts"/>
2
2
3 declare namespace JSX {
3 declare namespace JSX {
4
4
5 type Ref<T> = (value: T) => void;
5 type Ref<T> = (value: T) => void;
6
6
7 interface DjxIntrinsicAttributes<E> {
7 interface DjxIntrinsicAttributes<E> {
8 /** alias for className */
8 /** alias for className */
9 class: string;
9 class: string;
10
10
11 /** specifies the name of the property in the widget where the the
11 /** specifies the name of the property in the widget where the the
12 * reference to the current object will be stored
12 * reference to the current object will be stored
13 */
13 */
14 "data-dojo-attach-point": string;
14 "data-dojo-attach-point": string;
15
15
16 /** specifies handlers map for the events */
16 /** specifies handlers map for the events */
17 "data-dojo-attach-event": string;
17 "data-dojo-attach-event": string;
18
18
19 ref: Ref<E>;
19 ref: Ref<E>;
20
20
21 /** @deprecated */
21 /** @deprecated */
22 [attr: string]: any;
22 [attr: string]: any;
23 }
23 }
24
24
25 interface DjxIntrinsicElements {
25 interface DjxIntrinsicElements {
26 }
26 }
27
27
28 type RecursivePartial<T> = T extends string | number | boolean | null | undefined | Function ?
28 type RecursivePartial<T> = T extends string | number | boolean | null | undefined | Function ?
29 T :
29 T :
30 { [k in keyof T]?: RecursivePartial<T[k]> };
30 { [k in keyof T]?: RecursivePartial<T[k]> };
31
31
32 type MatchingMemberKeys<T, U> = {
32 type MatchingMemberKeys<T, U> = {
33 [K in keyof T]: T[K] extends U ? K : never;
33 [K in keyof T]: T[K] extends U ? K : never;
34 }[keyof T];
34 }[keyof T];
35 type NotMatchingMemberKeys<T, U> = {
35 type NotMatchingMemberKeys<T, U> = {
36 [K in keyof T]: T[K] extends U ? never : K;
36 [K in keyof T]: T[K] extends U ? never : K;
37 }[keyof T];
37 }[keyof T];
38
38
39 type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>;
39 type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>;
40
40
41 type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>;
41 type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>;
42
42
43 type ElementAttrNames<E> = NotMatchingMemberKeys<E, (...args: any[]) => any>;
43 type ElementAttrNames<E> = NotMatchingMemberKeys<E, (...args: any[]) => any>;
44
44
45 type ElementAttrType<E, K extends keyof any> = K extends keyof E ? RecursivePartial<E[K]> : string;
45 type ElementAttrType<E, K extends keyof any> = K extends keyof E ? RecursivePartial<E[K]> : string;
46
46
47
47
48 type ElementAttrNamesBlacklist = "children" | "getRootNode" | keyof EventTarget;
48 type ElementAttrNamesBlacklist = "children" | "getRootNode" | keyof EventTarget;
49
49
50 /** This type extracts keys of the specified parameter E by the following rule:
50 /** This type extracts keys of the specified parameter E by the following rule:
51 * 1. skips all ElementAttrNamesBlacklist
51 * 1. skips all ElementAttrNamesBlacklist
52 * 2. skips all methods except with the signature of event handlers
52 * 2. skips all methods except with the signature of event handlers
53 */
53 */
54 type AssignableElementAttrNames<E> = {
54 type AssignableElementAttrNames<E> = {
55 [K in keyof E]: K extends ElementAttrNamesBlacklist ? never :
55 [K in keyof E]: K extends ElementAttrNamesBlacklist ? never :
56 ((evt: Event) => any) extends E[K] ? K :
56 ((evt: Event) => any) extends E[K] ? K :
57 E[K] extends ((...args: any[]) => any) ? never :
57 E[K] extends ((...args: any[]) => any) ? never :
58 K;
58 K;
59 }[keyof E];
59 }[keyof E];
60
60
61 type LaxElement<E extends object> =
61 type LaxElement<E extends object> =
62 Pick<E, AssignableElementAttrNames<E>> &
62 Pick<E, AssignableElementAttrNames<E>> &
63 DjxIntrinsicAttributes<E>;
63 DjxIntrinsicAttributes<E>;
64
64
65 type LaxIntrinsicElementsMap = {
65 type LaxIntrinsicElementsMap = {
66 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
66 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
67 } & DjxIntrinsicElements;
67 } & DjxIntrinsicElements;
68
68
69 type IntrinsicElements = {
69 type IntrinsicElements = {
70 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
70 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
71 }
71 }
72
72
73 interface ElementChildrenAttribute {
73 interface ElementChildrenAttribute {
74 children: {};
74 children: {};
75 }
75 }
76
76
77 interface IntrinsicClassAttributes<T> {
77 interface IntrinsicClassAttributes<T> {
78 ref: (value: T) => void;
78 ref?: (value: T) => void;
79 children?: unknown;
79 }
80 }
80 }
81 }
@@ -1,108 +1,123
1 plugins {
1 plugins {
2 id "org.implab.gradle-typescript" version "1.3.4"
2 id "org.implab.gradle-typescript" version "1.3.4"
3 id "ivy-publish"
3 id "ivy-publish"
4 }
4 }
5
5
6 def container = "djx-playground"
7
6 configurations {
8 configurations {
7 npmLocal
9 npmLocal
8 }
10 }
9
11
10 dependencies {
12 dependencies {
11 npmLocal project(":djx")
13 npmLocal project(":djx")
12 }
14 }
13
15
14 def bundleDir = fileTree(layout.buildDirectory.dir("bundle")) {
16 def bundleDir = fileTree(layout.buildDirectory.dir("bundle")) {
15 builtBy "bundle"
17 builtBy "bundle"
16 }
18 }
17
19
18 typescript {
20 typescript {
19 compilerOptions {
21 compilerOptions {
20 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
22 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
21 // listFiles = true
23 // listFiles = true
22 strict = true
24 strict = true
23 types = ["requirejs", "@implab/dojo-typings", "@implab/djx"]
25 types = ["requirejs", "@implab/dojo-typings", "@implab/djx"]
24 module = "amd"
26 module = "amd"
25 it.target = "es5"
27 it.target = "es5"
26 experimentalDecorators = true
28 experimentalDecorators = true
27 noUnusedLocals = false
29 noUnusedLocals = false
28 jsx = "react"
30 jsx = "react"
29 jsxFactory = "createElement"
31 jsxFactory = "createElement"
30 moduleResolution = "node"
32 moduleResolution = "node"
31 // dojo-typings are sick
33 // dojo-typings are sick
32 skipLibCheck = true
34 skipLibCheck = true
33 // traceResolution = true
35 // traceResolution = true
34 // baseUrl = "./"
36 // baseUrl = "./"
35 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
37 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
36 // baseUrl = "$projectDir/src/typings"
38 // baseUrl = "$projectDir/src/typings"
37 // typeRoots = ["$projectDir/src/typings"]
39 // typeRoots = ["$projectDir/src/typings"]
38 }
40 }
39 tscCmd = "$projectDir/node_modules/.bin/tsc"
41 tscCmd = "$projectDir/node_modules/.bin/tsc"
40 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
42 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
41 esLintCmd = "$projectDir/node_modules/.bin/eslint"
43 esLintCmd = "$projectDir/node_modules/.bin/eslint"
42 }
44 }
43
45
44 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
46 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
45 compilerOptions {
47 compilerOptions {
46 if (symbols != 'none') {
48 if (symbols != 'none') {
47 sourceMap = true
49 sourceMap = true
48 switch(symbols) {
50 switch(symbols) {
49 case "local":
51 case "local":
50 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
52 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
51 break;
53 break;
52 }
54 }
53 }
55 }
54 }
56 }
55 }
57 }
56
58
57 npmInstall {
59 npmInstall {
58 //npmInstall.dependsOn it
60 //npmInstall.dependsOn it
59 dependsOn configurations.npmLocal
61 dependsOn configurations.npmLocal
60
62
61 doFirst {
63 doFirst {
62 configurations.npmLocal.each { f ->
64 configurations.npmLocal.each { f ->
63 exec {
65 exec {
64 commandLine "npm", "install", f, "--save-dev"
66 commandLine "npm", "install", f, "--save-dev"
65 }
67 }
66 }
68 }
67 }
69 }
68 }
70 }
69
71
70 clean {
72 clean {
71 doFirst {
73 doFirst {
72 delete "$buildDir/bundle"
74 delete "$buildDir/bundle"
73 }
75 }
74 }
76 }
75
77
76
78
77 task processResourcesBundle(type: Copy) {
79 task processResourcesBundle(type: Copy) {
78 from "src/bundle"
80 from "src/bundle"
79 into layout.buildDirectory.dir("bundle")
81 into layout.buildDirectory.dir("bundle")
80 }
82 }
81
83
82 task copyModules(type: Copy) {
84 task copyModules(type: Copy) {
83 dependsOn npmInstall
85 dependsOn npmInstall
84 into layout.buildDirectory.dir("bundle/js");
86 into layout.buildDirectory.dir("bundle/js");
85
87
86 def pack = { String jsmod ->
88 def pack = { String jsmod ->
87 into(jsmod) {
89 into(jsmod) {
88 from npm.module(jsmod)
90 from npm.module(jsmod)
89 }
91 }
90 }
92 }
91
93
92
94
93 pack("@implab/djx")
95 pack("@implab/djx")
94 pack("@implab/core-amd")
96 pack("@implab/core-amd")
95 pack("dojo")
97 pack("dojo")
96 pack("dijit")
98 pack("dijit")
97 from npm.module("requirejs/require.js")
99 from npm.module("requirejs/require.js")
98 }
100 }
99
101
100 task copyApp(type: Copy) {
102 task copyApp(type: Copy) {
101 dependsOn assemble
103 dependsOn assemble
102 from typescript.assemblyDir
104 from typescript.assemblyDir
103 into layout.buildDirectory.dir("bundle/js/app")
105 into layout.buildDirectory.dir("bundle/js/app")
104 }
106 }
105
107
106 task bundle {
108 task bundle {
107 dependsOn copyModules, processResourcesBundle, copyApp
109 dependsOn copyModules, processResourcesBundle, copyApp
110 }
111
112 task up(type: Exec) {
113 dependsOn bundle
114 commandLine "podman", "run", "--rm", "-d",
115 "--name", container,
116 "-p", "2078:80",
117 "-v", "$buildDir/bundle:/srv/www/htdocs",
118 "registry.implab.org/implab/apache2:latest"
119 }
120
121 task stop(type: Exec) {
122 commandLine "podman", "stop", container
108 } No newline at end of file
123 }
@@ -1,21 +1,49
1 import { djbase, djclass } from "@implab/djx/declare";
1 import { djbase, djclass } from "@implab/djx/declare";
2 import { DjxWidgetBase } from "@implab/djx/tsx/DjxWidgetBase";
2 import { DjxWidgetBase } from "@implab/djx/tsx/DjxWidgetBase";
3 import { createElement } from "@implab/djx/tsx";
3 import { createElement, watch } from "@implab/djx/tsx";
4 import ProgressBar from "./ProgressBar";
4 import ProgressBar from "./ProgressBar";
5 import Button = require("dijit/form/Button");
5
6
6 const ref = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
7 const ref = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
7
8
8 @djclass
9 @djclass
9 export default class MainWidget extends djbase(DjxWidgetBase) {
10 export default class MainWidget extends djbase(DjxWidgetBase) {
10
11
11 titleNode?: HTMLHeadingElement;
12 titleNode?: HTMLHeadingElement;
12
13
13 progressBar?: ProgressBar;
14 progressBar?: ProgressBar;
14
15
16 count = 0;
17
18 showCounter = false;
19
15 render() {
20 render() {
16 return <div>
21 return <div className="tundra">
17 <h2 ref={ref(this, "titleNode")}>Hi!</h2>
22 <h2 ref={ref(this, "titleNode")}>Hi!</h2>
18 <ProgressBar ref={ref(this, "progressBar")}/>
23 <ProgressBar ref={ref(this, "progressBar")} />
24 {watch(this, "showCounter", flag => flag &&
25 <section style={{padding: "10px"}}>
26 <label>
27 Counter: {watch(this, "count", v => [<span>{v}</span>, " ", <span>sec</span>])}
28 </label>
29 </section>
30 )}
31 <Button onClick={this._onToggleCounterClick}>Toggle counter</Button>
19 </div>;
32 </div>;
20 }
33 }
34
35 postCreate(): void {
36 super.postCreate();
37
38 const inc = () => {
39 this.set("count", this.count + 1);
40 this.defer(inc, 1000);
21 }
41 }
42
43 inc();
44 }
45
46 private _onToggleCounterClick = () => {
47 this.set("showCounter", !this.showCounter);
48 }
49 }
@@ -1,4 +1,7
1 import MainWidget from "./MainWidget";
1 import MainWidget from "./MainWidget";
2 import "@implab/djx/css!dojo/resources/dojo.css"
3 import "@implab/djx/css!dijit/themes/dijit.css"
4 import "@implab/djx/css!dijit/themes/tundra/tundra.css"
2
5
3 const w = new MainWidget();
6 const w = new MainWidget();
4 w.placeAt(document.body); No newline at end of file
7 w.placeAt(document.body);
General Comments 0
You need to be logged in to leave comments. Login now