##// 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 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 injectBefore?: 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 erorr: 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.injectBefore || _injectionPoint.firstChild;
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,68 +1,80
1 import { isNull, mixin } from "@implab/core-amd/safe";
1 import { isNull, mixin } from "@implab/core-amd/safe";
2 import { isPlainObject, isNode, isBuildContext, DojoNodePosition, BuildContext } from "./traits";
2 import { isPlainObject, isNode, isBuildContext, DojoNodePosition, BuildContext } from "./traits";
3
3
4 import dom = require("dojo/dom-construct");
4 import dom = require("dojo/dom-construct");
5 import registry = require("dijit/registry");
5
6
6 export abstract class BuildContextBase<TNode extends Node> implements BuildContext<TNode> {
7 export abstract class BuildContextBase<TNode extends Node> implements BuildContext<TNode> {
7 private _attrs = {};
8 private _attrs = {};
8
9
9 private _children = new Array();
10 private _children = new Array();
10
11
11 private _created: boolean = false;
12 private _created: boolean = false;
12
13
13 visitNext(v: any) {
14 visitNext(v: any) {
14 if (this._created)
15 if (this._created)
15 throw new Error("The Element is already created");
16 throw new Error("The Element is already created");
16
17
17 if (isNull(v))
18 if (isNull(v))
18 return;
19 return;
19
20
20 if (isPlainObject(v)) {
21 if (isPlainObject(v)) {
21 mixin(this._attrs, v);
22 mixin(this._attrs, v);
22 } else if (v instanceof Array) {
23 } else if (v instanceof Array) {
23 v.forEach(x => this.visitNext(x));
24 v.forEach(x => this.visitNext(x));
24 } else {
25 } else {
25 this._children.push(v);
26 this._children.push(v);
26 }
27 }
27 }
28 }
28
29
29 getChildDom(v: any) {
30 getChildDom(v: any) {
30 const tv = typeof v;
31 const tv = typeof v;
31 if (tv === "string" || tv === "number" || tv === "boolean" || v instanceof RegExp || v instanceof Date) {
32 if (tv === "string" || tv === "number" || tv === "boolean" || v instanceof RegExp || v instanceof Date) {
32 return document.createTextNode(v.toString());
33 return document.createTextNode(v.toString());
33 } else if (isNode(v)) {
34 } else if (isNode(v)) {
34 return v;
35 return v;
35 } else if (isBuildContext(v)) {
36 } else if (isBuildContext(v)) {
36 return v.getDomNode();
37 return v.getDomNode();
37 } else {
38 } else {
38 throw new Error("Invalid parameter");
39 throw new Error("Invalid parameter");
39 }
40 }
40 }
41 }
41
42
42 abstract _getDomNode(): TNode;
43
44 ensureCreated() {
43 ensureCreated() {
45 if (!this._created) {
44 if (!this._created) {
46 this._create(this._attrs, this._children);
45 this._create(this._attrs, this._children);
47 this._children = [];
46 this._children = [];
48 this._attrs = {};
47 this._attrs = {};
49 this._created = true;
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 getDomElement() {
53 getDomElement() {
55 return this.getDomNode();
54 return this.getDomNode();
56 }
55 }
57
56
57 /** Creates DOM node if not created. No additional actions are taken. */
58 getDomNode() {
58 getDomNode() {
59 this.ensureCreated();
59 this.ensureCreated();
60 return this._getDomNode();
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 placeAt(refNode: string | Node, position?: DojoNodePosition) {
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 abstract _create(attrs: object, children: any[]): void;
77 abstract _create(attrs: object, children: any[]): void;
78
79 abstract _getDomNode(): TNode;
68 }
80 }
@@ -1,25 +1,18
1 import { _Widget } from "./WidgetContext";
1 import { _Widget } from "./WidgetContext";
2 import { MapOf } from "@implab/core-amd/interfaces";
2 import { MapOf } from "@implab/core-amd/interfaces";
3 import { prototype } from "../declare";
3 import { prototype } from "../declare";
4
4
5 /** Special widget used to create a document fragment */
5 /** Special widget used to create a document fragment */
6 export class DjxFragment implements _Widget {
6 export class DjxFragment implements _Widget {
7
7
8 @prototype()
9 domNode: Node;
8 domNode: Node;
10
9
11 containerNode?: Node;
10 containerNode?: Node;
12
11
13 constructor() {
12 constructor() {
14 this.domNode = this.containerNode = document.createDocumentFragment();
13 this.domNode = this.containerNode = document.createDocumentFragment();
15 }
14 }
16
15 buildRendering() {
17 get(attr: string) {
16 // this function marks this class as _Widget
18 return undefined;
19 }
20 set(attr: string, value: any): void;
21 set(attrs: MapOf<any>): void;
22 set() {
23 /* do nothing */
24 }
17 }
25 } No newline at end of file
18 }
@@ -1,51 +1,95
1 import dom = require("dojo/dom-construct");
1 import dom = require("dojo/dom-construct");
2 import { argumentNotNull } from "@implab/core-amd/safe";
2 import { argumentNotNull } from "@implab/core-amd/safe";
3 import { BuildContextBase } from "./BuildContextBase";
3 import { BuildContextBase } from "./BuildContextBase";
4 import { MapOf } from "@implab/core-amd/interfaces";
4 import { DojoNodePosition, isWidget } from "./traits";
5
5
6 // tslint:disable-next-line: class-name
6 // tslint:disable-next-line: class-name
7 export interface _Widget {
7 export interface _Widget {
8 domNode: Node;
8 domNode: Node;
9
9
10 get(attr: string): any;
10 containerNode?: Node;
11
11
12 set(attr: string, value: any): void;
12 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
13 set(attrs: MapOf<any>): void;
13 startup?(): void;
14
14
15 containerNode?: Node
15 addChild?(widget: any, index?: number): void;
16 }
16 }
17
17
18 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
18 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
19
19
20 export class WidgetContext extends BuildContextBase<Node> {
20 export class WidgetContext extends BuildContextBase<Node> {
21 widgetClass: _WidgetCtor;
21 readonly widgetClass: _WidgetCtor;
22
22
23 _instance: _Widget | undefined;
23 _instance: _Widget | undefined;
24
24
25 constructor(widgetClass: _WidgetCtor) {
25 constructor(widgetClass: _WidgetCtor) {
26 super();
26 super();
27 argumentNotNull(widgetClass, "widgetClass");
27 argumentNotNull(widgetClass, "widgetClass");
28
28
29 this.widgetClass = widgetClass;
29 this.widgetClass = widgetClass;
30 }
30 }
31
31
32 _addChild(child: any): void {
32 _addChild(child: any): void {
33 if (!this._instance || !this._instance.containerNode)
33 const instance = this._getInstance();
34 throw new Error("Widget doesn't support adding children");
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 _create(attrs: object, children: any[]) {
52 _create(attrs: object, children: any[]) {
40 this._instance = new this.widgetClass(attrs);
53 this._instance = new this.widgetClass(attrs);
41 if (children)
54 if (children)
42 children.forEach(x => this._addChild(x));
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 _getDomNode() {
64 _getDomNode() {
46 if (!this._instance)
65 if (!this._instance)
47 throw new Error("The instance of the widget isn't created");
66 throw new Error("The instance of the widget isn't created");
48 return this._instance.domNode;
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 import _WidgetBase = require("dijit/_WidgetBase");
1 import _WidgetBase = require("dijit/_WidgetBase");
2
2
3 type _WidgetBaseConstructor = typeof _WidgetBase;
3 type _WidgetBaseConstructor = typeof _WidgetBase;
4
4
5 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
5 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
6
6
7 export interface BuildContext<TNode extends Node = Node> {
7 export interface BuildContext<TNode extends Node = Node> {
8 getDomNode(): TNode;
8 getDomNode(): TNode;
9
9
10 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
10 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
11 }
11 }
12
12
13 export function isNode(el: any): el is Node {
13 export function isNode(el: any): el is Node {
14 return el && el.nodeName && el.nodeType;
14 return el && el.nodeName && el.nodeType;
15 }
15 }
16
16
17 export function isElementNode(el: any): el is Element {
17 export function isElementNode(el: any): el is Element {
18 return isNode(el) && el.nodeType === 1;
18 return isNode(el) && el.nodeType === 1;
19 }
19 }
20
20
21 export function isTextNode(el: any): el is Text {
21 export function isTextNode(el: any): el is Text {
22 return isNode(el) && el.nodeType === 3;
22 return isNode(el) && el.nodeType === 3;
23 }
23 }
24
24
25 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
25 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
26 return isNode(el) && el.nodeType === 7;
26 return isNode(el) && el.nodeType === 7;
27 }
27 }
28
28
29 export function isCommentNode(el: any): el is Comment {
29 export function isCommentNode(el: any): el is Comment {
30 return isNode(el) && el.nodeType === 8;
30 return isNode(el) && el.nodeType === 8;
31 }
31 }
32
32
33 export function isDocumentNode(el: any): el is Document {
33 export function isDocumentNode(el: any): el is Document {
34 return isNode(el) && el.nodeType === 9;
34 return isNode(el) && el.nodeType === 9;
35 }
35 }
36
36
37 export function isDocumentTypeNode(el: any): el is DocumentType {
37 export function isDocumentTypeNode(el: any): el is DocumentType {
38 return isNode(el) && el.nodeType === 10;
38 return isNode(el) && el.nodeType === 10;
39 }
39 }
40
40
41 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
41 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
42 return isNode(el) && el.nodeType === 11;
42 return isNode(el) && el.nodeType === 11;
43 }
43 }
44
44
45 export function isWidget(v: any): v is _WidgetBase {
45 export function isWidget(v: any): v is _WidgetBase {
46 return v && "domNode" in v;
46 return v && "domNode" in v;
47 }
47 }
48
48
49 export function isBuildContext(v: any): v is BuildContext {
49 export function isBuildContext(v: any): v is BuildContext {
50 return typeof v === "object" && typeof v.getDomElement === "function";
50 return typeof v === "object" && typeof v.getDomElement === "function";
51 }
51 }
52
52
53 export function isPlainObject(v: object) {
53 export function isPlainObject(v: object) {
54 if (typeof v !== "object")
54 if (typeof v !== "object")
55 return false;
55 return false;
56
56
57 const vp = Object.getPrototypeOf(v);
57 const vp = Object.getPrototypeOf(v);
58 return !vp || vp === Object.prototype;
58 return !vp || vp === Object.prototype;
59 }
59 }
60
60
61 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
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