##// END OF EJS Templates
refactoring, adding scope to rendering methods
cin -
r96:a316cfea8bb1 v1.3
parent child
Show More
@@ -0,0 +1,34
1 import { IDestroyable } from "@implab/core-amd/interfaces";
2
3 export interface Sink<T> {
4 next: (value: T) => void;
5 error: (e: unknown) => void;
6 complete: () => void;
7 }
8
9 export type Consumer<T> = Partial<Sink<T>>;
10
11 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
12
13 export interface Observable<T> {
14 on(sink: Partial<Sink<T>>): IDestroyable;
15 }
16
17 const noop = () => {};
18
19 const sink = <T>(consumer: Consumer<T>) => {
20 const { next = noop, error = noop, complete = noop } = consumer;
21 let done = false;
22
23 return {
24 next: (value: T) => done && next(value),
25 error: (e: unknown) => done && (done = true, error(e)),
26 complete: () => done && (done = true, complete())
27 };
28 }
29
30 export const observe = <T>(producer: Producer<T>) => ({
31 on: (consumer: Consumer<T>) => ({
32 destroy: producer(sink(consumer)) ?? noop
33 })
34 });
@@ -1,119 +1,114
1 import { Constructor, IDestroyable, IRemovable } 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 { destroy, isWidgetConstructor, Rendition } from "./tsx/traits";
4 import { isWidgetConstructor, Rendition } from "./tsx/traits";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
6 import Stateful = require("dojo/Stateful");
6 import Stateful = require("dojo/Stateful");
7 import _WidgetBase = require("dijit/_WidgetBase");
7 import _WidgetBase = require("dijit/_WidgetBase");
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
9 import { WatchRendition } from "./tsx/WatchRendition";
10 import { observe } from "./observable";
9
11
10 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
12 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
11 if (typeof elementType === "string") {
13 if (typeof elementType === "string") {
12 const ctx = new HtmlRendition(elementType);
14 const ctx = new HtmlRendition(elementType);
13 if (args)
15 if (args)
14 args.forEach(x => ctx.visitNext(x));
16 args.forEach(x => ctx.visitNext(x));
15
17
16 return ctx;
18 return ctx;
17 } else if (isWidgetConstructor(elementType)) {
19 } else if (isWidgetConstructor(elementType)) {
18 const ctx = new WidgetRendition(elementType);
20 const ctx = new WidgetRendition(elementType);
19 if (args)
21 if (args)
20 args.forEach(x => ctx.visitNext(x));
22 args.forEach(x => ctx.visitNext(x));
21
23
22 return ctx;
24 return ctx;
23 } else if (typeof elementType === "function") {
25 } else if (typeof elementType === "function") {
24 const ctx = new FunctionRendition(elementType as (props: any) => Element);
26 const ctx = new FunctionRendition(elementType as (props: any) => Element);
25 if (args)
27 if (args)
26 args.forEach(x => ctx.visitNext(x));
28 args.forEach(x => ctx.visitNext(x));
27
29
28 return ctx;
30 return ctx;
29 } else {
31 } else {
30 throw new Error(`The element type '${elementType}' is unsupported`);
32 throw new Error(`The element type '${elementType}' is unsupported`);
31 }
33 }
32 }
34 }
33
35
34 export interface EventDetails<T = any> {
36 export interface EventDetails<T = any> {
35 detail: T;
37 detail: T;
36 }
38 }
37
39
38 export interface EventSelector {
40 export interface EventSelector {
39 selectorTarget: HTMLElement;
41 selectorTarget: HTMLElement;
40 target: HTMLElement;
42 target: HTMLElement;
41 }
43 }
42
44
43 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
45 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
44
46
45 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
47 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
46
48
47 type CleanFn = (instance: IRemovable | IDestroyable) => void;
48
49
49 /**
50 /**
50 * Observers the property and calls render callback each change.
51 * Observers the property and calls render callback each change.
51 *
52 *
52 * @param target The target object which property will be observed.
53 * @param target The target object which property will be observed.
53 * @param prop The name of the property.
54 * @param prop The name of the property.
54 * @param render The callback which will be called every time the value is changed
55 * @param render The callback which will be called every time the value is changed
55 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
56 * @returns Rendition which is created instantly
56 * @returns Rendition which is created instantly
57 */
57 */
58 export function watch<W extends _WidgetBase, K extends keyof W>(
58 export function watch<W extends _WidgetBase, K extends keyof W>(
59 target: W,
59 target: W,
60 prop: K,
60 prop: K,
61 render: (model: W[K]) => any,
61 render: (model: W[K]) => any
62 cleanupOrOwner?: { own: CleanFn } | CleanFn
63 ): Rendition;
62 ): Rendition;
64 /**
63 /**
65 * Observers the property and calls render callback each change.
64 * Observers the property and calls render callback each change.
66 *
65 *
67 * @param target The target object which property will be observed.
66 * @param target The target object which property will be observed.
68 * @param prop The name of the property.
67 * @param prop The name of the property.
69 * @param render The callback which will be called every time the value is changed
68 * @param render The callback which will be called every time the value is changed
70 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
71 * @returns Rendition which is created instantly
69 * @returns Rendition which is created instantly
72 */
70 */
73 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
71 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
74 target: T,
72 target: T,
75 prop: K,
73 prop: K,
76 render: (model: StatefulProps<T>[K]) => any,
74 render: (model: StatefulProps<T>[K]) => any
77 cleanupOrOwner?: { own: CleanFn } | CleanFn
78 ): Rendition;
75 ): Rendition;
79 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
76 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
80 target: T,
77 target: T,
81 prop: K,
78 prop: K,
82 render: (model: StatefulProps<T>[K]) => any,
79 render: (model: StatefulProps<T>[K]) => any
83 cleanupOrOwner: { own: CleanFn } | CleanFn = () => { }
84 ) {
80 ) {
85 let rendition = new FunctionRendition(() => render(target.get(prop)));
81 return new WatchRendition(
86 const _own = cleanupOrOwner instanceof Function ? cleanupOrOwner : (x: IRemovable) => cleanupOrOwner.own(x);
82 render,
87 _own(target.watch(prop, (_name, oldValue, newValue) => {
83 observe(({next}) => {
88 if (oldValue !== newValue) {
84 const h = target.watch(
89 const newRendition = new FunctionRendition(() => render(newValue));
85 prop,
90 newRendition.placeAt(rendition.getDomNode(), "replace");
86 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
91 destroy(rendition.getDomNode());
87 );
92 rendition = newRendition;
88 return () => h.remove();
93 }
89 })
94 }));
90 )
95 return rendition;
96 }
91 }
97
92
98 /** Decorates the method which will be registered as the handle for the specified event.
93 /** Decorates the method which will be registered as the handle for the specified event.
99 * This decorator can be applied to DjxWidgetBase subclass methods.
94 * This decorator can be applied to DjxWidgetBase subclass methods.
100 *
95 *
101 * ```
96 * ```
102 * @on("click")
97 * @on("click")
103 * _onClick(eventObj: MouseEvent) {
98 * _onClick(eventObj: MouseEvent) {
104 * // ...
99 * // ...
105 * }
100 * }
106 * ```
101 * ```
107 */
102 */
108 export const on = <E extends string>(...eventNames: E[]) =>
103 export const on = <E extends string>(...eventNames: E[]) =>
109 <K extends string,
104 <K extends string,
110 T extends DjxWidgetBase<any, { [p in E]: EV }>,
105 T extends DjxWidgetBase<any, { [p in E]: EV }>,
111 EV extends Event
106 EV extends Event
112 >(
107 >(
113 target: T,
108 target: T,
114 key: K,
109 key: K,
115 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
110 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
116 ): any => {
111 ): any => {
117 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
112 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
118 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
113 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
119 };
114 };
@@ -1,30 +1,33
1 import { argumentNotNull } from "@implab/core-amd/safe";
1 import { argumentNotNull } from "@implab/core-amd/safe";
2 import { getItemDom } from "./Renderer";
2 import { RenditionBase } from "./RenditionBase";
3 import { RenditionBase } from "./RenditionBase";
4 import { IScope } from "./Scope";
3
5
4 export class FunctionRendition extends RenditionBase<Node> {
6 export class FunctionRendition extends RenditionBase<Node> {
5 private _component: (...args: any[]) => any;
7 private _component: (...args: any[]) => any;
6
8
7 private _node: Node | undefined;
9 private _node: Node | undefined;
8
10
9 constructor(component: (...args: any[]) => any) {
11 constructor(component: (...args: any[]) => any) {
10 super();
12 super();
11 argumentNotNull(component, "component");
13 argumentNotNull(component, "component");
12
14
13 this._component = component;
15 this._component = component;
14 }
16 }
15
17
16 protected _create(attrs: object, children: any[]) {
18 protected _create(attrs: object, children: any[], scope: IScope) {
17 const _attrs: any = attrs || {};
19 const _attrs: any = attrs || {};
18 const _children = children.map(x => this.getItemDom(x));
20 const _children = children.map(x => getItemDom(x, scope));
19 this._node = this.getItemDom(
21 this._node = getItemDom(
20 this._component.call(null, { ..._attrs, children: _children })
22 this._component.call(null, { ..._attrs, children: _children }),
23 scope
21 );
24 );
22 }
25 }
23
26
24 protected _getDomNode() {
27 protected _getDomNode() {
25 if (!this._node)
28 if (!this._node)
26 throw new Error("The instance of the widget isn't created");
29 throw new Error("The instance of the widget isn't created");
27 return this._node;
30 return this._node;
28 }
31 }
29
32
30 }
33 }
@@ -1,36 +1,39
1 import dom = require("dojo/dom-construct");
1 import dom = require("dojo/dom-construct");
2 import { argumentNotEmptyString } from "@implab/core-amd/safe";
2 import { argumentNotEmptyString } from "@implab/core-amd/safe";
3 import { RenditionBase } from "./RenditionBase";
3 import { RenditionBase } from "./RenditionBase";
4 import { placeAt } from "./traits";
5 import { IScope } from "./Scope";
6 import { getItemDom } from "./Renderer";
4
7
5 export class HtmlRendition extends RenditionBase<HTMLElement> {
8 export class HtmlRendition extends RenditionBase<HTMLElement> {
6 elementType: string;
9 elementType: string;
7
10
8 _element: HTMLElement | undefined;
11 _element: HTMLElement | undefined;
9
12
10 constructor(elementType: string) {
13 constructor(elementType: string) {
11 argumentNotEmptyString(elementType, "elementType");
14 argumentNotEmptyString(elementType, "elementType");
12 super();
15 super();
13
16
14 this.elementType = elementType;
17 this.elementType = elementType;
15 }
18 }
16
19
17 _addChild(child: any): void {
20 _addChild(child: unknown, scope: IScope): void {
18 if (!this._element)
21 if (!this._element)
19 throw new Error("The HTML element isn't created");
22 throw new Error("The HTML element isn't created");
20 dom.place(this.getItemDom(child), this._element);
23 placeAt(getItemDom(child, scope), this._element);
21 }
24 }
22
25
23 _create(attrs: object, children: any[]) {
26 _create(attrs: object, children: unknown[], scope: IScope) {
24 this._element = dom.create(this.elementType, attrs);
27 this._element = dom.create(this.elementType, attrs);
25
28
26 children.forEach(v => this._addChild(v));
29 children.forEach(v => this._addChild(v, scope));
27 }
30 }
28
31
29 _getDomNode() {
32 _getDomNode() {
30 if (!this._element)
33 if (!this._element)
31 throw new Error("The HTML element isn't created");
34 throw new Error("The HTML element isn't created");
32
35
33 return this._element;
36 return this._element;
34 }
37 }
35
38
36 }
39 }
@@ -1,26 +1,49
1 import { Scope } from "./Scope";
1 import { IScope, Scope } from "./Scope";
2 import { destroy, Rendition } from "./traits";
2 import { destroy, isNode, isRendition, isWidget, Rendition } from "./traits";
3
3
4 let _scope = Scope.dummy;
4 let _scope = Scope.dummy;
5
5
6 const beginRender = async () => {
6 const beginRender = async () => {
7 }
7 }
8
8
9 const endRender = () => {
9 const endRender = () => {
10 }
10 }
11
11
12 export const getScope = () => _scope;
12 export const getScope = () => _scope;
13
13
14 export const render = async (rendition: () => Rendition, scope = Scope.dummy) => {
14 export const render = async (rendition: () => Rendition, scope = Scope.dummy) => {
15 await beginRender();
15 await beginRender();
16 const prev = _scope;
16 const prev = _scope;
17 _scope = scope;
17 _scope = scope;
18 try {
18 try {
19 const node = rendition().getDomNode();
19 const node = rendition().getDomNode();
20 scope.own(() => destroy(node));
20 scope.own(() => destroy(node));
21 return node;
21 return node;
22 } finally {
22 } finally {
23 _scope = prev;
23 _scope = prev;
24 endRender();
24 endRender();
25 }
25 }
26 }
26 }
27
28 /** Renders DOM element for different types of the argument. */
29 export const getItemDom = (v: unknown, scope: IScope) => {
30 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
31 // primitive types converted to the text nodes
32 return document.createTextNode(v.toString());
33 } else if (isNode(v)) {
34 // nodes are kept as is
35 return v;
36 } else if (isRendition(v)) {
37 // renditions are instantiated
38 return v.getDomNode(scope);
39 } else if (isWidget(v)) {
40 // widgets are converted to it's markup
41 return v.domNode;
42 } else if (typeof v === "boolean" || v === null || v === undefined) {
43 // null | undefined | boolean are removed, converted to comments
44 return document.createComment(`[${typeof v} ${String(v)}]`);
45 } else {
46 // bug: explicit error otherwise
47 throw new Error("Invalid parameter: " + v);
48 }
49 }
@@ -1,117 +1,73
1 import { isNull, mixin } from "@implab/core-amd/safe";
1 import { isNull, mixin } from "@implab/core-amd/safe";
2 import { isPlainObject, isNode, isRendition, DojoNodePosition, Rendition, isInPage, isWidget, isDocumentFragmentNode, startupWidgets } from "./traits";
2 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, autostartWidgets } from "./traits";
3
3
4 import dom = require("dojo/dom-construct");
4 import { IScope } from "./Scope";
5 import registry = require("dijit/registry");
5 import { getScope } from "./Renderer";
6
7
6
8 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
7 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
9 private _attrs = {};
8 private _attrs = {};
10
9
11 private _children = new Array();
10 private _children = new Array();
12
11
13 private _created: boolean = false;
12 private _created: boolean = false;
14
13
15 visitNext(v: any) {
14 visitNext(v: any) {
16 if (this._created)
15 if (this._created)
17 throw new Error("The Element is already created");
16 throw new Error("The Element is already created");
18
17
19 if (isNull(v) || typeof v === "boolean")
18 if (isNull(v) || typeof v === "boolean")
20 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
19 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
21 return;
20 return;
22
21
23 if (isPlainObject(v)) {
22 if (isPlainObject(v)) {
24 mixin(this._attrs, v);
23 mixin(this._attrs, v);
25 } else if (v instanceof Array) {
24 } else if (v instanceof Array) {
26 v.forEach(x => this.visitNext(x));
25 v.forEach(x => this.visitNext(x));
27 } else {
26 } else {
28 this._children.push(v);
27 this._children.push(v);
29 }
28 }
30 }
29 }
31
30
32 /** Renders DOM element for different types of the argument. */
31 ensureCreated(scope: IScope) {
33 protected getItemDom(v: any) {
34 const tv = typeof v;
35
36 if (tv === "string" || tv === "number" || v instanceof RegExp || v instanceof Date) {
37 // primitive types converted to the text nodes
38 return document.createTextNode(v.toString());
39 } else if (isNode(v)) {
40 // nodes are kept as is
41 return v;
42 } else if (isRendition(v)) {
43 // renditions are instantiated
44 return v.getDomNode();
45 } else if (isWidget(v)) {
46 // widgets are converted to it's markup
47 return v.domNode;
48 } else if (tv === "boolean" || v === null || v === undefined) {
49 // null | undefined | boolean are removed, converted to comments
50 return document.createComment(`[${tv} ${String(v)}]`);
51 } else {
52 // bug: explicit error otherwise
53 throw new Error("Invalid parameter: " + v);
54 }
55 }
56
57 ensureCreated() {
58 if (!this._created) {
32 if (!this._created) {
59 this._create(this._attrs, this._children);
33 this._create(this._attrs, this._children, scope);
60 this._children = [];
34 this._children = [];
61 this._attrs = {};
35 this._attrs = {};
62 this._created = true;
36 this._created = true;
63 }
37 }
64 }
38 }
65
39
66 /** Is rendition was instantiated to the DOM node */
40 /** Is rendition was instantiated to the DOM node */
67 isCreated() {
41 isCreated() {
68 return this._created;
42 return this._created;
69 }
43 }
70
44
71 /** Creates DOM node if not created. No additional actions are taken. */
45 /** Creates DOM node if not created. No additional actions are taken. */
72 getDomNode() {
46 getDomNode(scope?: IScope) {
73 this.ensureCreated();
47 this.ensureCreated(scope || getScope());
74 return this._getDomNode();
48 return this._getDomNode();
75 }
49 }
76
50
77 /** Creates DOM node if not created, places it to the specified position
51 /** Creates DOM node if not created, places it to the specified position
78 * and calls startup() method for all widgets contained by this node.
52 * and calls startup() method for all widgets contained by this node.
79 *
53 *
80 * @param {string | Node} refNode The reference node where the created
54 * @param {string | Node} refNode The reference node where the created
81 * DOM should be placed.
55 * DOM should be placed.
82 * @param {DojoNodePosition} position Optional parameter, specifies the
56 * @param {DojoNodePosition} position Optional parameter, specifies the
83 * position relative to refNode. Default is "last" (i.e. last child).
57 * position relative to refNode. Default is "last" (i.e. last child).
84 */
58 */
85 placeAt(refNode: string | Node, position?: DojoNodePosition) {
59 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
86 const domNode = this.getDomNode();
60 const domNode = this.getDomNode();
87
61
88 const collect = (collection: HTMLCollection) => {
62 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode];
89 const items = [];
90 for (let i = 0, n = collection.length; i < n; i++) {
91 items.push(collection[i]);
92 }
93 return items;
94 };
95
63
96 const startup = (node: Node) => {
64 placeAt(domNode, refNode, position);
97 if (node.parentNode) {
98 const parentWidget = registry.getEnclosingWidget(node.parentNode);
99 if (parentWidget && parentWidget._started)
100 return startupWidgets(node);
101 }
102 if (isInPage(node))
103 startupWidgets(node);
104 };
105
65
106 const startupPending = isDocumentFragmentNode(domNode) ? collect(domNode.children) : [domNode];
66 startupPending.forEach(autostartWidgets);
107
108 dom.place(domNode, refNode, position);
109
110 startupPending.forEach(startup);
111
67
112 }
68 }
113
69
114 protected abstract _create(attrs: object, children: any[]): void;
70 protected abstract _create(attrs: object, children: unknown[], scope: IScope): void;
115
71
116 protected abstract _getDomNode(): TNode;
72 protected abstract _getDomNode(): TNode;
117 }
73 }
@@ -1,58 +1,54
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 { place } from "dojo/dom-construct";
4 import { render } from "./Renderer";
5 import { getScope, render } from "./Renderer";
6 import { RenditionBase } from "./RenditionBase";
5 import { RenditionBase } from "./RenditionBase";
7 import { Scope } from "./Scope";
6 import { IScope, Scope } from "./Scope";
8 import { locateNode } from "./traits";
7 import { Observable } from "../observable";
9
8
10 const trace = TraceSource.get(mid);
9 const trace = TraceSource.get(mid);
11
10
12 export class WatchRendition<T> extends RenditionBase<Node> {
11 export class WatchRendition<T> extends RenditionBase<Node> {
13 private readonly _factory: (arg: T) => any;
12 private readonly _factory: (arg: T) => any;
14
13
15 private _node: Node;
14 private _node: Node;
16
15
17 private readonly _scope = new Scope();
16 private readonly _scope = new Scope();
18
17
19 constructor(component: (arg: T) => any, subject: any) {
18 private readonly _subject: Observable<T>;
19
20 constructor(component: (arg: T) => any, subject: Observable<T>) {
20 super();
21 super();
21 argumentNotNull(component, "component");
22 argumentNotNull(component, "component");
22
23
23 this._factory = component;
24 this._factory = component;
24
25
26 this._subject = subject;
27
25 this._node = document.createComment("WatchRendition placeholder");
28 this._node = document.createComment("WatchRendition placeholder");
26 }
29 }
27
30
28 protected _create(attrs: object, children: any[]) {
31 protected _create(attrs: object, children: any[], scope: IScope) {
29 const _attrs: any = attrs || {};
30 const _children = children.map(x => this.getItemDom(x));
31 this._node = this.getItemDom(
32 this._factory.call(null, { ..._attrs, children: _children })
33 );
34
35 const scope = getScope();
36 scope.own(this._scope);
32 scope.own(this._scope);
37
33 scope.own(this._subject.on({ next: this._onValue }))
38 // Ссли ΠΎΡ‚Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΠ»ΠΈ тСкст? ΠΈΠ»ΠΈ DocumentFragment
39 }
34 }
40
35
36 private _onValue = (value: T) => void this._render(value).catch( e => trace.error(e));
37
41 private async _render(value: T) {
38 private async _render(value: T) {
42 const [refNode, position] = locateNode(this._node);
39 const prevNode = this._node;
43 this._scope.clean();
40 this._scope.clean();
44
41
45 this._node = await render(() => this._factory(value), this._scope);
42 this._node = await render(() => this._factory(value), this._scope);
46
43
47 if (refNode)
44 this.placeAt(prevNode, "replace");
48 place(this._node, refNode, position);
49 }
45 }
50
46
51 protected _getDomNode() {
47 protected _getDomNode() {
52 if (!this._node)
48 if (!this._node)
53 throw new Error("The instance of the widget isn't created");
49 throw new Error("The instance of the widget isn't created");
54 return this._node;
50 return this._node;
55 }
51 }
56
52
57
53
58 }
54 }
@@ -1,128 +1,129
1 import dom = require("dojo/dom-construct");
2 import { argumentNotNull } from "@implab/core-amd/safe";
1 import { argumentNotNull } from "@implab/core-amd/safe";
3 import { RenditionBase } from "./RenditionBase";
2 import { getItemDom, RenditionBase } from "./RenditionBase";
4 import { DojoNodePosition, isElementNode, isInPage, isWidget } from "./traits";
3 import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits";
5 import registry = require("dijit/registry");
4 import registry = require("dijit/registry");
6 import ContentPane = require("dijit/layout/ContentPane");
5 import ContentPane = require("dijit/layout/ContentPane");
6 import { IScope } from "./Scope";
7 import { getScope } from "./Renderer";
7
8
8 // tslint:disable-next-line: class-name
9 // tslint:disable-next-line: class-name
9 export interface _Widget {
10 export interface _Widget {
10 domNode: Node;
11 domNode: Node;
11
12
12 containerNode?: Node;
13 containerNode?: Node;
13
14
14 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
15 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
15 startup?(): void;
16 startup?(): void;
16
17
17 addChild?(widget: any, index?: number): void;
18 addChild?(widget: any, index?: number): void;
18 }
19 }
19
20
20 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
21 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
21
22
22 export class WidgetRendition extends RenditionBase<Node> {
23 export class WidgetRendition extends RenditionBase<Node> {
23 readonly widgetClass: _WidgetCtor;
24 readonly widgetClass: _WidgetCtor;
24
25
25 _instance: _Widget | undefined;
26 _instance: _Widget | undefined;
26
27
27 constructor(widgetClass: _WidgetCtor) {
28 constructor(widgetClass: _WidgetCtor) {
28 super();
29 super();
29 argumentNotNull(widgetClass, "widgetClass");
30 argumentNotNull(widgetClass, "widgetClass");
30
31
31 this.widgetClass = widgetClass;
32 this.widgetClass = widgetClass;
32 }
33 }
33
34
34 _addChild(child: any): void {
35 _addChild(child: any, scope: IScope): void {
35 const instance = this._getInstance();
36 const instance = this._getInstance();
36
37
37 if (instance.addChild) {
38 if (instance.addChild) {
38 if (child instanceof WidgetRendition) {
39 if (child instanceof WidgetRendition) {
39 // layout containers add custom logic to addChild methods
40 // layout containers add custom logic to addChild methods
40 instance.addChild(child.getWidgetInstance());
41 instance.addChild(child.getWidgetInstance(scope));
41 } else if (isWidget(child)) {
42 } else if (isWidget(child)) {
42 instance.addChild(child);
43 instance.addChild(child);
43 } else {
44 } else {
44 const childDom = this.getItemDom(child);
45 const childDom = getItemDom(child, scope);
45 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
46 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
46
47
47 if (w) {
48 if (w) {
48 instance.addChild(w);
49 instance.addChild(w);
49 } else {
50 } else {
50 if (!instance.containerNode)
51 if (!instance.containerNode)
51 throw new Error("Failed to add DOM content. The widget doesn't have a containerNode");
52 throw new Error("Failed to add DOM content. The widget doesn't have a containerNode");
52
53
53 // the current widget isn't started, it's children shouldn't start too
54 // the current widget isn't started, it's children shouldn't start too
54 dom.place(this.getItemDom(child), instance.containerNode);
55 placeAt(getItemDom(child,scope), instance.containerNode, "last");
55 }
56 }
56 }
57 }
57 } else {
58 } else {
58 if (!instance.containerNode)
59 if (!instance.containerNode)
59 throw new Error("The widget doesn't have neither addChild nor containerNode");
60 throw new Error("The widget doesn't have neither addChild nor containerNode");
60
61
61 // the current widget isn't started, it's children shouldn't start too
62 // the current widget isn't started, it's children shouldn't start too
62 dom.place(this.getItemDom(child), instance.containerNode);
63 placeAt(getItemDom(child, scope), instance.containerNode, "last");
63 }
64 }
64 }
65 }
65
66
66 protected _create(attrs: any, children: any[]) {
67 protected _create(attrs: any, children: any[], scope: IScope) {
67 if (this.widgetClass.prototype instanceof ContentPane) {
68 if (this.widgetClass.prototype instanceof ContentPane) {
68 // a special case for the ContentPane this is for
69 // a special case for the ContentPane this is for
69 // the compatibility with this heavy widget, all
70 // the compatibility with this heavy widget, all
70 // regular containers could be easily manipulated
71 // regular containers could be easily manipulated
71 // through `containerNode` property or `addChild` method.
72 // through `containerNode` property or `addChild` method.
72
73
73 // render children to the DocumentFragment
74 // render children to the DocumentFragment
74 const content = document.createDocumentFragment();
75 const content = document.createDocumentFragment();
75 children.forEach(child => content.appendChild(this.getItemDom(child)));
76 children.forEach(child => content.appendChild(getItemDom(child, scope)));
76
77
77 // set the content property to the parameters of the widget
78 // set the content property to the parameters of the widget
78 const _attrs = { ...attrs, content };
79 const _attrs = { ...attrs, content };
79 this._instance = new this.widgetClass(_attrs);
80 this._instance = new this.widgetClass(_attrs);
80 } else {
81 } else {
81 this._instance = new this.widgetClass(attrs);
82 this._instance = new this.widgetClass(attrs);
82 children.forEach(x => this._addChild(x));
83 children.forEach(x => this._addChild(x, scope));
83 }
84 }
84
85
85 }
86 }
86
87
87 private _getInstance() {
88 private _getInstance() {
88 if (!this._instance)
89 if (!this._instance)
89 throw new Error("The instance of the widget isn't created");
90 throw new Error("The instance of the widget isn't created");
90 return this._instance;
91 return this._instance;
91 }
92 }
92
93
93 protected _getDomNode() {
94 protected _getDomNode() {
94 if (!this._instance)
95 if (!this._instance)
95 throw new Error("The instance of the widget isn't created");
96 throw new Error("The instance of the widget isn't created");
96 return this._instance.domNode;
97 return this._instance.domNode;
97 }
98 }
98
99
99 /** Overrides default placeAt implementation. Calls placeAt of the
100 /** Overrides default placeAt implementation. Calls placeAt of the
100 * widget and then starts it.
101 * widget and then starts it.
101 *
102 *
102 * @param refNode A node or id of the node where the widget should be placed.
103 * @param refNode A node or id of the node where the widget should be placed.
103 * @param position A position relative to refNode.
104 * @param position A position relative to refNode.
104 */
105 */
105 placeAt(refNode: string | Node, position?: DojoNodePosition) {
106 placeAt(refNode: string | Node, position?: DojoNodePosition) {
106 this.ensureCreated();
107 this.ensureCreated(getScope());
107 const instance = this._getInstance();
108 const instance = this._getInstance();
108 if (typeof instance.placeAt === "function") {
109 if (typeof instance.placeAt === "function") {
109 instance.placeAt(refNode, position);
110 instance.placeAt(refNode, position);
110
111
111 // fix the dojo startup behavior when the widget is placed
112 // fix the dojo startup behavior when the widget is placed
112 // directly to the document and doesn't have any enclosing widgets
113 // directly to the document and doesn't have any enclosing widgets
113 const parentWidget = instance.domNode.parentNode ?
114 const parentWidget = instance.domNode.parentNode ?
114 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
115 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
115 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
116 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
116 instance.startup();
117 instance.startup();
117 } else {
118 } else {
118 // the widget doesn't have a placeAt method, strange but whatever
119 // the widget doesn't have a placeAt method, strange but whatever
119 super.placeAt(refNode, position);
120 super.placeAt(refNode, position);
120 }
121 }
121 }
122 }
122
123
123 getWidgetInstance() {
124 getWidgetInstance(scope?: IScope) {
124 this.ensureCreated();
125 this.ensureCreated(scope || getScope());
125 return this._getInstance();
126 return this._getInstance();
126 }
127 }
127
128
128 }
129 }
@@ -1,206 +1,214
1 import { IDestroyable } from "@implab/core-amd/interfaces";
1 import { IDestroyable } from "@implab/core-amd/interfaces";
2 import { isDestroyable } from "@implab/core-amd/safe";
2 import { isDestroyable } from "@implab/core-amd/safe";
3 import _WidgetBase = require("dijit/_WidgetBase");
3 import _WidgetBase = require("dijit/_WidgetBase");
4 import registry = require("dijit/registry");
4 import registry = require("dijit/registry");
5 import dom = require("dojo/dom-construct");
5 import { IScope } from "./Scope";
6
6
7 type _WidgetBaseConstructor = typeof _WidgetBase;
7 type _WidgetBaseConstructor = typeof _WidgetBase;
8
8
9 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
9 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
10
10
11 export type DojoNodeLocation = [Node | null, DojoNodePosition];
11 export type DojoNodeLocation = [Node, DojoNodePosition];
12
12
13 export interface Rendition<TNode extends Node = Node> {
13 export interface Rendition<TNode extends Node = Node> {
14 getDomNode(): TNode;
14 getDomNode(scope?: IScope): TNode;
15
15
16 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
16 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
17 }
17 }
18
18
19 /**
19 /**
20 * @deprecated use Rendition
20 * @deprecated use Rendition
21 */
21 */
22 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
22 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
23
23
24 export interface IRecursivelyDestroyable {
24 export interface IRecursivelyDestroyable {
25 destroyRecursive(): void;
25 destroyRecursive(): void;
26 }
26 }
27
27
28 export function isNode(el: any): el is Node {
28 export const isNode = (el: unknown): el is Node => !!(el && (el as Node).nodeName && (el as Node).nodeType);
29 return el && el.nodeName && el.nodeType;
30 }
31
29
32 export function isElementNode(el: any): el is Element {
30 export const isElementNode = (el: unknown): el is Element => isNode(el) && el.nodeType === 1;
33 return isNode(el) && el.nodeType === 1;
34 }
35
31
36 export function isTextNode(el: any): el is Text {
32 export const isTextNode = (el: unknown): el is Text => isNode(el) && el.nodeType === 3;
37 return isNode(el) && el.nodeType === 3;
38 }
39
33
40 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
34 export const isProcessingInstructionNode = (el: unknown): el is ProcessingInstruction => isNode(el) && el.nodeType === 7;
41 return isNode(el) && el.nodeType === 7;
42 }
43
35
44 export function isCommentNode(el: any): el is Comment {
36 export const isCommentNode = (el: unknown): el is Comment => isNode(el) && el.nodeType === 8;
45 return isNode(el) && el.nodeType === 8;
46 }
47
37
48 export function isDocumentNode(el: any): el is Document {
38 export const isDocumentNode = (el: unknown): el is Document => isNode(el) && el.nodeType === 9;
49 return isNode(el) && el.nodeType === 9;
50 }
51
39
52 export function isDocumentTypeNode(el: any): el is DocumentType {
40 export const isDocumentTypeNode = (el: unknown): el is DocumentType => isNode(el) && el.nodeType === 10;
53 return isNode(el) && el.nodeType === 10;
54 }
55
41
56 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
42 export const isDocumentFragmentNode = (el: any): el is DocumentFragment => isNode(el) && el.nodeType === 11;
57 return isNode(el) && el.nodeType === 11;
58 }
59
43
60 export function isWidget(v: any): v is _WidgetBase {
44 export const isWidget = (v: unknown): v is _WidgetBase => !!(v && "domNode" in (v as _WidgetBase));
61 return v && "domNode" in v;
62 }
63
45
64 export function isRendition(v: any): v is Rendition {
46 export const isRendition = (v: unknown): v is Rendition => !!(v && typeof (v as Rendition).getDomNode === "function");
65 return v && typeof v.getDomElement === "function";
66 }
67
47
68 /**
48 /**
69 * @deprecated use isRendition
49 * @deprecated use isRendition
70 */
50 */
71 export const isBuildContext = isRendition;
51 export const isBuildContext = isRendition;
72
52
73 export function isPlainObject(v: object) {
53 export const isPlainObject = (v: object) => {
74 if (typeof v !== "object")
54 if (typeof v !== "object")
75 return false;
55 return false;
76
56
77 const vp = Object.getPrototypeOf(v);
57 const vp = Object.getPrototypeOf(v);
78 return !vp || vp === Object.prototype;
58 return !vp || vp === Object.prototype;
79 }
59 }
80
60
81 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
61 export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor =>
82 return typeof v === "function" && v.prototype && (
62 typeof v === "function" && v.prototype && (
83 "domNode" in v.prototype ||
63 "domNode" in v.prototype ||
84 "buildRendering" in v.prototype
64 "buildRendering" in v.prototype
85 );
65 );
86 }
66
87
67
88 /** Tests whether the specified node is placed in visible dom.
68 /** Tests whether the specified node is placed in visible dom.
89 * @param {Node} node The node to test
69 * @param {Node} node The node to test
90 */
70 */
91 export function isInPage(node: Node) {
71 export const isInPage = (node: Node) => node === document.body ? false : document.body.contains(node);
92 return (node === document.body) ? false : document.body.contains(node);
93 }
94
72
95 export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable {
73 export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable =>
96 return target && typeof target.destroyRecursive === "function";
74 !!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function");
97 }
75
98
76
99
77
100 /** Destroys DOM Node with all contained widgets.
78 /** Destroys DOM Node with all contained widgets.
101 * If the specified node is the root node of a widget, then the
79 * If the specified node is the root node of a widget, then the
102 * widget will be destroyed.
80 * widget will be destroyed.
103 *
81 *
104 * @param target DOM Node or widget to destroy
82 * @param target DOM Node or widget to destroy
105 */
83 */
106 export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) {
84 export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => {
107 if (isRecursivelyDestroyable(target)) {
85 if (isRecursivelyDestroyable(target)) {
108 target.destroyRecursive();
86 target.destroyRecursive();
109 } else if (isDestroyable(target)) {
87 } else if (isDestroyable(target)) {
110 target.destroy();
88 target.destroy();
111 } else if (isNode(target)) {
89 } else if (isNode(target)) {
112 if (isElementNode(target)) {
90 if (isElementNode(target)) {
113 const w = registry.byNode(target);
91 const w = registry.byNode(target);
114 if (w) {
92 if (w) {
115 w.destroyRecursive();
93 w.destroyRecursive();
116 } else {
94 } else {
117 registry.findWidgets(target).forEach(destroy);
95 emptyNode(target);
118 dom.destroy(target);
96 const parent = target.parentNode;
97 if (parent)
98 parent.removeChild(target);
119 }
99 }
120 }
100 }
121 }
101 }
122 }
102 }
123
103
124 /** Empties a content of the specified node and destroys all contained widgets.
104 /** Empties a content of the specified node and destroys all contained widgets.
125 *
105 *
126 * @param target DOM node to .
106 * @param target DOM node to empty.
127 */
107 */
128 export function emptyNode(target: Node) {
108 export const emptyNode = (target: Node) => {
129 registry.findWidgets(target).forEach(destroy);
109 registry.findWidgets(target).forEach(destroy);
130 dom.empty(target);
110
111 for(let c; c = target.lastChild;){ // intentional assignment
112 target.removeChild(c);
113 }
131 }
114 }
132
115
133 /** This function starts all widgets inside the DOM node if the target is a node
116 /** This function starts all widgets inside the DOM node if the target is a node
134 * or starts widget itself if the target is the widget. If the specified node
117 * or starts widget itself if the target is the widget. If the specified node
135 * associated with the widget that widget will be started.
118 * associated with the widget that widget will be started.
136 *
119 *
137 * @param target DOM node to find and start widgets or the widget itself.
120 * @param target DOM node to find and start widgets or the widget itself.
138 */
121 */
139 export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) {
122 export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => {
140 if (isNode(target)) {
123 if (isNode(target)) {
141 if (isElementNode(target)) {
124 if (isElementNode(target)) {
142 const w = registry.byNode(target);
125 const w = registry.byNode(target);
143 if (w) {
126 if (w) {
144 if (w.startup)
127 if (w.startup)
145 w.startup();
128 w.startup();
146 } else {
129 } else {
147 registry.findWidgets(target, skipNode).forEach(x => x.startup());
130 registry.findWidgets(target, skipNode).forEach(x => x.startup());
148 }
131 }
149 }
132 }
150 } else {
133 } else {
151 if (target.startup)
134 if (target.startup)
152 target.startup();
135 target.startup();
153 }
136 }
154 }
137 }
155
138
156 export function locateNode(node: Node): DojoNodeLocation {
139 /** Places the specified DOM node at the specified location.
157 const next = node.nextSibling;
140 *
158 return next ?
141 * @param node The node which should be placed
159 [next, "before"] :
142 * @param refNodeOrId The reference node where the created
160 [node.parentNode, "last"];
143 * DOM should be placed.
144 * @param position Optional parameter, specifies the
145 * position relative to refNode. Default is "last" (i.e. last child).
146 */
147 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => {
148 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
149 if (!ref)
150 return;
151
152 const parent = ref.parentNode;
153
154 const insertBefore = (node: Node, refNode: Node | null) => parent && parent.insertBefore(node, refNode);
155
156 if (typeof position == "number") {
157 if (ref.childNodes.length <= position) {
158 ref.appendChild(node);
159 } else {
160 ref.insertBefore(node, ref.childNodes[position]);
161 }
162 } else {
163 switch (position) {
164 case "before":
165 insertBefore(node, ref);
166 break;
167 case "after":
168 insertBefore(node, ref.nextSibling);
169 break;
170 case "first":
171 insertBefore(node, parent && parent.firstChild);
172 break;
173 case "last":
174 insertBefore(node, null);
175 break;
176 case "only":
177 emptyNode(ref);
178 ref.appendChild(node);
179 break;
180 case "replace":
181 if (parent)
182 parent.replaceChild(node, ref);
183 destroy(ref);
184 break;
185 }
186 }
161 }
187 }
162
188
163 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition) => {
189 /** Collects nodes from collection to an array.
164 const collect = (collection: HTMLCollection) => {
190 *
191 * @param collection The collection of nodes.
192 * @returns The array of nodes.
193 */
194 export const collectNodes = (collection: HTMLCollection) => {
165 const items = [];
195 const items = [];
166 for (let i = 0, n = collection.length; i < n; i++) {
196 for (let i = 0, n = collection.length; i < n; i++) {
167 items.push(collection[i]);
197 items.push(collection[i]);
168 }
198 }
169 return items;
199 return items;
170 };
200 };
171
201
172 const startup = (node: Node) => {
202 /** Starts widgets if the node contained in the document or in the started widget.
203 *
204 * @param node The node to start.
205 */
206 export const autostartWidgets = (node: Node) => {
173 if (node.parentNode) {
207 if (node.parentNode) {
174 const parentWidget = registry.getEnclosingWidget(node.parentNode);
208 const parentWidget = registry.getEnclosingWidget(node.parentNode);
175 if (parentWidget && parentWidget._started)
209 if (parentWidget && parentWidget._started)
176 return startupWidgets(node);
210 return startupWidgets(node);
177 }
211 }
178 if (isInPage(node))
212 if (isInPage(node))
179 startupWidgets(node);
213 startupWidgets(node);
180 };
214 }; No newline at end of file
181
182 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
183 if (!ref)
184 return;
185
186 const parent = ref.parentNode;
187
188 if (typeof position == "number") {
189
190 } else {
191 switch(position) {
192 case "before":
193 if (parent)
194 parent.insertBefore(node,ref);
195 case "after":
196 if (parent)
197 parent.insertBefore(node, ref.nextSibling);
198 }
199 }
200
201 const startupPending = isDocumentFragmentNode(node) ? collect(node.children) : [node];
202
203 dom.place(node, refNodeOrId, position);
204
205 startupPending.forEach(startup);
206 } No newline at end of file
@@ -1,68 +1,80
1 /// <reference path="./css.d.ts"/>
1 /// <reference path="./css.d.ts"/>
2
2
3 declare namespace JSX {
3 declare namespace JSX {
4
4
5 interface DjxIntrinsicAttributes {
5 type Ref<T> = (value: T) => void;
6
7 interface DjxIntrinsicAttributes<E> {
6 /** alias for className */
8 /** alias for className */
7 class: string;
9 class: string;
8
10
9 /** 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
10 * reference to the current object will be stored
12 * reference to the current object will be stored
11 */
13 */
12 "data-dojo-attach-point": string;
14 "data-dojo-attach-point": string;
13
15
14 /** specifies handlers map for the events */
16 /** specifies handlers map for the events */
15 "data-dojo-attach-event": string;
17 "data-dojo-attach-event": string;
16
18
19 ref: Ref<E>;
20
17 /** @deprecated */
21 /** @deprecated */
18 [attr: string]: any;
22 [attr: string]: any;
19 }
23 }
20
24
21 interface DjxIntrinsicElements {
25 interface DjxIntrinsicElements {
22 }
26 }
23
27
24 type RecursivePartial<T> = T extends string | number | boolean | null | undefined | Function ?
28 type RecursivePartial<T> = T extends string | number | boolean | null | undefined | Function ?
25 T :
29 T :
26 { [k in keyof T]?: RecursivePartial<T[k]> };
30 { [k in keyof T]?: RecursivePartial<T[k]> };
27
31
28 type MatchingMemberKeys<T, U> = {
32 type MatchingMemberKeys<T, U> = {
29 [K in keyof T]: T[K] extends U ? K : never;
33 [K in keyof T]: T[K] extends U ? K : never;
30 }[keyof T];
34 }[keyof T];
31 type NotMatchingMemberKeys<T, U> = {
35 type NotMatchingMemberKeys<T, U> = {
32 [K in keyof T]: T[K] extends U ? never : K;
36 [K in keyof T]: T[K] extends U ? never : K;
33 }[keyof T];
37 }[keyof T];
34
38
35 type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>;
39 type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>;
36
40
37 type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>;
41 type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>;
38
42
39 type ElementAttrNames<E> = NotMatchingMemberKeys<E, (...args: any[]) => any>;
43 type ElementAttrNames<E> = NotMatchingMemberKeys<E, (...args: any[]) => any>;
40
44
41 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;
42
46
43
47
44 type ElementAttrNamesBlacklist = "children" | "getRootNode" | keyof EventTarget;
48 type ElementAttrNamesBlacklist = "children" | "getRootNode" | keyof EventTarget;
45
49
46 /** 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:
47 * 1. skips all ElementAttrNamesBlacklist
51 * 1. skips all ElementAttrNamesBlacklist
48 * 2. skips all methods except with the signature of event handlers
52 * 2. skips all methods except with the signature of event handlers
49 */
53 */
50 type AssignableElementAttrNames<E> = {
54 type AssignableElementAttrNames<E> = {
51 [K in keyof E]: K extends ElementAttrNamesBlacklist ? never :
55 [K in keyof E]: K extends ElementAttrNamesBlacklist ? never :
52 ((evt: Event) => any) extends E[K] ? K :
56 ((evt: Event) => any) extends E[K] ? K :
53 E[K] extends ((...args: any[]) => any) ? never :
57 E[K] extends ((...args: any[]) => any) ? never :
54 K;
58 K;
55 }[keyof E];
59 }[keyof E];
56
60
57 type LaxElement<E extends object> =
61 type LaxElement<E extends object> =
58 Pick<E, AssignableElementAttrNames<E>> &
62 Pick<E, AssignableElementAttrNames<E>> &
59 DjxIntrinsicAttributes;
63 DjxIntrinsicAttributes<E>;
60
64
61 type LaxIntrinsicElementsMap = {
65 type LaxIntrinsicElementsMap = {
62 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
66 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
63 } & DjxIntrinsicElements;
67 } & DjxIntrinsicElements;
64
68
65 type IntrinsicElements = {
69 type IntrinsicElements = {
66 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
70 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
67 }
71 }
72
73 interface ElementChildrenAttribute {
74 children: {};
68 }
75 }
76
77 interface IntrinsicClassAttributes<T> {
78 ref: (value: T) => void;
79 }
80 }
@@ -1,70 +1,70
1 import { djbase, djclass, bind, prototype, AbstractConstructor } from "../declare";
1 import { djbase, djclass, bind, prototype, AbstractConstructor } from "../declare";
2
2
3 import { DjxWidgetBase } from "../tsx/DjxWidgetBase";
3 import { DjxWidgetBase } from "../tsx/DjxWidgetBase";
4 import { createElement, on } from "../tsx";
4 import { createElement, on } from "../tsx";
5
5
6 interface MyWidgetAttrs {
6 interface MyWidgetAttrs {
7 title: string;
7 title: string;
8
8
9 counter: number;
9 counter: number;
10 }
10 }
11
11
12 interface MyWidgetEvents {
12 interface MyWidgetEvents {
13 "count-inc": Event & {
13 "count-inc": Event & {
14 detail: number;
14 detail: number;
15 };
15 };
16
16
17 "count-dec": Event & {
17 "count-dec": Event & {
18 detail: number;
18 detail: number;
19 };
19 };
20 }
20 }
21
21
22
22
23 @djclass
23 @djclass
24 export class MyWidget extends djbase(DjxWidgetBase as AbstractConstructor<DjxWidgetBase<MyWidgetAttrs, MyWidgetEvents>>) {
24 export class MyWidget extends djbase(DjxWidgetBase as AbstractConstructor<DjxWidgetBase<MyWidgetAttrs, MyWidgetEvents>>) {
25
25
26 @bind({ node: "titleNode", type: "innerHTML" })
26 @bind({ node: "titleNode", type: "innerHTML" })
27 title = "";
27 title = "";
28
28
29 @prototype()
29 @prototype()
30 counter = 0;
30 counter = 0;
31
31
32 render() {
32 render() {
33 const Frame = (props: any) => <div>{props.children}</div>;
33 const Frame = ({children, ref}: {ref: JSX.Ref<HTMLDivElement>, children: any[]}) => <div ref={ref} >{children}</div>;
34 return <div className="myWidget" onsubmit={e => this._onSubmit(e)} tabIndex={3} style={{ alignContent: "center", border: "1px solid" }} >
34 return <div className="myWidget" onsubmit={e => this._onSubmit(e)} tabIndex={3} style={{ alignContent: "center", border: "1px solid" }} >
35 <h1 data-dojo-attach-point="titleNode"></h1>
35 <h1 data-dojo-attach-point="titleNode"></h1>
36 <Frame>
36 <Frame ref={ v => {}}>
37 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
37 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
38 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
38 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
39 </Frame>
39 </Frame>
40 </div>;
40 </div>;
41 }
41 }
42
42
43 postCreate() {
43 postCreate() {
44 super.postCreate();
44 super.postCreate();
45
45
46 this.on("click", () => {});
46 this.on("click", () => {});
47 }
47 }
48
48
49 _onSubmit(e: Event) {
49 _onSubmit(e: Event) {
50 }
50 }
51
51
52 _onIncClick(e: MouseEvent) {
52 _onIncClick(e: MouseEvent) {
53 this.set("counter", this.counter + 1);
53 this.set("counter", this.counter + 1);
54
54
55 this.emit("count-inc", { bubbles: false });
55 this.emit("count-inc", { bubbles: false });
56 }
56 }
57
57
58 _onDecClick() {
58 _onDecClick() {
59 this.emit("count-dec", { bubbles: false, detail: this.counter });
59 this.emit("count-dec", { bubbles: false, detail: this.counter });
60 }
60 }
61
61
62 @on("count-inc")
62 @on("count-inc")
63 private _onCounterInc(evt: Event & { detail: number; x?: number; }) {
63 private _onCounterInc(evt: Event & { detail: number; x?: number; }) {
64 }
64 }
65
65
66 @on("click", "keydown")
66 @on("click", "keydown")
67 protected _onClick(event: MouseEvent | KeyboardEvent) {
67 protected _onClick(event: MouseEvent | KeyboardEvent) {
68
68
69 }
69 }
70 } No newline at end of file
70 }
General Comments 0
You need to be logged in to leave comments. Login now