##// END OF EJS Templates
`Subscribable` is made compatible with rxjs, added map, filter and scan...
cin -
r102:c65ea2350b1a v1.3
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,13
1 import * as t from "tap";
2 import { Baz } from "./mock/Baz";
3
4 t.comment("Declare tests");
5
6 const baz = new Baz();
7
8 const data: string[] = [];
9 baz.writeHello(data);
10 t.pass("Test complete");
11
12 // tslint:disable-next-line: no-console
13 t.comment(data.join("\n"));
@@ -0,0 +1,52
1 import { observe } from "./observable";
2 import * as t from "tap";
3
4 const subj1 = observe<number>(({ next, complete }) => {
5 next(1);
6 complete();
7 next(2);
8 });
9
10 const consumer1 = {
11 sum: 0,
12 next(v: number) {
13 this.sum += v;
14 }
15 }
16
17 subj1.subscribe(consumer1);
18 t.equal(consumer1.sum, 1, "Should get only one value");
19
20 subj1.subscribe(consumer1);
21 t.equal(consumer1.sum, 2, "Should get the value again");
22
23 const consumer2 = {
24 value: 0,
25 completed: false,
26 next(v: number) { this.value = v; },
27 complete() { this.completed = true; }
28 };
29
30 let maps = 0;
31
32 subj1
33 .map(v => {
34 t.comment("map1: " + v * 2);
35 maps++;
36 return v * 2;
37 })
38 .map (v => {
39 t.comment("map2: " + v * 2);
40 maps++;
41 return v * 2;
42 })
43 .map(v => {
44 t.comment("map3: " + v * 2);
45 maps++;
46 return v * 2
47 })
48 .subscribe(consumer2);
49
50 t.equal(consumer2.value, 8, "Should map");
51 t.equal(maps, 3, "The map chain should not be executed after completion");
52 t.ok(consumer2.completed, "The completion signal should pass through"); No newline at end of file
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,34 +1,36
1 {
1 {
2 "name": "@implab/djx",
2 "name": "@implab/djx",
3 "version": "0.0.1-dev",
3 "version": "0.0.1-dev",
4 "description": "Supports using dojo version 1 with typescript and .tsx files",
4 "description": "Supports using dojo version 1 with typescript and .tsx files",
5 "keywords": [
5 "keywords": [
6 "dojo",
6 "dojo",
7 "tsx",
7 "tsx",
8 "typescript",
8 "typescript",
9 "widgets"
9 "widgets"
10 ],
10 ],
11 "author": "Implab team",
11 "author": "Implab team",
12 "license": "BSD-2-Clause",
12 "license": "BSD-2-Clause",
13 "repository": "https://code.implab.org/implab/implabjs-djx",
13 "repository": "https://code.implab.org/implab/implabjs-djx",
14 "publishConfig": {
14 "publishConfig": {
15 "access": "public"
15 "access": "public"
16 },
16 },
17 "peerDependencies": {
17 "peerDependencies": {
18 "@implab/core-amd": "^1.4.0",
18 "@implab/core-amd": "^1.4.0",
19 "dojo": "^1.10.0"
19 "dojo": "^1.10.0"
20 },
20 },
21 "devDependencies": {
21 "devDependencies": {
22 "@implab/core-amd": "^1.4.0",
22 "@implab/core-amd": "^1.4.0",
23 "@types/chai": "4.1.3",
23 "@types/chai": "4.1.3",
24 "@types/requirejs": "2.1.31",
24 "@types/requirejs": "2.1.31",
25 "@types/yaml": "1.2.0",
25 "@types/yaml": "1.2.0",
26 "@types/tap": "15.0.7",
26 "dojo": "1.16.0",
27 "dojo": "1.16.0",
27 "@implab/dojo-typings": "1.0.0",
28 "@implab/dojo-typings": "1.0.0",
28 "eslint": "6.8.0",
29 "eslint": "6.8.0",
29 "requirejs": "2.3.6",
30 "requirejs": "2.3.6",
30 "tslint": "^6.1.3",
31 "tslint": "^6.1.3",
31 "typescript": "4.7.4",
32 "typescript": "4.8.2",
32 "yaml": "~1.7.2"
33 "yaml": "~1.7.2",
34 "tap": "16.3.0"
33 }
35 }
34 }
36 }
@@ -1,34 +1,147
1 import { IDestroyable } from "@implab/core-amd/interfaces";
1 /**
2 * The interface for the consumer of an observable sequence
3 */
4 export interface Observer<T> {
5 /**
6 * Called for the next element in the sequence
7 */
8 next: (value: T) => void;
2
9
3 export interface Sink<T> {
10 /**
4 next: (value: T) => void;
11 * Called once when the error occurs in the sequence.
12 */
5 error: (e: unknown) => void;
13 error: (e: unknown) => void;
14
15 /**
16 * Called once at the end of the sequence.
17 */
6 complete: () => void;
18 complete: () => void;
7 }
19 }
8
20
9 export type Consumer<T> = Partial<Sink<T>>;
21 /**
22 * The group of functions to feed an observable. This methods are provided to
23 * the producer to generate a stream of events.
24 */
25 export type Sink<T> = {
26 [k in keyof Observer<T>]: (this: void, ...args: Parameters<Observer<T>[k]>) => void;
27 };
10
28
11 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
29 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
12
30
13 export interface Observable<T> {
31 export interface Unsubscribable {
14 on(consumer: Partial<Sink<T>>): IDestroyable;
32 unsubscribe(): void;
15 }
33 }
34
35 export const isUnsubsribable = (v: unknown): v is Unsubscribable =>
36 v !== null && v !== undefined && typeof (v as Unsubscribable).unsubscribe === "function";
37
38 export const isSubsribable = (v: unknown): v is Subscribable<unknown> =>
39 v !== null && v !== undefined && typeof (v as Subscribable<unknown>).subscribe === "function";
16
40
17 const noop = () => {};
41 export interface Subscribable<T> {
42 subscribe(consumer: Partial<Observer<T>>): Unsubscribable;
43 }
44
45 /** The observable source of items. */
46 export interface Observable<T> extends Subscribable<T> {
47 /** Transforms elements of the sequence with the specified mapper
48 *
49 * @param mapper The mapper used to transform the values
50 */
51 map<T2>(mapper: (value: T) => T2): Observable<T2>;
18
52
19 const sink = <T>(consumer: Consumer<T>) => {
53 /** Filters elements of the sequence. The resulting sequence will
20 const { next = noop, error = noop, complete = noop } = consumer;
54 * contain only elements which match the specified predicate.
21 let done = false;
55 *
56 * @param predicate The filter predicate.
57 */
58 filter(predicate: (value: T) => boolean): Observable<T>;
22
59
60 /** Applies accumulator to each value in the sequence and
61 * emits the accumulated value for each source element
62 *
63 * @param accumulator
64 * @param initial
65 */
66 scan<A>(accumulator: (acc: A, value: T) => A, initial: A): Observable<A>;
67 }
68
69 const noop = () => { };
70
71 const sink = <T>(consumer: Partial<Observer<T>>) => {
72 const { next, error, complete } = consumer;
23 return {
73 return {
24 next: (value: T) => !done && next(value),
74 next: next ? next.bind(consumer) : noop,
25 error: (e: unknown) => !done && (done = true, error(e)),
75 error: error ? error.bind(consumer) : noop,
26 complete: () => !done && (done = true, complete())
76 complete: complete ? complete.bind(consumer) : noop
27 };
77 }
78 };
79
80 const fuse = <T>({ next, error, complete }: Sink<T>) => {
81 let done = false;
82 return {
83 next: (value: T) => { !done && next(value) },
84 error: (e: unknown) => { !done && (done = true, error(e)) },
85 complete: () => { !done && (done = true, complete()) }
86 }
28 }
87 }
29
88
30 export const observe = <T>(producer: Producer<T>) : Observable<T> => ({
89 const _observe = <T>(producer: Producer<T>): Observable<T> => ({
31 on: (consumer: Consumer<T>) => ({
90 subscribe: (consumer: Partial<Observer<T>>) => ({
32 destroy: producer(sink(consumer)) ?? noop
91 unsubscribe: producer(sink(consumer)) ?? noop
92 }),
93 map: (mapper) => _observe(({ next, error, complete }) =>
94 producer({
95 next: next !== noop ? (v: T) => next(mapper(v)) : noop,
96 error,
97 complete
98 })
99 ),
100 filter: (predicate) => _observe(({ next, error, complete }) =>
101 producer({
102 next: next !== noop ?
103 (v: T) => predicate(v) ? next(v) : void(0) : noop,
104 error,
105 complete
106 })
107 ),
108 scan: (accumulator, initial) => _observe(({ next, error, complete }) => {
109 let _acc = initial;
110 return producer({
111 next: next !== noop ?
112 (v: T) => next(_acc = accumulator(_acc, v)) : noop,
113 error,
114 complete
115 });
33 })
116 })
34 });
117 });
118
119 export const observe = <T>(producer: Producer<T>): Observable<T> => ({
120 subscribe: (consumer: Partial<Observer<T>>) => ({
121 unsubscribe: producer(fuse(sink(consumer))) ?? noop
122 }),
123 map: (mapper) => _observe(({ next, error, complete }) =>
124 producer(fuse({
125 next: next !== noop ?
126 (v: T) => next(mapper(v)) : noop,
127 error,
128 complete
129 }))
130 ),
131 filter: (predicate) => _observe(({ next, error, complete }) =>
132 producer(fuse({
133 next: next !== noop ?
134 (v: T) => predicate(v) ? next(v) : void (0) : noop,
135 error,
136 complete
137 }))
138 ),
139 scan: (accumulator, initial?) => observe(({ next, error, complete }) => {
140 let _acc = initial;
141 return producer(fuse({
142 next: next !== noop ? (v: T) => next(_acc = accumulator(_acc, v)) : noop,
143 error,
144 complete
145 }));
146 })
147 });
@@ -1,116 +1,176
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 { isElementNode, isWidget, 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 { Observable, observe, Subscribable } from "./observable";
11 import djAttr = require("dojo/dom-attr");
12 import djClass = require("dojo/dom-class");
11
13
12 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
14 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
13 if (typeof elementType === "string") {
15 if (typeof elementType === "string") {
14 const ctx = new HtmlRendition(elementType);
16 const ctx = new HtmlRendition(elementType);
15 if (args)
17 if (args)
16 args.forEach(x => ctx.visitNext(x));
18 args.forEach(x => ctx.visitNext(x));
17
19
18 return ctx;
20 return ctx;
19 } else if (isWidgetConstructor(elementType)) {
21 } else if (isWidgetConstructor(elementType)) {
20 const ctx = new WidgetRendition(elementType);
22 const ctx = new WidgetRendition(elementType);
21 if (args)
23 if (args)
22 args.forEach(x => ctx.visitNext(x));
24 args.forEach(x => ctx.visitNext(x));
23
25
24 return ctx;
26 return ctx;
25 } else if (typeof elementType === "function") {
27 } else if (typeof elementType === "function") {
26 const ctx = new FunctionRendition(elementType as (props: any) => Element);
28 const ctx = new FunctionRendition(elementType as (props: any) => Element);
27 if (args)
29 if (args)
28 args.forEach(x => ctx.visitNext(x));
30 args.forEach(x => ctx.visitNext(x));
29
31
30 return ctx;
32 return ctx;
31 } else {
33 } else {
32 throw new Error(`The element type '${elementType}' is unsupported`);
34 throw new Error(`The element type '${elementType}' is unsupported`);
33 }
35 }
34 }
36 }
35
37
36 export interface EventDetails<T = any> {
38 export interface EventDetails<T = any> {
37 detail: T;
39 detail: T;
38 }
40 }
39
41
40 export interface EventSelector {
42 export interface EventSelector {
41 selectorTarget: HTMLElement;
43 selectorTarget: HTMLElement;
42 target: HTMLElement;
44 target: HTMLElement;
43 }
45 }
44
46
45 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
47 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
46
48
47 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
49 type StatefulProps<T> = T extends Stateful<infer A> ? A :
50 T extends _WidgetBase ? T : never;
48
51
49
52
50 /**
53 /**
51 * Observers the property and calls render callback each change.
54 * Observers the property and calls render callback each change.
52 *
55 *
53 * @param target The target object which property will be observed.
56 * @param target The target object which property will be observed.
54 * @param prop The name of the property.
57 * @param prop The name of the property.
55 * @param render The callback which will be called every time the value is changed
58 * @param render The callback which will be called every time the value is changed
56 * @returns Rendition which is created instantly
59 * @returns Rendition which is created instantly
57 */
60 */
58 export function watch<W extends _WidgetBase, K extends keyof W>(
61 export function watch<W extends _WidgetBase, K extends keyof W>(
59 target: W,
62 target: W,
60 prop: K,
63 prop: K,
61 render: (model: W[K]) => any
64 render: (model: W[K]) => any
62 ): Rendition;
65 ): Rendition;
63 /**
66 /**
64 * Observers the property and calls render callback each change.
67 * Observers the property and calls render callback each change.
65 *
68 *
66 * @param target The target object which property will be observed.
69 * @param target The target object which property will be observed.
67 * @param prop The name of the property.
70 * @param prop The name of the property.
68 * @param render The callback which will be called every time the value is changed
71 * @param render The callback which will be called every time the value is changed
69 * @returns Rendition which is created instantly
72 * @returns Rendition which is created instantly
70 */
73 */
71 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
74 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
72 target: T,
75 target: T,
73 prop: K,
76 prop: K,
74 render: (model: StatefulProps<T>[K]) => any
77 render: (model: StatefulProps<T>[K]) => any
75 ): Rendition;
78 ): Rendition;
76 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
79 export function watch<V>(subj: Subscribable<V>, render: (model: V) => unknown): Rendition;
77 target: T,
80 export function watch(
78 prop: K,
81 ...args: [Stateful, string, (model: unknown) => unknown] |
79 render: (model: StatefulProps<T>[K]) => any
82 [Subscribable<unknown>, (model: unknown) => unknown]
80 ) {
83 ) {
84 if (args.length === 3) {
85 const [target, prop, render] = args;
86 return new WatchRendition(
87 render,
88 observe(({next}) => {
89 const h = target.watch<any>(
90 prop,
91 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
92 );
93 next(target.get(prop));
94 return () => h.remove();
95 })
96 );
97 } else {
98 const [subj, render] = args;
99 return new WatchRendition(render, subj);
100 }
101 }
102
103 export const prop: {
104 <T extends Stateful, K extends string & keyof StatefulProps<T>>(target: T, name: K): Observable<StatefulProps<T>[K]>;
105 <T extends _WidgetBase, K extends keyof T>(target: T, name: K): Observable<T[K]>;
106 } = (target: Stateful, name: string) => {
107 return observe(({next}) => {
108 const h = target.watch(
109 name,
110 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
111 );
112 next(target.get(name));
113 return () => h.remove();
114 })
115 };
81
116
82 return new WatchRendition(
117 export const attach = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
83 render,
118
84 observe(({next}) => {
119 export const bind = <K extends string, T>(attr: K, subj: Subscribable<T>) => {
85 const h = target.watch(
120 let h = { unsubscribe() { } };
86 prop,
121
87 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
122 return <E extends (HTMLElement & { [p in K]: T }) | { set(name: K, value: T): void; }>(el: E | undefined) => {
88 );
123 if (el) {
89 next(target.get(prop));
124 if (isElementNode(el)) {
90 return () => h.remove();
125 h = subj.subscribe({
91 })
126 next: value => djAttr.set(el, attr, value)
92 )
127 });
128 } else {
129 h = subj.subscribe({
130 next: value => el.set(attr, value)
131 });
132 }
133 } else {
134 h.unsubscribe();
135 }
136 }
137 };
138
139 export const toggleClass = (className: string, subj: Subscribable<boolean>) => {
140 let h = { unsubscribe() { } };
141 return (elOrWidget: HTMLElement | _WidgetBase | undefined) => {
142 const el = isWidget(elOrWidget) ? elOrWidget.domNode : elOrWidget;
143 if (el) {
144 h = subj.subscribe({
145 next: v => djClass.toggle(el, className, v)
146 });
147 } else {
148 h.unsubscribe();
149 }
150 }
93 }
151 }
94
152
153 export const all = <T, A extends JSX.Ref<T>[]>(...cbs: A): JSX.Ref<T> => (arg: T | undefined) => cbs.forEach(cb => cb(arg));
154
95 /** Decorates the method which will be registered as the handle for the specified event.
155 /** Decorates the method which will be registered as the handle for the specified event.
96 * This decorator can be applied to DjxWidgetBase subclass methods.
156 * This decorator can be applied to DjxWidgetBase subclass methods.
97 *
157 *
98 * ```
158 * ```
99 * @on("click")
159 * @on("click")
100 * _onClick(eventObj: MouseEvent) {
160 * _onClick(eventObj: MouseEvent) {
101 * // ...
161 * // ...
102 * }
162 * }
103 * ```
163 * ```
104 */
164 */
105 export const on = <E extends string>(...eventNames: E[]) =>
165 export const on = <E extends string>(...eventNames: E[]) =>
106 <K extends string,
166 <K extends string,
107 T extends DjxWidgetBase<any, { [p in E]: EV }>,
167 T extends DjxWidgetBase<any, { [p in E]: EV }>,
108 EV extends Event
168 EV extends Event
109 >(
169 >(
110 target: T,
170 target: T,
111 key: K,
171 key: K,
112 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
172 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
113 ): any => {
173 ): any => {
114 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
174 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
115 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
175 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
116 };
176 };
@@ -1,114 +1,128
1 import { djbase, djclass } from "../declare";
1 import { djbase, djclass } from "../declare";
2 import _WidgetBase = require("dijit/_WidgetBase");
2 import _WidgetBase = require("dijit/_WidgetBase");
3 import _AttachMixin = require("dijit/_AttachMixin");
3 import _AttachMixin = require("dijit/_AttachMixin");
4 import { Rendition, isNode } from "./traits";
4 import { Rendition, isNode, isElementNode } from "./traits";
5 import registry = require("dijit/registry");
5 import registry = require("dijit/registry");
6 import on = require("dojo/on");
6 import on = require("dojo/on");
7 import { Scope } from "./Scope";
8 import { render } from "./render";
7
9
8 // type Handle = dojo.Handle;
10 // type Handle = dojo.Handle;
9
11
10 export interface EventArgs {
12 export interface EventArgs {
11 bubbles?: boolean;
13 bubbles?: boolean;
12
14
13 cancelable?: boolean;
15 cancelable?: boolean;
14
16
15 composed?: boolean;
17 composed?: boolean;
16 }
18 }
17
19
18 export interface DjxWidgetBase<Attrs = {}, Events extends { [name in keyof Events]: Event } = {}> extends
20 export interface DjxWidgetBase<Attrs = {}, Events extends { [name in keyof Events]: Event } = {}> extends
19 _WidgetBase<Events> {
21 _WidgetBase<Events> {
20
22
21 /** This property is declared only for type inference to work, it is never assigned
23 /** This property is declared only for type inference to work, it is never assigned
22 * and should not be used.
24 * and should not be used.
23 */
25 */
24 readonly _eventMap: Events & GlobalEventHandlersEventMap;
26 readonly _eventMap: Events & GlobalEventHandlersEventMap;
25
27
26 /** The list of pairs of event and method names. When the widget is created all methods from
28 /** The list of pairs of event and method names. When the widget is created all methods from
27 * this list will be connected to corresponding events.
29 * this list will be connected to corresponding events.
28 *
30 *
29 * This property is maintained in the prototype
31 * This property is maintained in the prototype
30 */
32 */
31 _eventHandlers: Array<{
33 _eventHandlers: Array<{
32 eventName: string,
34 eventName: string,
33 handlerMethod: keyof any;
35 handlerMethod: keyof any;
34 }>;
36 }>;
35 }
37 }
36
38
37 type _super = {
39 type _super = {
38 startup(): void;
40 startup(): void;
41
42 destroy(preserveDom?: boolean): void;
39 };
43 };
40
44
41 @djclass
45 @djclass
42 export abstract class DjxWidgetBase<Attrs = {}, Events = {}> extends djbase<_super, _AttachMixin>(_WidgetBase, _AttachMixin) {
46 export abstract class DjxWidgetBase<Attrs = {}, Events = {}> extends djbase<_super, _AttachMixin>(_WidgetBase, _AttachMixin) {
47 private readonly _scope = new Scope();
43
48
44 buildRendering() {
49 buildRendering() {
45 this.domNode = this.render().getDomNode();
50 const node = render(this.render(), this._scope);
51 if (!isElementNode(node))
52 throw new Error("The render method must return a single DOM element");
53 this.domNode = node as HTMLElement;
54
46 super.buildRendering();
55 super.buildRendering();
47
56
48 // now we should get assigned data-dojo-attach-points
57 // now we should get assigned data-dojo-attach-points
49 // place the contents of the original srcNode to the containerNode
58 // place the contents of the original srcNode to the containerNode
50 const src = this.srcNodeRef;
59 const src = this.srcNodeRef;
51 const dest = this.containerNode;
60 const dest = this.containerNode;
52
61
53 // the donNode is constructed now we need to connect event handlers
62 // the donNode is constructed now we need to connect event handlers
54 this._connectEventHandlers();
63 this._connectEventHandlers();
55
64
56 if (src && dest) {
65 if (src && dest) {
57 while (src.firstChild)
66 while (src.firstChild)
58 dest.appendChild(src.firstChild);
67 dest.appendChild(src.firstChild);
59 }
68 }
60 }
69 }
61
70
62 abstract render(): Rendition<HTMLElement>;
71 abstract render(): Rendition<HTMLElement>;
63
72
64 private _connectEventHandlers() {
73 private _connectEventHandlers() {
65 if (this._eventHandlers)
74 if (this._eventHandlers)
66 this._eventHandlers.forEach(({ eventName, handlerMethod }) => {
75 this._eventHandlers.forEach(({ eventName, handlerMethod }) => {
67 const handler = this[handlerMethod as keyof this];
76 const handler = this[handlerMethod as keyof this];
68 if (typeof handler === "function")
77 if (typeof handler === "function")
69 on(this.domNode, eventName, handler.bind(this));
78 on(this.domNode, eventName, handler.bind(this));
70 });
79 });
71 }
80 }
72
81
73 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
82 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
74 baseNode: T,
83 baseNode: T,
75 getAttrFunc: (baseNode: T, attr: string) => any,
84 getAttrFunc: (baseNode: T, attr: string) => any,
76 // tslint:disable-next-line: ban-types
85 // tslint:disable-next-line: ban-types
77 attachFunc: (node: T, type: string, func?: Function) => dojo.Handle
86 attachFunc: (node: T, type: string, func?: Function) => dojo.Handle
78 ): boolean {
87 ): boolean {
79 if (isNode(baseNode)) {
88 if (isNode(baseNode)) {
80 const w = registry.byNode(baseNode);
89 const w = registry.byNode(baseNode);
81 if (w) {
90 if (w) {
82 // from dijit/_WidgetsInTemplateMixin
91 // from dijit/_WidgetsInTemplateMixin
83 this._processTemplateNode(w,
92 this._processTemplateNode(w,
84 (n, p) => n.get(p as any), // callback to get a property of a widget
93 (n, p) => n.get(p as any), // callback to get a property of a widget
85 (widget, type, callback) => {
94 (widget, type, callback) => {
86 if (!callback)
95 if (!callback)
87 throw new Error("The callback must be specified");
96 throw new Error("The callback must be specified");
88
97
89 // callback to do data-dojo-attach-event to a widget
98 // callback to do data-dojo-attach-event to a widget
90 if (type in widget) {
99 if (type in widget) {
91 // back-compat, remove for 2.0
100 // back-compat, remove for 2.0
92 return widget.connect(widget, type, callback as EventListener);
101 return widget.connect(widget, type, callback as EventListener);
93 } else {
102 } else {
94 // 1.x may never hit this branch, but it's the default for 2.0
103 // 1.x may never hit this branch, but it's the default for 2.0
95 return widget.on(type, callback);
104 return widget.on(type, callback);
96 }
105 }
97
106
98 });
107 });
99 // don't process widgets internals
108 // don't process widgets internals
100 return false;
109 return false;
101 }
110 }
102 }
111 }
103 return super._processTemplateNode(baseNode, getAttrFunc, attachFunc);
112 return super._processTemplateNode(baseNode, getAttrFunc, attachFunc);
104 }
113 }
105
114
106 /** Starts current widget and all its supporting widgets (placed outside
115 /** Starts current widget and all its supporting widgets (placed outside
107 * `containerNode`) and child widgets (placed inside `containerNode`)
116 * `containerNode`) and child widgets (placed inside `containerNode`)
108 */
117 */
109 startup() {
118 startup() {
110 // startup supporting widgets
119 // startup supporting widgets
111 registry.findWidgets(this.domNode, this.containerNode).forEach(w => w.startup());
120 registry.findWidgets(this.domNode, this.containerNode).forEach(w => w.startup());
112 super.startup();
121 super.startup();
113 }
122 }
123
124 destroy(preserveDom?: boolean) {
125 this._scope.destroy();
126 super.destroy(preserveDom);
127 }
114 }
128 }
@@ -1,51 +1,50
1 import djDom = require("dojo/dom-construct");
1 import djDom = require("dojo/dom-construct");
2 import djAttr = require("dojo/dom-attr");
2 import djAttr = require("dojo/dom-attr");
3 import { argumentNotEmptyString } from "@implab/core-amd/safe";
3 import { argumentNotEmptyString } from "@implab/core-amd/safe";
4 import { RenditionBase } from "./RenditionBase";
4 import { RenditionBase } from "./RenditionBase";
5 import { placeAt } from "./traits";
5 import { placeAt } from "./traits";
6 import { IScope } from "./Scope";
6 import { getItemDom, refHook } from "./render";
7 import { getItemDom, renderHook } from "./render";
8
7
9 export class HtmlRendition extends RenditionBase<Element> {
8 export class HtmlRendition extends RenditionBase<Element> {
10 elementType: string;
9 elementType: string;
11
10
12 _element: Element | undefined;
11 _element: Element | undefined;
13
12
14 constructor(elementType: string) {
13 constructor(elementType: string) {
15 argumentNotEmptyString(elementType, "elementType");
14 argumentNotEmptyString(elementType, "elementType");
16 super();
15 super();
17
16
18 this.elementType = elementType;
17 this.elementType = elementType;
19 }
18 }
20
19
21 _addChild(child: unknown, scope: IScope): void {
20 _addChild(child: unknown): void {
22 if (!this._element)
21 if (!this._element)
23 throw new Error("The HTML element isn't created");
22 throw new Error("The HTML element isn't created");
24 placeAt(getItemDom(child), this._element);
23 placeAt(getItemDom(child), this._element);
25 }
24 }
26
25
27 _create({ xmlns, ref, ...attrs }: { xmlns?: string, ref?: JSX.Ref<Element> }, children: unknown[], scope: IScope) {
26 _create({ xmlns, ref, ...attrs }: { xmlns?: string, ref?: JSX.Ref<Element> }, children: unknown[]) {
28
27
29 if (xmlns) {
28 if (xmlns) {
30 this._element = document.createElementNS(xmlns, this.elementType);
29 this._element = document.createElementNS(xmlns, this.elementType);
31 djAttr.set(this._element, attrs);
30 djAttr.set(this._element, attrs);
32 } else {
31 } else {
33 this._element = djDom.create(this.elementType, attrs);
32 this._element = djDom.create(this.elementType, attrs);
34 }
33 }
35
34
36 children.forEach(v => this._addChild(v, scope));
35 children.forEach(v => this._addChild(v));
37
36
38 const element = this._element;
37 const element = this._element;
39
38
40 if (ref)
39 if (ref)
41 renderHook(() => ref(element));
40 refHook(element, ref);
42 }
41 }
43
42
44 _getDomNode() {
43 _getDomNode() {
45 if (!this._element)
44 if (!this._element)
46 throw new Error("The HTML element isn't created");
45 throw new Error("The HTML element isn't created");
47
46
48 return this._element;
47 return this._element;
49 }
48 }
50
49
51 }
50 }
@@ -1,73 +1,70
1 import { isNull, mixin } from "@implab/core-amd/safe";
1 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, isMounted, startupWidgets } from "./traits";
2 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, autostartWidgets } from "./traits";
3
4 import { IScope } from "./Scope";
5 import { getScope } from "./render";
6
2
7 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
3 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
8 private _attrs = {};
4 private _attrs = {};
9
5
10 private _children = new Array();
6 private _children = new Array();
11
7
12 private _created: boolean = false;
8 private _created: boolean = false;
13
9
14 visitNext(v: any) {
10 visitNext(v: any) {
15 if (this._created)
11 if (this._created)
16 throw new Error("The Element is already created");
12 throw new Error("The Element is already created");
17
13
18 if (isNull(v) || typeof v === "boolean")
14 if (v === null || v === undefined || typeof v === "boolean")
19 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
15 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
20 return;
16 return;
21
17
22 if (isPlainObject(v)) {
18 if (isPlainObject(v)) {
23 mixin(this._attrs, v);
19 this._attrs = {... this._attrs, ...v};
24 } else if (v instanceof Array) {
20 } else if (v instanceof Array) {
25 v.forEach(x => this.visitNext(x));
21 v.forEach(x => this.visitNext(x));
26 } else {
22 } else {
27 this._children.push(v);
23 this._children.push(v);
28 }
24 }
29 }
25 }
30
26
31 ensureCreated(scope: IScope) {
27 ensureCreated() {
32 if (!this._created) {
28 if (!this._created) {
33 this._create(this._attrs, this._children, scope);
29 this._create(this._attrs, this._children);
34 this._children = [];
30 this._children = [];
35 this._attrs = {};
31 this._attrs = {};
36 this._created = true;
32 this._created = true;
37 }
33 }
38 }
34 }
39
35
40 /** Is rendition was instantiated to the DOM node */
36 /** Is rendition was instantiated to the DOM node */
41 isCreated() {
37 isCreated() {
42 return this._created;
38 return this._created;
43 }
39 }
44
40
45 /** Creates DOM node if not created. No additional actions are taken. */
41 /** Creates DOM node if not created. No additional actions are taken. */
46 getDomNode(scope?: IScope) {
42 getDomNode() {
47 this.ensureCreated(scope || getScope());
43 this.ensureCreated();
48 return this._getDomNode();
44 return this._getDomNode();
49 }
45 }
50
46
51 /** Creates DOM node if not created, places it to the specified position
47 /** Creates DOM node if not created, places it to the specified position
52 * and calls startup() method for all widgets contained by this node.
48 * and calls startup() method for all widgets contained by this node.
53 *
49 *
54 * @param {string | Node} refNode The reference node where the created
50 * @param {string | Node} refNode The reference node where the created
55 * DOM should be placed.
51 * DOM should be placed.
56 * @param {DojoNodePosition} position Optional parameter, specifies the
52 * @param {DojoNodePosition} position Optional parameter, specifies the
57 * position relative to refNode. Default is "last" (i.e. last child).
53 * position relative to refNode. Default is "last" (i.e. last child).
58 */
54 */
59 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
55 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
60 const domNode = this.getDomNode();
56 const domNode = this.getDomNode();
61
57
62 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode];
58 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.childNodes) : [domNode];
63
59
64 placeAt(domNode, refNode, position);
60 placeAt(domNode, refNode, position);
65
61
66 startupPending.forEach(autostartWidgets);
62 if (isMounted(startupPending[0]))
63 startupPending.forEach(n => startupWidgets(n));
67
64
68 }
65 }
69
66
70 protected abstract _create(attrs: object, children: unknown[], scope: IScope): void;
67 protected abstract _create(attrs: object, children: unknown[]): void;
71
68
72 protected abstract _getDomNode(): TNode;
69 protected abstract _getDomNode(): TNode;
73 }
70 }
@@ -1,40 +1,43
1 import { IDestroyable, IRemovable } from "@implab/core-amd/interfaces";
1 import { IDestroyable, IRemovable } from "@implab/core-amd/interfaces";
2 import { isDestroyable, isRemovable } from "@implab/core-amd/safe";
2 import { isDestroyable, isRemovable } from "@implab/core-amd/safe";
3 import { isUnsubsribable, Unsubscribable } from "../observable";
3
4
4 export interface IScope {
5 export interface IScope {
5 own(target: (() => void) | IDestroyable | IRemovable): void;
6 own(target: (() => void) | IDestroyable | IRemovable | Unsubscribable): void;
6 }
7 }
7
8
8 export class Scope implements IDestroyable, IScope {
9 export class Scope implements IDestroyable, IScope {
9 private readonly _cleanup: (() => void)[] = [];
10 private readonly _cleanup: (() => void)[] = [];
10
11
11 static readonly dummy: IScope = { own() { } };
12 static readonly dummy: IScope = { own() { } };
12
13
13 own(target: (() => void) | IDestroyable | IRemovable) {
14 own(target: (() => void) | IDestroyable | IRemovable | Unsubscribable) {
14 if (target instanceof Function) {
15 if (target instanceof Function) {
15 this._cleanup.push(target);
16 this._cleanup.push(target);
16 } else if (isDestroyable(target)) {
17 } else if (isDestroyable(target)) {
17 this._cleanup.push(() => target.destroy());
18 this._cleanup.push(() => target.destroy());
18 } else if (isRemovable(target)) {
19 } else if (isRemovable(target)) {
19 this._cleanup.push(() => target.remove());
20 this._cleanup.push(() => target.remove());
21 } else if (isUnsubsribable(target)) {
22 this._cleanup.push(() => target.unsubscribe());
20 }
23 }
21 }
24 }
22
25
23 clean() {
26 clean() {
24 const guard = (cb: () => void) => {
27 const guard = (cb: () => void) => {
25 try {
28 try {
26 cb();
29 cb();
27 } catch {
30 } catch {
28 // guard
31 // guard
29 }
32 }
30 }
33 }
31
34
32 this._cleanup.forEach(guard);
35 this._cleanup.forEach(guard);
33 this._cleanup.length = 0;
36 this._cleanup.length = 0;
34 }
37 }
35
38
36 destroy() {
39 destroy() {
37 this.clean();
40 this.clean();
38 }
41 }
39
42
40 } No newline at end of file
43 }
@@ -1,55 +1,97
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 { getScope, render } from "./render";
4 import { getScope, render } from "./render";
5 import { RenditionBase } from "./RenditionBase";
5 import { RenditionBase } from "./RenditionBase";
6 import { Scope } from "./Scope";
6 import { Scope } from "./Scope";
7 import { Observable } from "../observable";
7 import { Subscribable } from "../observable";
8 import { destroy } from "./traits";
8 import { Cancellation } from "@implab/core-amd/Cancellation";
9 import { collectNodes, destroy, isDocumentFragmentNode, isMounted, placeAt, startupWidgets } from "./traits";
9
10
10 const trace = TraceSource.get(mid);
11 const trace = TraceSource.get(mid);
11
12
12 export class WatchRendition<T> extends RenditionBase<Node> {
13 export class WatchRendition<T> extends RenditionBase<Node> {
13 private readonly _component: (arg: T) => unknown;
14 private readonly _component: (arg: T) => unknown;
14
15
15 private _node: Node;
16 private readonly _node: Node;
16
17
17 private readonly _scope = new Scope();
18 private readonly _scope = new Scope();
18
19
19 private readonly _subject: Observable<T>;
20 private readonly _subject: Subscribable<T>;
21
22 private _renderJob?: { value: T };
20
23
21 constructor(component: (arg: T) => unknown, subject: Observable<T>) {
24 private _ct = Cancellation.none;
25
26 constructor(component: (arg: T) => unknown, subject: Subscribable<T>) {
22 super();
27 super();
23 argumentNotNull(component, "component");
28 argumentNotNull(component, "component");
24
29
25 this._component = component;
30 this._component = component;
26
31
27 this._subject = subject;
32 this._subject = subject;
28
33
29 this._node = document.createComment("WatchRendition placeholder");
34 this._node = document.createComment("[Watch]");
30 }
35 }
31
36
32 protected _create(attrs: object, children: any[]) {
37 protected _create() {
33 const scope = getScope();
38 const scope = getScope();
34 scope.own(this._scope);
39 scope.own(() => {
35 scope.own(this._subject.on({ next: this._onValue }));
40 this._scope.destroy();
41 destroy(this._node);
42 });
43 scope.own(this._subject.subscribe({ next: this._onValue }));
44 this._ct = new Cancellation(cancel => scope.own(cancel));
45 }
46
47 private _onValue = (value: T) => {
48 if (!this._renderJob) {
49 // schedule a new job
50 this._renderJob = { value };
51 this._render().catch(e => trace.error(e));
52 } else {
53 // update existing job
54 this._renderJob = { value };
55 }
36 }
56 }
37
57
38 private _onValue = (value: T) =>
58 private async _render() {
39 void this._render(value).catch( e => trace.error(e));
59 // fork
60 await Promise.resolve();
61 // don't render destroyed rendition
62 if (this._ct.isRequested())
63 return;
64
65 // remove all previous content
66 this._scope.clean();
40
67
41 private async _render(value: T) {
68 // render the new node
42 this._scope.clean();
69 const node = render(
43 const [refNode, ...rest] = await render(this._component(value), this._node, "replace", this._scope);
70 this._renderJob ? this._component(this._renderJob.value) : undefined,
44 this._node = refNode;
71 this._scope
45 this._scope.own(() => rest.forEach(destroy));
72 );
73
74 // get actual content
75 const pending = isDocumentFragmentNode(node) ?
76 collectNodes(node.childNodes) :
77 [node];
78
79 placeAt(node, this._node, "after");
80
81 if (isMounted(this._node))
82 pending.forEach(n => startupWidgets(n));
83
84 if (pending.length)
85 this._scope.own(() => pending.forEach(destroy));
86
87 this._renderJob = undefined;
46 }
88 }
47
89
48 protected _getDomNode() {
90 protected _getDomNode() {
49 if (!this._node)
91 if (!this._node)
50 throw new Error("The instance of the widget isn't created");
92 throw new Error("The instance of the widget isn't created");
51 return this._node;
93 return this._node;
52 }
94 }
53
95
54
96
55 }
97 }
@@ -1,134 +1,130
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 { getItemDom, refHook } from "./render";
7 import { getItemDom, getScope, renderHook } from "./render";
8
7
9 // tslint:disable-next-line: class-name
8 // tslint:disable-next-line: class-name
10 export interface _Widget {
9 export interface _Widget {
11 domNode: Node;
10 domNode: Node;
12
11
13 containerNode?: Node;
12 containerNode?: Node;
14
13
15 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
14 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
16 startup?(): void;
15 startup?(): void;
17
16
18 addChild?(widget: unknown, index?: number): void;
17 addChild?(widget: unknown, index?: number): void;
19 }
18 }
20
19
21 export type _WidgetCtor = new (attrs: {}, srcNode?: string | Node) => _Widget;
20 export type _WidgetCtor = new (attrs: {}, srcNode?: string | Node) => _Widget;
22
21
23 export class WidgetRendition extends RenditionBase<Node> {
22 export class WidgetRendition extends RenditionBase<Node> {
24 readonly widgetClass: _WidgetCtor;
23 readonly widgetClass: _WidgetCtor;
25
24
26 _instance: _Widget | undefined;
25 _instance: _Widget | undefined;
27
26
28 constructor(widgetClass: _WidgetCtor) {
27 constructor(widgetClass: _WidgetCtor) {
29 super();
28 super();
30 argumentNotNull(widgetClass, "widgetClass");
29 argumentNotNull(widgetClass, "widgetClass");
31
30
32 this.widgetClass = widgetClass;
31 this.widgetClass = widgetClass;
33 }
32 }
34
33
35 _addChild(child: unknown, scope: IScope): void {
34 _addChild(child: unknown): void {
36 const instance = this._getInstance();
35 const instance = this._getInstance();
37
36
38 if (instance.addChild) {
37 if (instance.addChild) {
39 if (child instanceof WidgetRendition) {
38 if (child instanceof WidgetRendition) {
40 // layout containers add custom logic to addChild methods
39 // layout containers add custom logic to addChild methods
41 instance.addChild(child.getWidgetInstance(scope));
40 instance.addChild(child.getWidgetInstance());
42 } else if (isWidget(child)) {
41 } else if (isWidget(child)) {
43 instance.addChild(child);
42 instance.addChild(child);
44 } else {
43 } else {
45 const childDom = getItemDom(child);
44 const childDom = getItemDom(child);
46 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
45 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
47
46
48 if (w) {
47 if (w) {
49 instance.addChild(w);
48 instance.addChild(w);
50 } else {
49 } else {
51 if (!instance.containerNode)
50 if (!instance.containerNode)
52 throw new Error("Failed to add DOM content. The widget doesn't have a containerNode");
51 throw new Error("Failed to add DOM content. The widget doesn't have a containerNode");
53
52
54 // the current widget isn't started, it's children shouldn't start too
53 // the current widget isn't started, it's children shouldn't start too
55 placeAt(getItemDom(child), instance.containerNode, "last");
54 placeAt(getItemDom(child), instance.containerNode, "last");
56 }
55 }
57 }
56 }
58 } else {
57 } else {
59 if (!instance.containerNode)
58 if (!instance.containerNode)
60 throw new Error("The widget doesn't have neither addChild nor containerNode");
59 throw new Error("The widget doesn't have neither addChild nor containerNode");
61
60
62 // the current widget isn't started, it's children shouldn't start too
61 // the current widget isn't started, it's children shouldn't start too
63 placeAt(getItemDom(child), instance.containerNode, "last");
62 placeAt(getItemDom(child), instance.containerNode, "last");
64 }
63 }
65 }
64 }
66
65
67 protected _create({ref, ...attrs}: {ref?: JSX.Ref<_Widget>}, children: unknown[], scope: IScope) {
66 protected _create({ref, ...attrs}: {ref?: JSX.Ref<_Widget>}, children: unknown[]) {
68 if (this.widgetClass.prototype instanceof ContentPane) {
67 if (this.widgetClass.prototype instanceof ContentPane) {
69 // a special case for the ContentPane this is for
68 // a special case for the ContentPane this is for
70 // compatibility with that heavy widget, all
69 // compatibility with that heavy widget, all
71 // regular containers could be easily manipulated
70 // regular containers could be easily manipulated
72 // through `containerNode` property or `addChild` method.
71 // through `containerNode` property or `addChild` method.
73
72
74 // render children to the DocumentFragment
73 // render children to the DocumentFragment
75 const content = document.createDocumentFragment();
74 const content = document.createDocumentFragment();
76 children.forEach(child => content.appendChild(getItemDom(child)));
75 children.forEach(child => content.appendChild(getItemDom(child)));
77
76
78 // set the content property to the parameters of the widget
77 // set the content property to the parameters of the widget
79 const _attrs = { ...attrs, content };
78 const _attrs = { ...attrs, content };
80 this._instance = new this.widgetClass(_attrs);
79 this._instance = new this.widgetClass(_attrs);
81 } else {
80 } else {
82 this._instance = new this.widgetClass(attrs);
81 this._instance = new this.widgetClass(attrs);
83 children.forEach(x => this._addChild(x, scope));
82 children.forEach(x => this._addChild(x));
84 }
83 }
85
84
86 if (ref) {
85 if (ref)
87 const instance = this._instance;
86 refHook(this._instance, ref);
88 renderHook(() => ref(instance));
89 }
90
91 }
87 }
92
88
93 private _getInstance() {
89 private _getInstance() {
94 if (!this._instance)
90 if (!this._instance)
95 throw new Error("The instance of the widget isn't created");
91 throw new Error("The instance of the widget isn't created");
96 return this._instance;
92 return this._instance;
97 }
93 }
98
94
99 protected _getDomNode() {
95 protected _getDomNode() {
100 if (!this._instance)
96 if (!this._instance)
101 throw new Error("The instance of the widget isn't created");
97 throw new Error("The instance of the widget isn't created");
102 return this._instance.domNode;
98 return this._instance.domNode;
103 }
99 }
104
100
105 /** Overrides default placeAt implementation. Calls placeAt of the
101 /** Overrides default placeAt implementation. Calls placeAt of the
106 * widget and then starts it.
102 * widget and then starts it.
107 *
103 *
108 * @param refNode A node or id of the node where the widget should be placed.
104 * @param refNode A node or id of the node where the widget should be placed.
109 * @param position A position relative to refNode.
105 * @param position A position relative to refNode.
110 */
106 */
111 placeAt(refNode: string | Node, position?: DojoNodePosition) {
107 placeAt(refNode: string | Node, position?: DojoNodePosition) {
112 this.ensureCreated(getScope());
108 this.ensureCreated();
113 const instance = this._getInstance();
109 const instance = this._getInstance();
114 if (typeof instance.placeAt === "function") {
110 if (typeof instance.placeAt === "function") {
115 instance.placeAt(refNode, position);
111 instance.placeAt(refNode, position);
116
112
117 // fix the dojo startup behavior when the widget is placed
113 // fix the dojo startup behavior when the widget is placed
118 // directly to the document and doesn't have any enclosing widgets
114 // directly to the document and doesn't have any enclosing widgets
119 const parentWidget = instance.domNode.parentNode ?
115 const parentWidget = instance.domNode.parentNode ?
120 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
116 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
121 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
117 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
122 instance.startup();
118 instance.startup();
123 } else {
119 } else {
124 // the widget doesn't have a placeAt method, strange but whatever
120 // the widget doesn't have a placeAt method, strange but whatever
125 super.placeAt(refNode, position);
121 super.placeAt(refNode, position);
126 }
122 }
127 }
123 }
128
124
129 getWidgetInstance(scope?: IScope) {
125 getWidgetInstance() {
130 this.ensureCreated(scope || getScope());
126 this.ensureCreated();
131 return this._getInstance();
127 return this._getInstance();
132 }
128 }
133
129
134 }
130 }
@@ -1,104 +1,112
1 import { TraceSource } from "@implab/core-amd/log/TraceSource";
1 import { TraceSource } from "@implab/core-amd/log/TraceSource";
2 import { isPromise } from "@implab/core-amd/safe";
2 import { isPromise } from "@implab/core-amd/safe";
3 import { id as mid } from "module";
3 import { id as mid } from "module";
4 import { Scope } from "./Scope";
4 import { IScope, Scope } from "./Scope";
5 import { autostartWidgets, collectNodes, DojoNodePosition, isDocumentFragmentNode, isNode, isRendition, isWidget, placeAt } from "./traits";
5 import { isNode, isRendition, isWidget } from "./traits";
6
6
7 const trace = TraceSource.get(mid);
7 const trace = TraceSource.get(mid);
8
8
9 let _scope = Scope.dummy;
9 interface Context {
10 scope: IScope;
10
11
11 let renderCount = 0;
12 hooks?: (() => void)[];
13 }
12
14
13 const hooks: (() => void)[] = [];
15 let _context: Context = {
16 scope: Scope.dummy
17 }
14
18
15 const guard = (cb: () => unknown) => {
19 const guard = (cb: () => unknown) => {
16 try {
20 try {
17 const result = cb()
21 const result = cb()
18 if (isPromise(result)) {
22 if (isPromise(result)) {
19 const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret);
23 const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret);
20 result.then(warn, warn);
24 result.then(warn, warn);
21 }
25 }
22 } catch (e) {
26 } catch (e) {
23 trace.error(e);
27 trace.error(e);
24 }
28 }
25 }
29 }
26
30
27 /**
31 export const beginRender = (scope: IScope = getScope()) => {
28 * Schedules rendering micro task
32 const prev = _context;
29 * @returns Promise
33 _context = {
30 */
34 scope,
31 const beginRender = () => {
35 hooks: []
32 renderCount++;
36 };
33 return Promise.resolve();
37 return endRender(prev);
34 }
38 }
35
39
36 /**
40 /**
37 * Completes render operation
41 * Completes render operation
38 */
42 */
39 const endRender = () => {
43 const endRender = (prev: Context) => () => {
40 if (!--renderCount) {
44 const { hooks } = _context;
45 if (hooks)
41 hooks.forEach(guard);
46 hooks.forEach(guard);
42 hooks.length = 0;
47
43 }
48 _context = prev;
44 }
49 }
45
50
46 export const renderHook = (hook: () => void) => {
51 export const renderHook = (hook: () => void) => {
47 if (renderCount)
52 const { hooks } = _context;
53 if (hooks)
48 hooks.push(hook);
54 hooks.push(hook);
49 else
55 else
50 guard(hook);
56 guard(hook);
51 }
57 }
52
58
59 export const refHook = <T>(value: T, ref: JSX.Ref<T>) => {
60 const { hooks, scope } = _context;
61 if (hooks)
62 hooks.push(() => ref(value));
63 else
64 guard(() => ref(value));
65
66 scope.own(() => ref(undefined));
67 }
68
53 /** Returns the current scope */
69 /** Returns the current scope */
54 export const getScope = () => _scope;
70 export const getScope = () => _context.scope;
55
71
56 /** Schedules the rendition to be rendered to the DOM Node
72 /** Schedules the rendition to be rendered to the DOM Node
57 * @param rendition The rendition to be rendered
73 * @param rendition The rendition to be rendered
58 * @param scope The scope
74 * @param scope The scope
59 */
75 */
60 export const render = async (rendition: unknown, refNode: Node, position: DojoNodePosition = "last", scope = Scope.dummy) => {
76 export const render = (rendition: unknown, scope = Scope.dummy) => {
61 await beginRender();
77 const complete = beginRender(scope);
62 const prev = _scope;
63 _scope = scope;
64 try {
78 try {
65 const domNode = getItemDom(rendition);
79 return getItemDom(rendition);
66 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode];
67 placeAt(domNode, refNode, position);
68 startupPending.forEach(autostartWidgets);
69
70 return startupPending;
71 } finally {
80 } finally {
72 _scope = prev;
81 complete();
73 endRender();
74 }
82 }
75 }
83 }
76
84
77 /** Renders DOM element for different types of the argument. */
85 /** Renders DOM element for different types of the argument. */
78 export const getItemDom = (v: unknown) => {
86 export const getItemDom = (v: unknown) => {
79 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
87 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
80 // primitive types converted to the text nodes
88 // primitive types converted to the text nodes
81 return document.createTextNode(v.toString());
89 return document.createTextNode(v.toString());
82 } else if (isNode(v)) {
90 } else if (isNode(v)) {
83 // nodes are kept as is
91 // nodes are kept as is
84 return v;
92 return v;
85 } else if (isRendition(v)) {
93 } else if (isRendition(v)) {
86 // renditions are instantiated
94 // renditions are instantiated
87 return v.getDomNode();
95 return v.getDomNode();
88 } else if (isWidget(v)) {
96 } else if (isWidget(v)) {
89 // widgets are converted to it's markup
97 // widgets are converted to it's markup
90 return v.domNode;
98 return v.domNode;
91 } else if (typeof v === "boolean" || v === null || v === undefined) {
99 } else if (typeof v === "boolean" || v === null || v === undefined) {
92 // null | undefined | boolean are removed, converted to comments
100 // null | undefined | boolean are removed
93 return document.createComment(`[${typeof v} ${String(v)}]`);
101 return document.createDocumentFragment();
94 } else if (v instanceof Array) {
102 } else if (v instanceof Array) {
95 // arrays will be translated to document fragments
103 // arrays will be translated to document fragments
96 const fragment = document.createDocumentFragment();
104 const fragment = document.createDocumentFragment();
97 v.map(item => getItemDom(item))
105 v.map(item => getItemDom(item))
98 .forEach(node => fragment.appendChild(node));
106 .forEach(node => fragment.appendChild(node));
99 return fragment;
107 return fragment;
100 } else {
108 } else {
101 // bug: explicit error otherwise
109 // bug: explicit error otherwise
102 throw new Error("Invalid parameter: " + v);
110 throw new Error("Invalid parameter: " + v);
103 }
111 }
104 }
112 }
@@ -1,214 +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
5
6 interface _WidgetBaseConstructor {
6 interface _WidgetBaseConstructor {
7 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;
8 prototype: _WidgetBase<any>;
8 prototype: _WidgetBase<any>;
9 }
9 }
10
10
11 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
11 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
12
12
13 export type DojoNodeLocation = [Node, DojoNodePosition];
13 export type DojoNodeLocation = [Node, DojoNodePosition];
14
14
15 export interface Rendition<TNode extends Node = Node> {
15 export interface Rendition<TNode extends Node = Node> {
16 getDomNode(): TNode;
16 getDomNode(): TNode;
17
17
18 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
18 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
19 }
19 }
20
20
21 /**
21 /**
22 * @deprecated use Rendition
22 * @deprecated use Rendition
23 */
23 */
24 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
24 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
25
25
26 export interface IRecursivelyDestroyable {
26 export interface IRecursivelyDestroyable {
27 destroyRecursive(): void;
27 destroyRecursive(): void;
28 }
28 }
29
29
30 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);
31
31
32 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;
33
33
34 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;
35
35
36 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;
37
37
38 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;
39
39
40 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;
41
41
42 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;
43
43
44 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;
45
45
46 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));
47
47
48 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");
49
49
50 /**
50 /**
51 * @deprecated use isRendition
51 * @deprecated use isRendition
52 */
52 */
53 export const isBuildContext = isRendition;
53 export const isBuildContext = isRendition;
54
54
55 export const isPlainObject = (v: object) => {
55 export const isPlainObject = (v: object) => {
56 if (typeof v !== "object")
56 if (typeof v !== "object")
57 return false;
57 return false;
58
58
59 const vp = Object.getPrototypeOf(v);
59 const vp = Object.getPrototypeOf(v);
60 return !vp || vp === Object.prototype;
60 return !vp || vp === Object.prototype;
61 }
61 }
62
62
63 export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor =>
63 export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor =>
64 typeof v === "function" && v.prototype && (
64 typeof v === "function" && v.prototype && (
65 "domNode" in v.prototype ||
65 "domNode" in v.prototype ||
66 "buildRendering" in v.prototype
66 "buildRendering" in v.prototype
67 );
67 );
68
68
69
69
70 /** Tests whether the specified node is placed in visible dom.
70 /** Tests whether the specified node is placed in visible dom.
71 * @param {Node} node The node to test
71 * @param {Node} node The node to test
72 */
72 */
73 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);
74
74
75 export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable =>
75 export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable =>
76 !!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function");
76 !!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function");
77
77
78
78
79
79
80 /** Destroys DOM Node with all contained widgets.
80 /** Destroys DOM Node with all contained widgets.
81 * 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
82 * widget will be destroyed.
82 * widget will be destroyed.
83 *
83 *
84 * @param target DOM Node or widget to destroy
84 * @param target DOM Node or widget to destroy
85 */
85 */
86 export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => {
86 export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => {
87 if (isRecursivelyDestroyable(target)) {
87 if (isRecursivelyDestroyable(target)) {
88 target.destroyRecursive();
88 target.destroyRecursive();
89 } else if (isDestroyable(target)) {
89 } else if (isDestroyable(target)) {
90 target.destroy();
90 target.destroy();
91 } else if (isNode(target)) {
91 } else if (isNode(target)) {
92 if (isElementNode(target)) {
92 if (isElementNode(target)) {
93 const w = registry.byNode(target);
93 const w = registry.byNode(target);
94 if (w) {
94 if (w) {
95 w.destroyRecursive();
95 w.destroyRecursive();
96 return;
96 } else {
97 } else {
97 emptyNode(target);
98 emptyNode(target);
98 const parent = target.parentNode;
99 if (parent)
100 parent.removeChild(target);
101 }
99 }
102 }
100 }
101 const parent = target.parentNode;
102 if (parent)
103 parent.removeChild(target);
104
103 }
105 }
104 }
106 }
105
107
106 /** Empties a content of the specified node and destroys all contained widgets.
108 /** Empties a content of the specified node and destroys all contained widgets.
107 *
109 *
108 * @param target DOM node to empty.
110 * @param target DOM node to empty.
109 */
111 */
110 export const emptyNode = (target: Node) => {
112 export const emptyNode = (target: Node) => {
111 registry.findWidgets(target).forEach(destroy);
113 registry.findWidgets(target).forEach(destroy);
112
114
113 for (let c; c = target.lastChild;) { // intentional assignment
115 for (let c; c = target.lastChild;) { // intentional assignment
114 target.removeChild(c);
116 target.removeChild(c);
115 }
117 }
116 }
118 }
117
119
118 /** This function starts all widgets inside the DOM node if the target is a node
120 /** This function starts all widgets inside the DOM node if the target is a node
119 * or starts widget itself if the target is the widget. If the specified node
121 * or starts widget itself if the target is the widget. If the specified node
120 * associated with the widget that widget will be started.
122 * associated with the widget that widget will be started.
121 *
123 *
122 * @param target DOM node to find and start widgets or the widget itself.
124 * @param target DOM node to find and start widgets or the widget itself.
123 */
125 */
124 export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => {
126 export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => {
125 if (isNode(target)) {
127 if (isNode(target)) {
126 if (isElementNode(target)) {
128 if (isElementNode(target)) {
127 const w = registry.byNode(target);
129 const w = registry.byNode(target);
128 if (w) {
130 if (w) {
129 if (w.startup)
131 if (w.startup)
130 w.startup();
132 w.startup();
131 } else {
133 } else {
132 registry.findWidgets(target, skipNode).forEach(x => x.startup());
134 registry.findWidgets(target, skipNode).forEach(x => x.startup());
133 }
135 }
134 }
136 }
135 } else {
137 } else {
136 if (target.startup)
138 if (target.startup)
137 target.startup();
139 target.startup();
138 }
140 }
139 }
141 }
140
142
141 /** Places the specified DOM node at the specified location.
143 /** Places the specified DOM node at the specified location.
142 *
144 *
143 * @param node The node which should be placed
145 * @param node The node which should be placed
144 * @param refNodeOrId The reference node where the created
146 * @param refNodeOrId The reference node where the created
145 * DOM should be placed.
147 * DOM should be placed.
146 * @param position Optional parameter, specifies the
148 * @param position Optional parameter, specifies the
147 * position relative to refNode. Default is "last" (i.e. last child).
149 * position relative to refNode. Default is "last" (i.e. last child).
148 */
150 */
149 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => {
151 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => {
150 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
152 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
151 if (!ref)
153 if (!ref)
152 return;
154 return;
153
155
154 const parent = ref.parentNode;
156 const parent = ref.parentNode;
155
157
156 if (typeof position == "number") {
158 if (typeof position == "number") {
157 if (ref.childNodes.length <= position) {
159 if (ref.childNodes.length <= position) {
158 ref.appendChild(node);
160 ref.appendChild(node);
159 } else {
161 } else {
160 ref.insertBefore(node, ref.childNodes[position]);
162 ref.insertBefore(node, ref.childNodes[position]);
161 }
163 }
162 } else {
164 } else {
163 switch (position) {
165 switch (position) {
164 case "before":
166 case "before":
165 parent && parent.insertBefore(node, ref);
167 parent && parent.insertBefore(node, ref);
166 break;
168 break;
167 case "after":
169 case "after":
168 parent && parent.insertBefore(node, ref.nextSibling);
170 parent && parent.insertBefore(node, ref.nextSibling);
169 break;
171 break;
170 case "first":
172 case "first":
171 ref.insertBefore(node, ref.firstChild);
173 ref.insertBefore(node, ref.firstChild);
172 break;
174 break;
173 case "last":
175 case "last":
174 ref.appendChild(node);
176 ref.appendChild(node);
175 break;
177 break;
176 case "only":
178 case "only":
177 emptyNode(ref);
179 emptyNode(ref);
178 ref.appendChild(node);
180 ref.appendChild(node);
179 break;
181 break;
180 case "replace":
182 case "replace":
181 if (parent)
183 if (parent)
182 parent.replaceChild(node, ref);
184 parent.replaceChild(node, ref);
183 destroy(ref);
185 destroy(ref);
184 break;
186 break;
185 }
187 }
186 }
188 }
187 }
189 }
188
190
189 /** Collects nodes from collection to an array.
191 /** Collects nodes from collection to an array.
190 *
192 *
191 * @param collection The collection of nodes.
193 * @param collection The collection of nodes.
192 * @returns The array of nodes.
194 * @returns The array of nodes.
193 */
195 */
194 export const collectNodes = (collection: HTMLCollection) => {
196 export const collectNodes = (collection: NodeListOf<ChildNode>) => {
195 const items = [];
197 const items = [];
196 for (let i = 0, n = collection.length; i < n; i++) {
198 for (let i = 0, n = collection.length; i < n; i++) {
197 items.push(collection[i]);
199 items.push(collection[i]);
198 }
200 }
199 return items;
201 return items;
200 };
202 };
201
203
202 /** Starts widgets if the node contained in the document or in the started widget.
204
203 *
205 export const isMounted = (node: Node) => {
204 * @param node The node to start.
205 */
206 export const autostartWidgets = (node: Node) => {
207 if (node.parentNode) {
206 if (node.parentNode) {
208 const parentWidget = registry.getEnclosingWidget(node.parentNode);
207 const parentWidget = registry.getEnclosingWidget(node.parentNode);
209 if (parentWidget && parentWidget._started)
208 if (parentWidget && parentWidget._started)
210 return startupWidgets(node);
209 return true;
211 }
210 }
212 if (isInPage(node))
211 if (isInPage(node))
213 startupWidgets(node);
212 return true;
213 return false;
214 }; No newline at end of file
214 };
@@ -1,81 +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 | undefined) => 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 children?: unknown;
80 }
80 }
81 }
81 }
@@ -1,1 +1,2
1 import "./DeclareTests"; No newline at end of file
1 import "./declare-tests";
2 import "./observable-tests"; No newline at end of file
@@ -1,123 +1,126
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"
6 def container = "djx-playground"
7
7
8 configurations {
8 configurations {
9 npmLocal
9 npmLocal
10 }
10 }
11
11
12 dependencies {
12 dependencies {
13 npmLocal project(":djx")
13 npmLocal project(":djx")
14 }
14 }
15
15
16 def bundleDir = fileTree(layout.buildDirectory.dir("bundle")) {
16 def bundleDir = fileTree(layout.buildDirectory.dir("bundle")) {
17 builtBy "bundle"
17 builtBy "bundle"
18 }
18 }
19
19
20 typescript {
20 typescript {
21 compilerOptions {
21 compilerOptions {
22 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
22 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
23 // listFiles = true
23 // listFiles = true
24 strict = true
24 strict = true
25 types = ["requirejs", "@implab/dojo-typings", "@implab/djx"]
25 types = ["requirejs", "@implab/dojo-typings", "@implab/djx"]
26 module = "amd"
26 module = "amd"
27 it.target = "es5"
27 it.target = "es5"
28 experimentalDecorators = true
28 experimentalDecorators = true
29 noUnusedLocals = false
29 noUnusedLocals = false
30 jsx = "react"
30 jsx = "react"
31 jsxFactory = "createElement"
31 jsxFactory = "createElement"
32 moduleResolution = "node"
32 moduleResolution = "node"
33 // dojo-typings are sick
33 // dojo-typings are sick
34 skipLibCheck = true
34 skipLibCheck = true
35 // traceResolution = true
35 // traceResolution = true
36 // baseUrl = "./"
36 // baseUrl = "./"
37 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
37 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
38 // baseUrl = "$projectDir/src/typings"
38 // baseUrl = "$projectDir/src/typings"
39 // typeRoots = ["$projectDir/src/typings"]
39 // typeRoots = ["$projectDir/src/typings"]
40 }
40 }
41 tscCmd = "$projectDir/node_modules/.bin/tsc"
41 tscCmd = "$projectDir/node_modules/.bin/tsc"
42 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
42 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
43 esLintCmd = "$projectDir/node_modules/.bin/eslint"
43 esLintCmd = "$projectDir/node_modules/.bin/eslint"
44 }
44 }
45
45
46 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
46 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
47 compilerOptions {
47 compilerOptions {
48 if (symbols != 'none') {
48 if (symbols != 'none') {
49 sourceMap = true
49 sourceMap = true
50 switch(symbols) {
50 switch(symbols) {
51 case "local":
51 case "local":
52 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
52 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
53 break;
53 break;
54 }
54 }
55 }
55 }
56 }
56 }
57 }
57 }
58
58
59 npmInstall {
59 npmInstall {
60 //npmInstall.dependsOn it
60 //npmInstall.dependsOn it
61 dependsOn configurations.npmLocal
61 dependsOn configurations.npmLocal
62
62
63 doFirst {
63 doFirst {
64 configurations.npmLocal.each { f ->
64 configurations.npmLocal.each { f ->
65 exec {
65 exec {
66 commandLine "npm", "install", f, "--save-dev"
66 commandLine "npm", "install", f, "--save-dev"
67 }
67 }
68 }
68 }
69 }
69 }
70 }
70 }
71
71
72 clean {
72 clean {
73 doFirst {
73 doFirst {
74 delete "$buildDir/bundle"
74 delete "$buildDir/bundle"
75 }
75 }
76 }
76 }
77
77
78
78
79 task processResourcesBundle(type: Copy) {
79 task processResourcesBundle(type: Copy) {
80 from "src/bundle"
80 from "src/bundle"
81 into layout.buildDirectory.dir("bundle")
81 into layout.buildDirectory.dir("bundle")
82 }
82 }
83
83
84 task copyModules(type: Copy) {
84 task copyModules(type: Copy) {
85 dependsOn npmInstall
85 dependsOn npmInstall
86 into layout.buildDirectory.dir("bundle/js");
86 into layout.buildDirectory.dir("bundle/js");
87
87
88 def pack = { String jsmod ->
88 def pack = { String jsmod ->
89 into(jsmod) {
89 into(jsmod) {
90 from npm.module(jsmod)
90 from npm.module(jsmod)
91 }
91 }
92 }
92 }
93
93
94
94
95 pack("@implab/djx")
95 pack("@implab/djx")
96 pack("@implab/core-amd")
96 pack("@implab/core-amd")
97 pack("dojo")
97 pack("dojo")
98 pack("dijit")
98 pack("dijit")
99 into("rxjs") {
100 from(npm.module("rxjs/dist/bundles"))
101 }
99 from npm.module("requirejs/require.js")
102 from npm.module("requirejs/require.js")
100 }
103 }
101
104
102 task copyApp(type: Copy) {
105 task copyApp(type: Copy) {
103 dependsOn assemble
106 dependsOn assemble
104 from typescript.assemblyDir
107 from typescript.assemblyDir
105 into layout.buildDirectory.dir("bundle/js/app")
108 into layout.buildDirectory.dir("bundle/js/app")
106 }
109 }
107
110
108 task bundle {
111 task bundle {
109 dependsOn copyModules, processResourcesBundle, copyApp
112 dependsOn copyModules, processResourcesBundle, copyApp
110 }
113 }
111
114
112 task up(type: Exec) {
115 task up(type: Exec) {
113 dependsOn bundle
116 dependsOn bundle
114 commandLine "podman", "run", "--rm", "-d",
117 commandLine "podman", "run", "--rm", "-d",
115 "--name", container,
118 "--name", container,
116 "-p", "2078:80",
119 "-p", "2078:80",
117 "-v", "$buildDir/bundle:/srv/www/htdocs",
120 "-v", "$buildDir/bundle:/srv/www/htdocs",
118 "registry.implab.org/implab/apache2:latest"
121 "registry.implab.org/implab/apache2:latest"
119 }
122 }
120
123
121 task stop(type: Exec) {
124 task stop(type: Exec) {
122 commandLine "podman", "stop", container
125 commandLine "podman", "stop", container
123 } No newline at end of file
126 }
@@ -1,143 +1,170
1 {
1 {
2 "name": "@implab/djx-playground",
2 "name": "@implab/djx-playground",
3 "lockfileVersion": 2,
3 "lockfileVersion": 2,
4 "requires": true,
4 "requires": true,
5 "packages": {
5 "packages": {
6 "": {
6 "": {
7 "name": "@implab/djx-playground",
7 "name": "@implab/djx-playground",
8 "dependencies": {
8 "dependencies": {
9 "dijit": "1.17.3",
9 "dijit": "1.17.3",
10 "dojo": "1.17.3",
10 "dojo": "1.17.3",
11 "requirejs": "2.3.6"
11 "requirejs": "2.3.6",
12 "rxjs": "7.5.6"
12 },
13 },
13 "devDependencies": {
14 "devDependencies": {
14 "@implab/core-amd": "1.4.6",
15 "@implab/core-amd": "1.4.6",
15 "@implab/djx": "file:../djx/build/npm/package",
16 "@implab/djx": "file:../djx/build/npm/package",
16 "@implab/dojo-typings": "1.0.2",
17 "@implab/dojo-typings": "1.0.2",
17 "@types/requirejs": "2.1.34",
18 "@types/requirejs": "2.1.34",
18 "typescript": "4.8.2"
19 "typescript": "4.8.2"
19 }
20 }
20 },
21 },
21 "../djx/build/npm/package": {
22 "../djx/build/npm/package": {
22 "name": "@implab/djx",
23 "name": "@implab/djx",
23 "dev": true,
24 "dev": true,
24 "license": "BSD-2-Clause",
25 "license": "BSD-2-Clause",
25 "peerDependencies": {
26 "peerDependencies": {
26 "@implab/core-amd": "^1.4.0",
27 "@implab/core-amd": "^1.4.0",
27 "dojo": "^1.10.0"
28 "dojo": "^1.10.0"
28 }
29 }
29 },
30 },
30 "node_modules/@implab/core-amd": {
31 "node_modules/@implab/core-amd": {
31 "version": "1.4.6",
32 "version": "1.4.6",
32 "resolved": "https://registry.npmjs.org/@implab/core-amd/-/core-amd-1.4.6.tgz",
33 "resolved": "https://registry.npmjs.org/@implab/core-amd/-/core-amd-1.4.6.tgz",
33 "integrity": "sha512-I1RwUAxeiodePpiBzveoHaehMSAyk7NFPPPEvDqfphHBC8yXoXWAaUrp7EcOKEzjXAs7lJQVhNpmjCjIqoj6BQ==",
34 "integrity": "sha512-I1RwUAxeiodePpiBzveoHaehMSAyk7NFPPPEvDqfphHBC8yXoXWAaUrp7EcOKEzjXAs7lJQVhNpmjCjIqoj6BQ==",
34 "dev": true,
35 "dev": true,
35 "peerDependencies": {
36 "peerDependencies": {
36 "dojo": "^1.10.0"
37 "dojo": "^1.10.0"
37 }
38 }
38 },
39 },
39 "node_modules/@implab/djx": {
40 "node_modules/@implab/djx": {
40 "resolved": "../djx/build/npm/package",
41 "resolved": "../djx/build/npm/package",
41 "link": true
42 "link": true
42 },
43 },
43 "node_modules/@implab/dojo-typings": {
44 "node_modules/@implab/dojo-typings": {
44 "version": "1.0.2",
45 "version": "1.0.2",
45 "resolved": "https://registry.npmjs.org/@implab/dojo-typings/-/dojo-typings-1.0.2.tgz",
46 "resolved": "https://registry.npmjs.org/@implab/dojo-typings/-/dojo-typings-1.0.2.tgz",
46 "integrity": "sha512-/lbcMCHdRoHJLKFcT8xdk1KbGazSlb1pGSDJ406io7iMenPm/XbJYcUti+VzXnn71zOJ8aYpGT12T5L0rfOZNA==",
47 "integrity": "sha512-/lbcMCHdRoHJLKFcT8xdk1KbGazSlb1pGSDJ406io7iMenPm/XbJYcUti+VzXnn71zOJ8aYpGT12T5L0rfOZNA==",
47 "dev": true
48 "dev": true
48 },
49 },
49 "node_modules/@types/requirejs": {
50 "node_modules/@types/requirejs": {
50 "version": "2.1.34",
51 "version": "2.1.34",
51 "resolved": "https://registry.npmjs.org/@types/requirejs/-/requirejs-2.1.34.tgz",
52 "resolved": "https://registry.npmjs.org/@types/requirejs/-/requirejs-2.1.34.tgz",
52 "integrity": "sha512-iQLGNE1DyIRYih60B47l/hI5X7J0wAnnRBL6Yn85GUYQg8Fm3wl8kvT6NRwncKroUOSx7/lbAagIFNV7y02DiQ==",
53 "integrity": "sha512-iQLGNE1DyIRYih60B47l/hI5X7J0wAnnRBL6Yn85GUYQg8Fm3wl8kvT6NRwncKroUOSx7/lbAagIFNV7y02DiQ==",
53 "dev": true
54 "dev": true
54 },
55 },
55 "node_modules/dijit": {
56 "node_modules/dijit": {
56 "version": "1.17.3",
57 "version": "1.17.3",
57 "resolved": "https://registry.npmjs.org/dijit/-/dijit-1.17.3.tgz",
58 "resolved": "https://registry.npmjs.org/dijit/-/dijit-1.17.3.tgz",
58 "integrity": "sha512-QS+1bNhPT+BF9E+iomQSi5qI+o3oUNSx1r5TF8WlGH4LybGZP+IIGJBOO5/41YduBPljVXhY7vaPsgrycxC6UQ==",
59 "integrity": "sha512-QS+1bNhPT+BF9E+iomQSi5qI+o3oUNSx1r5TF8WlGH4LybGZP+IIGJBOO5/41YduBPljVXhY7vaPsgrycxC6UQ==",
59 "dependencies": {
60 "dependencies": {
60 "dojo": "1.17.3"
61 "dojo": "1.17.3"
61 }
62 }
62 },
63 },
63 "node_modules/dojo": {
64 "node_modules/dojo": {
64 "version": "1.17.3",
65 "version": "1.17.3",
65 "resolved": "https://registry.npmjs.org/dojo/-/dojo-1.17.3.tgz",
66 "resolved": "https://registry.npmjs.org/dojo/-/dojo-1.17.3.tgz",
66 "integrity": "sha512-iWDx1oSfCEDnIrs8cMW7Zh9Fbjgxu8iRagFz+Qi2eya3MXIAxFXKhv2A7dpi+bfpMpFozLwcsLV8URLw6BsHsA=="
67 "integrity": "sha512-iWDx1oSfCEDnIrs8cMW7Zh9Fbjgxu8iRagFz+Qi2eya3MXIAxFXKhv2A7dpi+bfpMpFozLwcsLV8URLw6BsHsA=="
67 },
68 },
68 "node_modules/requirejs": {
69 "node_modules/requirejs": {
69 "version": "2.3.6",
70 "version": "2.3.6",
70 "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
71 "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
71 "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==",
72 "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==",
72 "bin": {
73 "bin": {
73 "r_js": "bin/r.js",
74 "r_js": "bin/r.js",
74 "r.js": "bin/r.js"
75 "r.js": "bin/r.js"
75 },
76 },
76 "engines": {
77 "engines": {
77 "node": ">=0.4.0"
78 "node": ">=0.4.0"
78 }
79 }
79 },
80 },
81 "node_modules/rxjs": {
82 "version": "7.5.6",
83 "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz",
84 "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==",
85 "dependencies": {
86 "tslib": "^2.1.0"
87 }
88 },
89 "node_modules/tslib": {
90 "version": "2.4.0",
91 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
92 "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
93 },
80 "node_modules/typescript": {
94 "node_modules/typescript": {
81 "version": "4.8.2",
95 "version": "4.8.2",
82 "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
96 "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
83 "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
97 "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
84 "dev": true,
98 "dev": true,
85 "bin": {
99 "bin": {
86 "tsc": "bin/tsc",
100 "tsc": "bin/tsc",
87 "tsserver": "bin/tsserver"
101 "tsserver": "bin/tsserver"
88 },
102 },
89 "engines": {
103 "engines": {
90 "node": ">=4.2.0"
104 "node": ">=4.2.0"
91 }
105 }
92 }
106 }
93 },
107 },
94 "dependencies": {
108 "dependencies": {
95 "@implab/core-amd": {
109 "@implab/core-amd": {
96 "version": "1.4.6",
110 "version": "1.4.6",
97 "resolved": "https://registry.npmjs.org/@implab/core-amd/-/core-amd-1.4.6.tgz",
111 "resolved": "https://registry.npmjs.org/@implab/core-amd/-/core-amd-1.4.6.tgz",
98 "integrity": "sha512-I1RwUAxeiodePpiBzveoHaehMSAyk7NFPPPEvDqfphHBC8yXoXWAaUrp7EcOKEzjXAs7lJQVhNpmjCjIqoj6BQ==",
112 "integrity": "sha512-I1RwUAxeiodePpiBzveoHaehMSAyk7NFPPPEvDqfphHBC8yXoXWAaUrp7EcOKEzjXAs7lJQVhNpmjCjIqoj6BQ==",
99 "dev": true,
113 "dev": true,
100 "requires": {}
114 "requires": {}
101 },
115 },
102 "@implab/djx": {
116 "@implab/djx": {
103 "version": "file:../djx/build/npm/package",
117 "version": "file:../djx/build/npm/package",
104 "requires": {}
118 "requires": {}
105 },
119 },
106 "@implab/dojo-typings": {
120 "@implab/dojo-typings": {
107 "version": "1.0.2",
121 "version": "1.0.2",
108 "resolved": "https://registry.npmjs.org/@implab/dojo-typings/-/dojo-typings-1.0.2.tgz",
122 "resolved": "https://registry.npmjs.org/@implab/dojo-typings/-/dojo-typings-1.0.2.tgz",
109 "integrity": "sha512-/lbcMCHdRoHJLKFcT8xdk1KbGazSlb1pGSDJ406io7iMenPm/XbJYcUti+VzXnn71zOJ8aYpGT12T5L0rfOZNA==",
123 "integrity": "sha512-/lbcMCHdRoHJLKFcT8xdk1KbGazSlb1pGSDJ406io7iMenPm/XbJYcUti+VzXnn71zOJ8aYpGT12T5L0rfOZNA==",
110 "dev": true
124 "dev": true
111 },
125 },
112 "@types/requirejs": {
126 "@types/requirejs": {
113 "version": "2.1.34",
127 "version": "2.1.34",
114 "resolved": "https://registry.npmjs.org/@types/requirejs/-/requirejs-2.1.34.tgz",
128 "resolved": "https://registry.npmjs.org/@types/requirejs/-/requirejs-2.1.34.tgz",
115 "integrity": "sha512-iQLGNE1DyIRYih60B47l/hI5X7J0wAnnRBL6Yn85GUYQg8Fm3wl8kvT6NRwncKroUOSx7/lbAagIFNV7y02DiQ==",
129 "integrity": "sha512-iQLGNE1DyIRYih60B47l/hI5X7J0wAnnRBL6Yn85GUYQg8Fm3wl8kvT6NRwncKroUOSx7/lbAagIFNV7y02DiQ==",
116 "dev": true
130 "dev": true
117 },
131 },
118 "dijit": {
132 "dijit": {
119 "version": "1.17.3",
133 "version": "1.17.3",
120 "resolved": "https://registry.npmjs.org/dijit/-/dijit-1.17.3.tgz",
134 "resolved": "https://registry.npmjs.org/dijit/-/dijit-1.17.3.tgz",
121 "integrity": "sha512-QS+1bNhPT+BF9E+iomQSi5qI+o3oUNSx1r5TF8WlGH4LybGZP+IIGJBOO5/41YduBPljVXhY7vaPsgrycxC6UQ==",
135 "integrity": "sha512-QS+1bNhPT+BF9E+iomQSi5qI+o3oUNSx1r5TF8WlGH4LybGZP+IIGJBOO5/41YduBPljVXhY7vaPsgrycxC6UQ==",
122 "requires": {
136 "requires": {
123 "dojo": "1.17.3"
137 "dojo": "1.17.3"
124 }
138 }
125 },
139 },
126 "dojo": {
140 "dojo": {
127 "version": "1.17.3",
141 "version": "1.17.3",
128 "resolved": "https://registry.npmjs.org/dojo/-/dojo-1.17.3.tgz",
142 "resolved": "https://registry.npmjs.org/dojo/-/dojo-1.17.3.tgz",
129 "integrity": "sha512-iWDx1oSfCEDnIrs8cMW7Zh9Fbjgxu8iRagFz+Qi2eya3MXIAxFXKhv2A7dpi+bfpMpFozLwcsLV8URLw6BsHsA=="
143 "integrity": "sha512-iWDx1oSfCEDnIrs8cMW7Zh9Fbjgxu8iRagFz+Qi2eya3MXIAxFXKhv2A7dpi+bfpMpFozLwcsLV8URLw6BsHsA=="
130 },
144 },
131 "requirejs": {
145 "requirejs": {
132 "version": "2.3.6",
146 "version": "2.3.6",
133 "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
147 "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
134 "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg=="
148 "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg=="
135 },
149 },
150 "rxjs": {
151 "version": "7.5.6",
152 "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz",
153 "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==",
154 "requires": {
155 "tslib": "^2.1.0"
156 }
157 },
158 "tslib": {
159 "version": "2.4.0",
160 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
161 "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
162 },
136 "typescript": {
163 "typescript": {
137 "version": "4.8.2",
164 "version": "4.8.2",
138 "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
165 "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
139 "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
166 "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
140 "dev": true
167 "dev": true
141 }
168 }
142 }
169 }
143 }
170 }
@@ -1,16 +1,17
1 {
1 {
2 "name": "@implab/djx-playground",
2 "name": "@implab/djx-playground",
3 "private": true,
3 "private": true,
4 "dependencies": {
4 "dependencies": {
5 "dijit": "1.17.3",
5 "dijit": "1.17.3",
6 "dojo": "1.17.3",
6 "dojo": "1.17.3",
7 "requirejs": "2.3.6"
7 "requirejs": "2.3.6",
8 "rxjs": "7.5.6"
8 },
9 },
9 "devDependencies": {
10 "devDependencies": {
10 "@implab/core-amd": "1.4.6",
11 "@implab/core-amd": "1.4.6",
11 "@implab/djx": "file:../djx/build/npm/package",
12 "@implab/djx": "file:../djx/build/npm/package",
12 "@implab/dojo-typings": "1.0.2",
13 "@implab/dojo-typings": "1.0.2",
13 "@types/requirejs": "2.1.34",
14 "@types/requirejs": "2.1.34",
14 "typescript": "4.8.2"
15 "typescript": "4.8.2"
15 }
16 }
16 }
17 }
@@ -1,11 +1,16
1 requirejs.config({
1 requirejs.config({
2 baseUrl: "js",
2 baseUrl: "js",
3 packages: [
3 packages: [
4 "app",
4 "app",
5 "@implab/djx",
5 "@implab/djx",
6 "@implab/core-amd",
6 "@implab/core-amd",
7 "dojo",
7 "dojo",
8 "dijit"
8 "dijit",
9 {
10 name: "rxjs",
11 location: "rxjs",
12 main: "rxjs.umd.min"
13 }
9 ],
14 ],
10 deps: ["app"]
15 deps: ["app"]
11 });
16 });
@@ -1,49 +1,82
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, watch } from "@implab/djx/tsx";
3 import { createElement, watch, prop, attach, all, bind, toggleClass } from "@implab/djx/tsx";
4 import ProgressBar from "./ProgressBar";
4 import ProgressBar from "./ProgressBar";
5 import Button = require("dijit/form/Button");
5 import Button = require("dijit/form/Button");
6
6 import { interval } from "rxjs";
7 const ref = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
8
7
9 @djclass
8 @djclass
10 export default class MainWidget extends djbase(DjxWidgetBase) {
9 export default class MainWidget extends djbase(DjxWidgetBase) {
11
10
12 titleNode?: HTMLHeadingElement;
11 titleNode?: HTMLHeadingElement;
13
12
14 progressBar?: ProgressBar;
13 progressBar?: ProgressBar;
15
14
16 count = 0;
15 count = 0;
17
16
18 showCounter = false;
17 showCounter = false;
19
18
19 counterNode?: HTMLInputElement;
20
21 paused = false;
22
20 render() {
23 render() {
24 const Counter = ({ children }: { children: unknown[] }) => <span>Counter: {children}</span>;
25
21 return <div className="tundra">
26 return <div className="tundra">
22 <h2 ref={ref(this, "titleNode")}>Hi!</h2>
27 <h2 ref={attach(this, "titleNode")}>Hi!</h2>
23 <ProgressBar ref={ref(this, "progressBar")} />
28 <ProgressBar ref={attach(this, "progressBar")} />
24 {watch(this, "showCounter", flag => flag &&
29 <section style={{ padding: "10px" }}>
25 <section style={{padding: "10px"}}>
30 {watch(prop(this, "showCounter"), flag => flag &&
26 <label>
31 [
27 Counter: {watch(this, "count", v => [<span>{v}</span>, " ", <span>sec</span>])}
32 <Counter><input ref={all(
28 </label>
33 bind("value", prop(this, "count")
29 </section>
34 .map(x => x*10)
30 )}
35 .map(String)
36 ),
37 attach(this, "counterNode")
38 )} /> <span>ms</span></Counter>,
39 " | ",
40 <span ref={bind("innerHTML", interval(1000))}></span>,
41 " | ",
42 <Button
43 ref={all(
44 bind("label", prop(this, "paused")
45 .map(x => x ? "Unpause" : "Pause")
46 ),
47 toggleClass("paused", prop(this,"paused"))
48 )}
49 onClick={this._onPauseClick}
50 />
51 ]
52
53 )}
54 </section>
31 <Button onClick={this._onToggleCounterClick}>Toggle counter</Button>
55 <Button onClick={this._onToggleCounterClick}>Toggle counter</Button>
32 </div>;
56 </div>;
33 }
57 }
34
58
35 postCreate(): void {
59 postCreate(): void {
36 super.postCreate();
60 super.postCreate();
37
61
38 const inc = () => {
62 const h = setInterval(
39 this.set("count", this.count + 1);
63 () => {
40 this.defer(inc, 1000);
64 this.set("count", this.count + 1);
41 }
65 },
66 10
67 );
68 this.own({
69 destroy: () => {
70 clearInterval(h);
71 }
72 });
73 }
42
74
43 inc();
75 private _onPauseClick = () => {
76 this.set("paused", !this.paused);
44 }
77 }
45
78
46 private _onToggleCounterClick = () => {
79 private _onToggleCounterClick = () => {
47 this.set("showCounter", !this.showCounter);
80 this.set("showCounter", !this.showCounter);
48 }
81 }
49 }
82 }
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now