##// END OF EJS Templates
Fixed startup of DjxWidgetBase to start inner widgets...
cin -
r52:0b9593714536 default
parent child
Show More
@@ -1,100 +1,100
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 { MapOf } from "@implab/core-amd/interfaces";
3 import { MapOf } from "@implab/core-amd/interfaces";
4 import { mixin } from "@implab/core-amd/safe";
4 import { mixin } from "@implab/core-amd/safe";
5
5
6 const trace = TraceSource.get(mid);
6 const trace = TraceSource.get(mid);
7
7
8
8
9 function on<T extends keyof HTMLElementEventMap>(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any): () => void {
9 function on<T extends keyof HTMLElementEventMap>(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any): () => void {
10 // Add an event listener to a DOM node
10 // Add an event listener to a DOM node
11 node.addEventListener(eventName, handler, false);
11 node.addEventListener(eventName, handler, false);
12
12
13 return () => node.removeEventListener(eventName, handler, false);
13 return () => node.removeEventListener(eventName, handler, false);
14 }
14 }
15
15
16 interface NodeLoadResult {
16 interface NodeLoadResult {
17 node: HTMLElement;
17 node: HTMLElement;
18 }
18 }
19
19
20 class DomInject {
20 class DomInject {
21 injectionPoint?: HTMLElement;
21 injectionPoint?: HTMLElement;
22 injectAfter?: HTMLElement;
22 injectAfter?: HTMLElement;
23
23
24 _map: MapOf<Promise<NodeLoadResult>> = {};
24 _map: MapOf<Promise<NodeLoadResult>> = {};
25
25
26 _inject<T extends keyof HTMLElementTagNameMap>(name: T, attr: Partial<HTMLElementTagNameMap[T]>) {
26 _inject<T extends keyof HTMLElementTagNameMap>(name: T, attr: Partial<HTMLElementTagNameMap[T]>) {
27 const node = document.createElement(name);
27 const node = document.createElement(name);
28
28
29 return new Promise<NodeLoadResult>((ok, fail) => {
29 return new Promise<NodeLoadResult>((ok, fail) => {
30
30
31 const cleanup = () => {
31 const cleanup = () => {
32 noerr();
32 noerr();
33 noload();
33 noload();
34 };
34 };
35
35
36 const noload = on(node, "load", () => {
36 const noload = on(node, "load", () => {
37 ok({ node });
37 ok({ node });
38 cleanup();
38 cleanup();
39 });
39 });
40
40
41 const noerr = on(node, "error", e => {
41 const noerr = on(node, "error", e => {
42 fail({
42 fail({
43 erorr: e,
43 error: e,
44 node
44 node
45 });
45 });
46 cleanup();
46 cleanup();
47 });
47 });
48
48
49 mixin(node, attr);
49 mixin(node, attr);
50
50
51 const _injectionPoint = this.injectionPoint || document.head;
51 const _injectionPoint = this.injectionPoint || document.head;
52 const _injectBefore = this.injectAfter ? this.injectAfter.nextSibling : null;
52 const _injectBefore = this.injectAfter ? this.injectAfter.nextSibling : null;
53
53
54 _injectionPoint.insertBefore(node, _injectBefore);
54 _injectionPoint.insertBefore(node, _injectBefore);
55 });
55 });
56 }
56 }
57
57
58 async injectScript(url: string) {
58 async injectScript(url: string) {
59 let d = this._map[url];
59 let d = this._map[url];
60 if (!d) {
60 if (!d) {
61 trace.log("js {0}", url);
61 trace.log("js {0}", url);
62 d = this._inject("script", {
62 d = this._inject("script", {
63 type: "text/javascript",
63 type: "text/javascript",
64 charset: "utf-8",
64 charset: "utf-8",
65 src: url
65 src: url
66 });
66 });
67 this._map[url] = d;
67 this._map[url] = d;
68 }
68 }
69 try {
69 try {
70 await d;
70 await d;
71 trace.log("done {0}", url);
71 trace.log("done {0}", url);
72 } catch (e) {
72 } catch (e) {
73 trace.error("failed {0}: {1}", url, e);
73 trace.error("failed {0}: {1}", url, e);
74 throw e;
74 throw e;
75 }
75 }
76 }
76 }
77
77
78 async injectStylesheet(url: string) {
78 async injectStylesheet(url: string) {
79 let d = this._map[url];
79 let d = this._map[url];
80 if (!d) {
80 if (!d) {
81 trace.log("js {0}", url);
81 trace.log("js {0}", url);
82 d = this._inject("link", {
82 d = this._inject("link", {
83 type: "text/css",
83 type: "text/css",
84 rel: "stylesheet",
84 rel: "stylesheet",
85 href: url
85 href: url
86 });
86 });
87 this._map[url] = d;
87 this._map[url] = d;
88 }
88 }
89 try {
89 try {
90 await d;
90 await d;
91 trace.log("done {0}", url);
91 trace.log("done {0}", url);
92 } catch (e) {
92 } catch (e) {
93 trace.error("failed {0}: {1}", url, e);
93 trace.error("failed {0}: {1}", url, e);
94 throw e;
94 throw e;
95 }
95 }
96 }
96 }
97 };
97 };
98
98
99 const instance = new DomInject();
99 const instance = new DomInject();
100 export default instance;
100 export default instance;
@@ -1,69 +1,77
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 { BuildContext, isNode } from "./traits";
4 import { BuildContext, isNode, startupWidgets } from "./traits";
5 import registry = require("dijit/registry");
5 import registry = require("dijit/registry");
6
6
7 // type Handle = dojo.Handle;
7 // type Handle = dojo.Handle;
8
8
9 export interface EventArgs {
9 export interface EventArgs {
10 bubbles?: boolean;
10 bubbles?: boolean;
11
11
12 cancelable?: boolean;
12 cancelable?: boolean;
13
13
14 composed?: boolean;
14 composed?: boolean;
15 }
15 }
16
16
17 export interface DjxWidgetBase<Attrs = any, Events extends { [name in keyof Events]: Event } = any> {
17 export interface DjxWidgetBase<Attrs = any, Events extends { [name in keyof Events]: Event } = any> {
18 set<K extends keyof Attrs & string>(key: K, value: Attrs[K]): this;
18 set<K extends keyof Attrs & string>(key: K, value: Attrs[K]): this;
19 set(props: Partial<Attrs>): this;
19 set(props: Partial<Attrs>): this;
20 get<K extends keyof Attrs & string>(key: K): Attrs[K];
20 get<K extends keyof Attrs & string>(key: K): Attrs[K];
21
21
22 on<K extends keyof Events & string>(eventName: K, cb: (evt: Events[K]) => void): dojo.WatchHandle;
22 on<K extends keyof Events & string>(eventName: K, cb: (evt: Events[K]) => void): dojo.WatchHandle;
23
23
24 emit<K extends keyof Events & string>(eventName: K, evt: Omit<Events[K], keyof Event> & EventArgs ): void;
24 emit<K extends keyof Events & string>(eventName: K, evt: Omit<Events[K], keyof Event> & EventArgs ): void;
25 }
25 }
26
26
27 @djclass
27 @djclass
28 export abstract class DjxWidgetBase<Attrs = any, Events = any> extends djbase(_WidgetBase, _AttachMixin) {
28 export abstract class DjxWidgetBase<Attrs = any, Events = any> extends djbase(_WidgetBase, _AttachMixin) {
29
29
30 buildRendering() {
30 buildRendering() {
31 this.domNode = this.render().getDomNode();
31 this.domNode = this.render().getDomNode();
32 super.buildRendering();
32 super.buildRendering();
33 }
33 }
34
34
35 abstract render(): BuildContext<HTMLElement>;
35 abstract render(): BuildContext<HTMLElement>;
36
36
37 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
37 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
38 baseNode: T,
38 baseNode: T,
39 getAttrFunc: (baseNode: T, attr: string) => string,
39 getAttrFunc: (baseNode: T, attr: string) => string,
40 // tslint:disable-next-line: ban-types
40 // tslint:disable-next-line: ban-types
41 attachFunc: (node: T, type: string, func?: Function) => dojo.Handle
41 attachFunc: (node: T, type: string, func?: Function) => dojo.Handle
42 ): boolean {
42 ): boolean {
43 if (isNode(baseNode)) {
43 if (isNode(baseNode)) {
44 const w = registry.byNode(baseNode);
44 const w = registry.byNode(baseNode);
45 if (w) {
45 if (w) {
46 // from dijit/_WidgetsInTemplateMixin
46 // from dijit/_WidgetsInTemplateMixin
47 this._processTemplateNode(w,
47 this._processTemplateNode(w,
48 (n, p) => n.get(p), // callback to get a property of a widget
48 (n, p) => n.get(p), // callback to get a property of a widget
49 (widget, type, callback) => {
49 (widget, type, callback) => {
50 if (!callback)
50 if (!callback)
51 throw new Error("The callback must be specified");
51 throw new Error("The callback must be specified");
52
52
53 // callback to do data-dojo-attach-event to a widget
53 // callback to do data-dojo-attach-event to a widget
54 if (type in widget) {
54 if (type in widget) {
55 // back-compat, remove for 2.0
55 // back-compat, remove for 2.0
56 return widget.connect(widget, type, callback as EventListener);
56 return widget.connect(widget, type, callback as EventListener);
57 } else {
57 } else {
58 // 1.x may never hit this branch, but it's the default for 2.0
58 // 1.x may never hit this branch, but it's the default for 2.0
59 return widget.on(type, callback);
59 return widget.on(type, callback);
60 }
60 }
61
61
62 });
62 });
63 // don't process widgets internals
63 // don't process widgets internals
64 return false;
64 return false;
65 }
65 }
66 }
66 }
67 return super._processTemplateNode(baseNode, getAttrFunc, attachFunc);
67 return super._processTemplateNode(baseNode, getAttrFunc, attachFunc);
68 }
68 }
69
70 /** Starts current widget and all its supporting widgets (placed outside
71 * `containerNode`) and child widgets (placed inside `containerNode`)*/
72 startup() {
73 // startup supporting widgets
74 startupWidgets(this.domNode, this.containerNode);
75 super.startup();
69 }
76 }
77 }
@@ -1,39 +1,37
1 import dom = require("dojo/dom-construct");
1 import dom = require("dojo/dom-construct");
2 import attr = require("dojo/dom-attr");
3 import { argumentNotEmptyString } from "@implab/core-amd/safe";
2 import { argumentNotEmptyString } from "@implab/core-amd/safe";
4 import { BuildContextBase } from "./BuildContextBase";
3 import { BuildContextBase } from "./BuildContextBase";
5 import registry = require("dijit/registry");
6
4
7 export class HtmlElementContext extends BuildContextBase<HTMLElement> {
5 export class HtmlElementContext extends BuildContextBase<HTMLElement> {
8 elementType: string;
6 elementType: string;
9
7
10 _element: HTMLElement | undefined;
8 _element: HTMLElement | undefined;
11
9
12 constructor(elementType: string) {
10 constructor(elementType: string) {
13 argumentNotEmptyString(elementType, "elementType");
11 argumentNotEmptyString(elementType, "elementType");
14 super();
12 super();
15
13
16 this.elementType = elementType;
14 this.elementType = elementType;
17 }
15 }
18
16
19 _addChild(child: any): void {
17 _addChild(child: any): void {
20 if (!this._element)
18 if (!this._element)
21 throw new Error("The HTML element isn't created");
19 throw new Error("The HTML element isn't created");
22 dom.place(this.getChildDom(child), this._element);
20 dom.place(this.getChildDom(child), this._element);
23 }
21 }
24
22
25 _create(attrs: object, children: any[]) {
23 _create(attrs: object, children: any[]) {
26 this._element = dom.create(this.elementType, attrs);
24 this._element = dom.create(this.elementType, attrs);
27
25
28 if (children)
26 if (children)
29 children.forEach(v => this._addChild(v));
27 children.forEach(v => this._addChild(v));
30 }
28 }
31
29
32 _getDomNode() {
30 _getDomNode() {
33 if (!this._element)
31 if (!this._element)
34 throw new Error("The HTML element isn't created");
32 throw new Error("The HTML element isn't created");
35
33
36 return this._element;
34 return this._element;
37 }
35 }
38
36
39 }
37 }
@@ -1,73 +1,130
1 import { IDestroyable } from "@implab/core-amd/interfaces";
2 import { isDestroyable } from "@implab/core-amd/safe";
1 import _WidgetBase = require("dijit/_WidgetBase");
3 import _WidgetBase = require("dijit/_WidgetBase");
4 import registry = require("dijit/registry");
5 import dom = require("dojo/dom-construct");
2
6
3 type _WidgetBaseConstructor = typeof _WidgetBase;
7 type _WidgetBaseConstructor = typeof _WidgetBase;
4
8
5 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
9 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
6
10
7 export interface BuildContext<TNode extends Node = Node> {
11 export interface BuildContext<TNode extends Node = Node> {
8 getDomNode(): TNode;
12 getDomNode(): TNode;
9
13
10 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
14 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
11 }
15 }
12
16
17 export interface IRecursivelyDestroyable {
18 destroyRecursive(): void;
19 }
20
13 export function isNode(el: any): el is Node {
21 export function isNode(el: any): el is Node {
14 return el && el.nodeName && el.nodeType;
22 return el && el.nodeName && el.nodeType;
15 }
23 }
16
24
17 export function isElementNode(el: any): el is Element {
25 export function isElementNode(el: any): el is Element {
18 return isNode(el) && el.nodeType === 1;
26 return isNode(el) && el.nodeType === 1;
19 }
27 }
20
28
21 export function isTextNode(el: any): el is Text {
29 export function isTextNode(el: any): el is Text {
22 return isNode(el) && el.nodeType === 3;
30 return isNode(el) && el.nodeType === 3;
23 }
31 }
24
32
25 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
33 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
26 return isNode(el) && el.nodeType === 7;
34 return isNode(el) && el.nodeType === 7;
27 }
35 }
28
36
29 export function isCommentNode(el: any): el is Comment {
37 export function isCommentNode(el: any): el is Comment {
30 return isNode(el) && el.nodeType === 8;
38 return isNode(el) && el.nodeType === 8;
31 }
39 }
32
40
33 export function isDocumentNode(el: any): el is Document {
41 export function isDocumentNode(el: any): el is Document {
34 return isNode(el) && el.nodeType === 9;
42 return isNode(el) && el.nodeType === 9;
35 }
43 }
36
44
37 export function isDocumentTypeNode(el: any): el is DocumentType {
45 export function isDocumentTypeNode(el: any): el is DocumentType {
38 return isNode(el) && el.nodeType === 10;
46 return isNode(el) && el.nodeType === 10;
39 }
47 }
40
48
41 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
49 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
42 return isNode(el) && el.nodeType === 11;
50 return isNode(el) && el.nodeType === 11;
43 }
51 }
44
52
45 export function isWidget(v: any): v is _WidgetBase {
53 export function isWidget(v: any): v is _WidgetBase {
46 return v && "domNode" in v;
54 return v && "domNode" in v;
47 }
55 }
48
56
49 export function isBuildContext(v: any): v is BuildContext {
57 export function isBuildContext(v: any): v is BuildContext {
50 return typeof v === "object" && typeof v.getDomElement === "function";
58 return typeof v === "object" && typeof v.getDomElement === "function";
51 }
59 }
52
60
53 export function isPlainObject(v: object) {
61 export function isPlainObject(v: object) {
54 if (typeof v !== "object")
62 if (typeof v !== "object")
55 return false;
63 return false;
56
64
57 const vp = Object.getPrototypeOf(v);
65 const vp = Object.getPrototypeOf(v);
58 return !vp || vp === Object.prototype;
66 return !vp || vp === Object.prototype;
59 }
67 }
60
68
61 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
69 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
62 return typeof v === "function" && v.prototype && (
70 return typeof v === "function" && v.prototype && (
63 "domNode" in v.prototype ||
71 "domNode" in v.prototype ||
64 "buildRendering" in v.prototype
72 "buildRendering" in v.prototype
65 );
73 );
66 }
74 }
67
75
68 /** Tests whether the specified node is placed in visible dom.
76 /** Tests whether the specified node is placed in visible dom.
69 * @param {Node} node The node to test
77 * @param {Node} node The node to test
70 */
78 */
71 export function isInPage(node: Node) {
79 export function isInPage(node: Node) {
72 return (node === document.body) ? false : document.body.contains(node);
80 return (node === document.body) ? false : document.body.contains(node);
73 }
81 }
82
83 export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable {
84 return target && typeof target.destroyRecursive === "function";
85 }
86
87
88 /** Destroys DOM Node with all contained widgets.
89 * If the specified node is the root node of a widget, then the
90 * widget will be destroyed.
91 *
92 * @param target DOM Node or widget to destroy
93 */
94 export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) {
95 if (isRecursivelyDestroyable(target)) {
96 target.destroyRecursive();
97 } else if (isDestroyable(target)) {
98 target.destroy();
99 } else if (isNode(target)) {
100 const self = registry.byNode(target);
101 if (self) {
102 self.destroyRecursive();
103 } else {
104 registry.findWidgets(target).forEach(destroy);
105 dom.destroy(target);
106 }
107 }
108 }
109
110 /** Empties a content of the specified node and destroys all contained widgets.
111 *
112 * @param target DOM node to .
113 */
114 export function emptyNode(target: Node) {
115 registry.findWidgets(target).forEach(destroy);
116 dom.empty(target);
117 }
118
119 /** This function starts all widgets inside the DOM node if the target is a node,
120 * or starts widget itself if the target is the widget.
121 *
122 * @param target DOM node to find and start widgets or the widget itself.
123 */
124 export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) {
125 if (isNode(target)) {
126 registry.findWidgets(target, skipNode).forEach(w => w.startup());
127 } else {
128 target.startup();
129 }
130 } No newline at end of file
@@ -1,52 +1,69
1 /// <reference path="./css.d.ts"/>
1 /// <reference path="./css.d.ts"/>
2 /// <reference path="./dijit.d.ts"/>
2 /// <reference path="./dijit.d.ts"/>
3
3
4 declare namespace JSX {
4 declare namespace JSX {
5
5
6 interface DjxIntrinsicAttributes {
6 interface DjxIntrinsicAttributes {
7 /** alias for className */
7 /** alias for className */
8 class: string;
8 class: string;
9
9
10 /** specifies the name of the property in the widget where the the
10 /** specifies the name of the property in the widget where the the
11 * reference to the current object will be stored
11 * reference to the current object will be stored
12 */
12 */
13 "data-dojo-attach-point": string;
13 "data-dojo-attach-point": string;
14
14
15 /** specifies handlers map for the events */
15 /** specifies handlers map for the events */
16 "data-dojo-attach-event": string;
16 "data-dojo-attach-event": string;
17
17
18 /** @deprecated */
18 [attr: string]: any;
19 [attr: string]: any;
19 }
20 }
20
21
21 interface DjxIntrinsicElements {
22 interface DjxIntrinsicElements {
22 }
23 }
23
24
24 type RecursivePartial<T> = T extends string | number | boolean | null | undefined | Function ?
25 type RecursivePartial<T> = T extends string | number | boolean | null | undefined | Function ?
25 T :
26 T :
26 { [k in keyof T]?: RecursivePartial<T[k]> };
27 { [k in keyof T]?: RecursivePartial<T[k]> };
27
28
28 type MatchingMemberKeys<T, U> = {
29 type MatchingMemberKeys<T, U> = {
29 [K in keyof T]: T[K] extends U ? K : never;
30 [K in keyof T]: T[K] extends U ? K : never;
30 }[keyof T];
31 }[keyof T];
31 type NotMatchingMemberKeys<T, U> = {
32 type NotMatchingMemberKeys<T, U> = {
32 [K in keyof T]: T[K] extends U ? never : K;
33 [K in keyof T]: T[K] extends U ? never : K;
33 }[keyof T];
34 }[keyof T];
34
35
35 type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>;
36 type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>;
36
37
37 type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>;
38 type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>;
38
39
39 type ElementAttrNames<E> = NotMatchingMemberKeys<E, (...args: any[]) => any>;
40 type ElementAttrNames<E> = NotMatchingMemberKeys<E, (...args: any[]) => any>;
40
41
41 type ElementAttrType<E, K extends keyof any> = K extends keyof E ? RecursivePartial<E[K]> : string;
42 type ElementAttrType<E, K extends keyof any> = K extends keyof E ? RecursivePartial<E[K]> : string;
42
43
43 type LaxElement<E extends object> = ExcludeMembers<Omit<E, "children">, (...args: any[]) => any> & DjxIntrinsicAttributes;
44
45 type ElementAttrNamesBlacklist = "children" | "getRootNode" | keyof EventTarget;
46
47 /** This type extracts keys of the specified parameter E by the following rule:
48 * 1. skips all ElementAttrNamesBlacklist
49 * 2. skips all methods except with the signature of event handlers
50 */
51 type AssignableElementAttrNames<E> = {
52 [K in keyof E]: K extends ElementAttrNamesBlacklist ? never :
53 ((evt: Event) => any) extends E[K] ? K :
54 E[K] extends ((...args: any[]) => any) ? never :
55 K;
56 }[keyof E];
57
58 type LaxElement<E extends object> =
59 Pick<E, AssignableElementAttrNames<E>> &
60 DjxIntrinsicAttributes;
44
61
45 type LaxIntrinsicElementsMap = {
62 type LaxIntrinsicElementsMap = {
46 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
63 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
47 } & DjxIntrinsicElements;
64 } & DjxIntrinsicElements;
48
65
49 type IntrinsicElements = {
66 type IntrinsicElements = {
50 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
67 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
51 }
68 }
52 }
69 }
General Comments 0
You need to be logged in to leave comments. Login now