##// END OF EJS Templates
Fixes in WidgetContex, added _Container.addChild support
cin -
r38:5c6c7e16919c v1.0.0-rc18 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 injectBefore?: HTMLElement;
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 43 erorr: 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 const _injectBefore = this.injectBefore || _injectionPoint.firstChild;
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,68 +1,80
1 1 import { isNull, mixin } from "@implab/core-amd/safe";
2 2 import { isPlainObject, isNode, isBuildContext, DojoNodePosition, BuildContext } from "./traits";
3 3
4 4 import dom = require("dojo/dom-construct");
5 import registry = require("dijit/registry");
5 6
6 7 export abstract class BuildContextBase<TNode extends Node> implements BuildContext<TNode> {
7 8 private _attrs = {};
8 9
9 10 private _children = new Array();
10 11
11 12 private _created: boolean = false;
12 13
13 14 visitNext(v: any) {
14 15 if (this._created)
15 16 throw new Error("The Element is already created");
16 17
17 18 if (isNull(v))
18 19 return;
19 20
20 21 if (isPlainObject(v)) {
21 22 mixin(this._attrs, v);
22 23 } else if (v instanceof Array) {
23 24 v.forEach(x => this.visitNext(x));
24 25 } else {
25 26 this._children.push(v);
26 27 }
27 28 }
28 29
29 30 getChildDom(v: any) {
30 31 const tv = typeof v;
31 32 if (tv === "string" || tv === "number" || tv === "boolean" || v instanceof RegExp || v instanceof Date) {
32 33 return document.createTextNode(v.toString());
33 34 } else if (isNode(v)) {
34 35 return v;
35 36 } else if (isBuildContext(v)) {
36 37 return v.getDomNode();
37 38 } else {
38 39 throw new Error("Invalid parameter");
39 40 }
40 41 }
41 42
42 abstract _getDomNode(): TNode;
43
44 43 ensureCreated() {
45 44 if (!this._created) {
46 45 this._create(this._attrs, this._children);
47 46 this._children = [];
48 47 this._attrs = {};
49 48 this._created = true;
50 49 }
51 50 }
52 51
53 /** @deprecated use getDomNode() */
52 /** @deprecated will be removed in 1.0.0, use getDomNode() */
54 53 getDomElement() {
55 54 return this.getDomNode();
56 55 }
57 56
57 /** Creates DOM node if not created. No additional actions are taken. */
58 58 getDomNode() {
59 59 this.ensureCreated();
60 60 return this._getDomNode();
61 61 }
62 62
63 /** Creates DOM node if not created, places it to the specified position
64 * and calls startup() method for all widgets contained by this node.
65 *
66 * @param {string | Node} refNode The reference node where the created
67 * DOM should be placed.
68 * @param {DojoNodePosition} position Optional parameter, specifies the
69 * position relative to refNode. Default is "last" (i.e. last child).
70 */
63 71 placeAt(refNode: string | Node, position?: DojoNodePosition) {
64 dom.place(this.getDomNode(), refNode, position);
72 const domNode = this.getDomNode();
73 dom.place(domNode, refNode, position);
74 registry.findWidgets(domNode).forEach(w => w.startup());
65 75 }
66 76
67 77 abstract _create(attrs: object, children: any[]): void;
78
79 abstract _getDomNode(): TNode;
68 80 }
@@ -1,25 +1,18
1 1 import { _Widget } from "./WidgetContext";
2 2 import { MapOf } from "@implab/core-amd/interfaces";
3 3 import { prototype } from "../declare";
4 4
5 5 /** Special widget used to create a document fragment */
6 6 export class DjxFragment implements _Widget {
7 7
8 @prototype()
9 8 domNode: Node;
10 9
11 10 containerNode?: Node;
12 11
13 12 constructor() {
14 13 this.domNode = this.containerNode = document.createDocumentFragment();
15 14 }
16
17 get(attr: string) {
18 return undefined;
19 }
20 set(attr: string, value: any): void;
21 set(attrs: MapOf<any>): void;
22 set() {
23 /* do nothing */
15 buildRendering() {
16 // this function marks this class as _Widget
24 17 }
25 18 } No newline at end of file
@@ -1,51 +1,95
1 1 import dom = require("dojo/dom-construct");
2 2 import { argumentNotNull } from "@implab/core-amd/safe";
3 3 import { BuildContextBase } from "./BuildContextBase";
4 import { MapOf } from "@implab/core-amd/interfaces";
4 import { DojoNodePosition, isWidget } from "./traits";
5 5
6 6 // tslint:disable-next-line: class-name
7 7 export interface _Widget {
8 8 domNode: Node;
9 9
10 get(attr: string): any;
10 containerNode?: Node;
11 11
12 set(attr: string, value: any): void;
13 set(attrs: MapOf<any>): void;
12 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
13 startup?(): void;
14 14
15 containerNode?: Node
15 addChild?(widget: any, index?: number): void;
16 16 }
17 17
18 18 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
19 19
20 20 export class WidgetContext extends BuildContextBase<Node> {
21 widgetClass: _WidgetCtor;
21 readonly widgetClass: _WidgetCtor;
22 22
23 23 _instance: _Widget | undefined;
24 24
25 25 constructor(widgetClass: _WidgetCtor) {
26 26 super();
27 27 argumentNotNull(widgetClass, "widgetClass");
28 28
29 29 this.widgetClass = widgetClass;
30 30 }
31 31
32 32 _addChild(child: any): void {
33 if (!this._instance || !this._instance.containerNode)
34 throw new Error("Widget doesn't support adding children");
33 const instance = this._getInstance();
35 34
36 dom.place(this.getChildDom(child), this._instance.containerNode);
35 if (instance.addChild) {
36 if (child instanceof WidgetContext)
37 // layout containers add custom logic to addChild methods
38 instance.addChild(child.getWidgetInstance());
39 else if (isWidget(child))
40 instance.addChild(child);
41 else
42 instance.addChild(this.getChildDom(child));
43 } else {
44 if (!instance.containerNode)
45 throw new Error("The widget doesn't have neither addChild nor containerNode");
46
47 // the current widget isn't started, it's children shouldn't start too
48 dom.place(this.getChildDom(child), instance.containerNode);
49 }
37 50 }
38 51
39 52 _create(attrs: object, children: any[]) {
40 53 this._instance = new this.widgetClass(attrs);
41 54 if (children)
42 55 children.forEach(x => this._addChild(x));
43 56 }
44 57
58 private _getInstance() {
59 if (!this._instance)
60 throw new Error("The instance of the widget isn't created");
61 return this._instance;
62 }
63
45 64 _getDomNode() {
46 65 if (!this._instance)
47 66 throw new Error("The instance of the widget isn't created");
48 67 return this._instance.domNode;
49 68 }
50 69
70 /** Overrides default placeAt implementation. Calls placeAt of the
71 * widget and then starts it.
72 *
73 * @param refNode A node or id of the node where the widget should be placed.
74 * @param position A position relative to refNode.
75 */
76 placeAt(refNode: string | Node, position?: DojoNodePosition) {
77 this.ensureCreated();
78 const instance = this._getInstance();
79 if (typeof instance.placeAt === "function") {
80 instance.placeAt(refNode, position);
81
82 // do we need to force widget startup?
83 if (typeof instance.startup === "function")
84 instance.startup();
85 } else {
86 super.placeAt(refNode, position);
51 87 }
88 }
89
90 getWidgetInstance() {
91 this.ensureCreated();
92 return this._getInstance();
93 }
94
95 }
@@ -1,63 +1,66
1 1 import _WidgetBase = require("dijit/_WidgetBase");
2 2
3 3 type _WidgetBaseConstructor = typeof _WidgetBase;
4 4
5 5 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
6 6
7 7 export interface BuildContext<TNode extends Node = Node> {
8 8 getDomNode(): TNode;
9 9
10 10 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
11 11 }
12 12
13 13 export function isNode(el: any): el is Node {
14 14 return el && el.nodeName && el.nodeType;
15 15 }
16 16
17 17 export function isElementNode(el: any): el is Element {
18 18 return isNode(el) && el.nodeType === 1;
19 19 }
20 20
21 21 export function isTextNode(el: any): el is Text {
22 22 return isNode(el) && el.nodeType === 3;
23 23 }
24 24
25 25 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
26 26 return isNode(el) && el.nodeType === 7;
27 27 }
28 28
29 29 export function isCommentNode(el: any): el is Comment {
30 30 return isNode(el) && el.nodeType === 8;
31 31 }
32 32
33 33 export function isDocumentNode(el: any): el is Document {
34 34 return isNode(el) && el.nodeType === 9;
35 35 }
36 36
37 37 export function isDocumentTypeNode(el: any): el is DocumentType {
38 38 return isNode(el) && el.nodeType === 10;
39 39 }
40 40
41 41 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
42 42 return isNode(el) && el.nodeType === 11;
43 43 }
44 44
45 45 export function isWidget(v: any): v is _WidgetBase {
46 46 return v && "domNode" in v;
47 47 }
48 48
49 49 export function isBuildContext(v: any): v is BuildContext {
50 50 return typeof v === "object" && typeof v.getDomElement === "function";
51 51 }
52 52
53 53 export function isPlainObject(v: object) {
54 54 if (typeof v !== "object")
55 55 return false;
56 56
57 57 const vp = Object.getPrototypeOf(v);
58 58 return !vp || vp === Object.prototype;
59 59 }
60 60
61 61 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
62 return typeof v === "function" && v.prototype && "domNode" in v.prototype;
62 return typeof v === "function" && v.prototype && (
63 "domNode" in v.prototype ||
64 "buildRendering" in v.prototype
65 );
63 66 }
General Comments 0
You need to be logged in to leave comments. Login now