##// 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 1 import { id as mid } from "module";
2 2 import { TraceSource } from "@implab/core-amd/log/TraceSource";
3 3 import { MapOf } from "@implab/core-amd/interfaces";
4 4 import { mixin } from "@implab/core-amd/safe";
5 5
6 6 const trace = TraceSource.get(mid);
7 7
8 8
9 9 function on<T extends keyof HTMLElementEventMap>(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any): () => void {
10 10 // Add an event listener to a DOM node
11 11 node.addEventListener(eventName, handler, false);
12 12
13 13 return () => node.removeEventListener(eventName, handler, false);
14 14 }
15 15
16 16 interface NodeLoadResult {
17 17 node: HTMLElement;
18 18 }
19 19
20 20 class DomInject {
21 21 injectionPoint?: HTMLElement;
22 22 injectAfter?: HTMLElement;
23 23
24 24 _map: MapOf<Promise<NodeLoadResult>> = {};
25 25
26 26 _inject<T extends keyof HTMLElementTagNameMap>(name: T, attr: Partial<HTMLElementTagNameMap[T]>) {
27 27 const node = document.createElement(name);
28 28
29 29 return new Promise<NodeLoadResult>((ok, fail) => {
30 30
31 31 const cleanup = () => {
32 32 noerr();
33 33 noload();
34 34 };
35 35
36 36 const noload = on(node, "load", () => {
37 37 ok({ node });
38 38 cleanup();
39 39 });
40 40
41 41 const noerr = on(node, "error", e => {
42 42 fail({
43 erorr: e,
43 error: e,
44 44 node
45 45 });
46 46 cleanup();
47 47 });
48 48
49 49 mixin(node, attr);
50 50
51 51 const _injectionPoint = this.injectionPoint || document.head;
52 52 const _injectBefore = this.injectAfter ? this.injectAfter.nextSibling : null;
53 53
54 54 _injectionPoint.insertBefore(node, _injectBefore);
55 55 });
56 56 }
57 57
58 58 async injectScript(url: string) {
59 59 let d = this._map[url];
60 60 if (!d) {
61 61 trace.log("js {0}", url);
62 62 d = this._inject("script", {
63 63 type: "text/javascript",
64 64 charset: "utf-8",
65 65 src: url
66 66 });
67 67 this._map[url] = d;
68 68 }
69 69 try {
70 70 await d;
71 71 trace.log("done {0}", url);
72 72 } catch (e) {
73 73 trace.error("failed {0}: {1}", url, e);
74 74 throw e;
75 75 }
76 76 }
77 77
78 78 async injectStylesheet(url: string) {
79 79 let d = this._map[url];
80 80 if (!d) {
81 81 trace.log("js {0}", url);
82 82 d = this._inject("link", {
83 83 type: "text/css",
84 84 rel: "stylesheet",
85 85 href: url
86 86 });
87 87 this._map[url] = d;
88 88 }
89 89 try {
90 90 await d;
91 91 trace.log("done {0}", url);
92 92 } catch (e) {
93 93 trace.error("failed {0}: {1}", url, e);
94 94 throw e;
95 95 }
96 96 }
97 97 };
98 98
99 99 const instance = new DomInject();
100 100 export default instance;
@@ -1,69 +1,77
1 1 import { djbase, djclass } from "../declare";
2 2 import _WidgetBase = require("dijit/_WidgetBase");
3 3 import _AttachMixin = require("dijit/_AttachMixin");
4 import { BuildContext, isNode } from "./traits";
4 import { BuildContext, isNode, startupWidgets } from "./traits";
5 5 import registry = require("dijit/registry");
6 6
7 7 // type Handle = dojo.Handle;
8 8
9 9 export interface EventArgs {
10 10 bubbles?: boolean;
11 11
12 12 cancelable?: boolean;
13 13
14 14 composed?: boolean;
15 15 }
16 16
17 17 export interface DjxWidgetBase<Attrs = any, Events extends { [name in keyof Events]: Event } = any> {
18 18 set<K extends keyof Attrs & string>(key: K, value: Attrs[K]): this;
19 19 set(props: Partial<Attrs>): this;
20 20 get<K extends keyof Attrs & string>(key: K): Attrs[K];
21 21
22 22 on<K extends keyof Events & string>(eventName: K, cb: (evt: Events[K]) => void): dojo.WatchHandle;
23 23
24 24 emit<K extends keyof Events & string>(eventName: K, evt: Omit<Events[K], keyof Event> & EventArgs ): void;
25 25 }
26 26
27 27 @djclass
28 28 export abstract class DjxWidgetBase<Attrs = any, Events = any> extends djbase(_WidgetBase, _AttachMixin) {
29 29
30 30 buildRendering() {
31 31 this.domNode = this.render().getDomNode();
32 32 super.buildRendering();
33 33 }
34 34
35 35 abstract render(): BuildContext<HTMLElement>;
36 36
37 37 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
38 38 baseNode: T,
39 39 getAttrFunc: (baseNode: T, attr: string) => string,
40 40 // tslint:disable-next-line: ban-types
41 41 attachFunc: (node: T, type: string, func?: Function) => dojo.Handle
42 42 ): boolean {
43 43 if (isNode(baseNode)) {
44 44 const w = registry.byNode(baseNode);
45 45 if (w) {
46 46 // from dijit/_WidgetsInTemplateMixin
47 47 this._processTemplateNode(w,
48 48 (n, p) => n.get(p), // callback to get a property of a widget
49 49 (widget, type, callback) => {
50 50 if (!callback)
51 51 throw new Error("The callback must be specified");
52 52
53 53 // callback to do data-dojo-attach-event to a widget
54 54 if (type in widget) {
55 55 // back-compat, remove for 2.0
56 56 return widget.connect(widget, type, callback as EventListener);
57 57 } else {
58 58 // 1.x may never hit this branch, but it's the default for 2.0
59 59 return widget.on(type, callback);
60 60 }
61 61
62 62 });
63 63 // don't process widgets internals
64 64 return false;
65 65 }
66 66 }
67 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();
76 }
69 77 }
@@ -1,39 +1,37
1 1 import dom = require("dojo/dom-construct");
2 import attr = require("dojo/dom-attr");
3 2 import { argumentNotEmptyString } from "@implab/core-amd/safe";
4 3 import { BuildContextBase } from "./BuildContextBase";
5 import registry = require("dijit/registry");
6 4
7 5 export class HtmlElementContext extends BuildContextBase<HTMLElement> {
8 6 elementType: string;
9 7
10 8 _element: HTMLElement | undefined;
11 9
12 10 constructor(elementType: string) {
13 11 argumentNotEmptyString(elementType, "elementType");
14 12 super();
15 13
16 14 this.elementType = elementType;
17 15 }
18 16
19 17 _addChild(child: any): void {
20 18 if (!this._element)
21 19 throw new Error("The HTML element isn't created");
22 20 dom.place(this.getChildDom(child), this._element);
23 21 }
24 22
25 23 _create(attrs: object, children: any[]) {
26 24 this._element = dom.create(this.elementType, attrs);
27 25
28 26 if (children)
29 27 children.forEach(v => this._addChild(v));
30 28 }
31 29
32 30 _getDomNode() {
33 31 if (!this._element)
34 32 throw new Error("The HTML element isn't created");
35 33
36 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 3 import _WidgetBase = require("dijit/_WidgetBase");
4 import registry = require("dijit/registry");
5 import dom = require("dojo/dom-construct");
2 6
3 7 type _WidgetBaseConstructor = typeof _WidgetBase;
4 8
5 9 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
6 10
7 11 export interface BuildContext<TNode extends Node = Node> {
8 12 getDomNode(): TNode;
9 13
10 14 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
11 15 }
12 16
17 export interface IRecursivelyDestroyable {
18 destroyRecursive(): void;
19 }
20
13 21 export function isNode(el: any): el is Node {
14 22 return el && el.nodeName && el.nodeType;
15 23 }
16 24
17 25 export function isElementNode(el: any): el is Element {
18 26 return isNode(el) && el.nodeType === 1;
19 27 }
20 28
21 29 export function isTextNode(el: any): el is Text {
22 30 return isNode(el) && el.nodeType === 3;
23 31 }
24 32
25 33 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
26 34 return isNode(el) && el.nodeType === 7;
27 35 }
28 36
29 37 export function isCommentNode(el: any): el is Comment {
30 38 return isNode(el) && el.nodeType === 8;
31 39 }
32 40
33 41 export function isDocumentNode(el: any): el is Document {
34 42 return isNode(el) && el.nodeType === 9;
35 43 }
36 44
37 45 export function isDocumentTypeNode(el: any): el is DocumentType {
38 46 return isNode(el) && el.nodeType === 10;
39 47 }
40 48
41 49 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
42 50 return isNode(el) && el.nodeType === 11;
43 51 }
44 52
45 53 export function isWidget(v: any): v is _WidgetBase {
46 54 return v && "domNode" in v;
47 55 }
48 56
49 57 export function isBuildContext(v: any): v is BuildContext {
50 58 return typeof v === "object" && typeof v.getDomElement === "function";
51 59 }
52 60
53 61 export function isPlainObject(v: object) {
54 62 if (typeof v !== "object")
55 63 return false;
56 64
57 65 const vp = Object.getPrototypeOf(v);
58 66 return !vp || vp === Object.prototype;
59 67 }
60 68
61 69 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
62 70 return typeof v === "function" && v.prototype && (
63 71 "domNode" in v.prototype ||
64 72 "buildRendering" in v.prototype
65 73 );
66 74 }
67 75
68 76 /** Tests whether the specified node is placed in visible dom.
69 77 * @param {Node} node The node to test
70 78 */
71 79 export function isInPage(node: Node) {
72 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 1 /// <reference path="./css.d.ts"/>
2 2 /// <reference path="./dijit.d.ts"/>
3 3
4 4 declare namespace JSX {
5 5
6 6 interface DjxIntrinsicAttributes {
7 7 /** alias for className */
8 8 class: string;
9 9
10 10 /** specifies the name of the property in the widget where the the
11 11 * reference to the current object will be stored
12 12 */
13 13 "data-dojo-attach-point": string;
14 14
15 15 /** specifies handlers map for the events */
16 16 "data-dojo-attach-event": string;
17
17
18 /** @deprecated */
18 19 [attr: string]: any;
19 20 }
20 21
21 22 interface DjxIntrinsicElements {
22 23 }
23 24
24 25 type RecursivePartial<T> = T extends string | number | boolean | null | undefined | Function ?
25 26 T :
26 27 { [k in keyof T]?: RecursivePartial<T[k]> };
27 28
28 29 type MatchingMemberKeys<T, U> = {
29 30 [K in keyof T]: T[K] extends U ? K : never;
30 31 }[keyof T];
31 32 type NotMatchingMemberKeys<T, U> = {
32 33 [K in keyof T]: T[K] extends U ? never : K;
33 34 }[keyof T];
34 35
35 36 type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>;
36 37
37 38 type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>;
38 39
39 40 type ElementAttrNames<E> = NotMatchingMemberKeys<E, (...args: any[]) => any>;
40 41
41 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 62 type LaxIntrinsicElementsMap = {
46 63 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
47 64 } & DjxIntrinsicElements;
48 65
49 66 type IntrinsicElements = {
50 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