##// END OF EJS Templates
file rename
cin -
r98:12074cb11473 v1.3
parent child
Show More
@@ -1,33 +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 { getItemDom } from "./render";
3 import { RenditionBase } from "./RenditionBase";
3 import { RenditionBase } from "./RenditionBase";
4 import { IScope } from "./Scope";
4 import { IScope } from "./Scope";
5
5
6 export class FunctionRendition extends RenditionBase<Node> {
6 export class FunctionRendition extends RenditionBase<Node> {
7 private _component: (...args: any[]) => any;
7 private _component: (...args: any[]) => any;
8
8
9 private _node: Node | undefined;
9 private _node: Node | undefined;
10
10
11 constructor(component: (...args: any[]) => any) {
11 constructor(component: (...args: any[]) => any) {
12 super();
12 super();
13 argumentNotNull(component, "component");
13 argumentNotNull(component, "component");
14
14
15 this._component = component;
15 this._component = component;
16 }
16 }
17
17
18 protected _create(attrs: object, children: any[], scope: IScope) {
18 protected _create(attrs: object, children: any[], scope: IScope) {
19 const _attrs: any = attrs || {};
19 const _attrs: any = attrs || {};
20 const _children = children.map(x => getItemDom(x, scope));
20 const _children = children.map(x => getItemDom(x, scope));
21 this._node = getItemDom(
21 this._node = getItemDom(
22 this._component.call(null, { ..._attrs, children: _children }),
22 this._component.call(null, { ..._attrs, children: _children }),
23 scope
23 scope
24 );
24 );
25 }
25 }
26
26
27 protected _getDomNode() {
27 protected _getDomNode() {
28 if (!this._node)
28 if (!this._node)
29 throw new Error("The instance of the widget isn't created");
29 throw new Error("The instance of the widget isn't created");
30 return this._node;
30 return this._node;
31 }
31 }
32
32
33 }
33 }
@@ -1,39 +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";
4 import { placeAt } from "./traits";
5 import { IScope } from "./Scope";
5 import { IScope } from "./Scope";
6 import { getItemDom } from "./Renderer";
6 import { getItemDom } from "./render";
7
7
8 export class HtmlRendition extends RenditionBase<HTMLElement> {
8 export class HtmlRendition extends RenditionBase<HTMLElement> {
9 elementType: string;
9 elementType: string;
10
10
11 _element: HTMLElement | undefined;
11 _element: HTMLElement | undefined;
12
12
13 constructor(elementType: string) {
13 constructor(elementType: string) {
14 argumentNotEmptyString(elementType, "elementType");
14 argumentNotEmptyString(elementType, "elementType");
15 super();
15 super();
16
16
17 this.elementType = elementType;
17 this.elementType = elementType;
18 }
18 }
19
19
20 _addChild(child: unknown, scope: IScope): void {
20 _addChild(child: unknown, scope: IScope): void {
21 if (!this._element)
21 if (!this._element)
22 throw new Error("The HTML element isn't created");
22 throw new Error("The HTML element isn't created");
23 placeAt(getItemDom(child, scope), this._element);
23 placeAt(getItemDom(child, scope), this._element);
24 }
24 }
25
25
26 _create(attrs: object, children: unknown[], scope: IScope) {
26 _create(attrs: object, children: unknown[], scope: IScope) {
27 this._element = dom.create(this.elementType, attrs);
27 this._element = dom.create(this.elementType, attrs);
28
28
29 children.forEach(v => this._addChild(v, scope));
29 children.forEach(v => this._addChild(v, scope));
30 }
30 }
31
31
32 _getDomNode() {
32 _getDomNode() {
33 if (!this._element)
33 if (!this._element)
34 throw new Error("The HTML element isn't created");
34 throw new Error("The HTML element isn't created");
35
35
36 return this._element;
36 return this._element;
37 }
37 }
38
38
39 }
39 }
@@ -1,73 +1,73
1 import { isNull, mixin } from "@implab/core-amd/safe";
1 import { isNull, mixin } from "@implab/core-amd/safe";
2 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, autostartWidgets } from "./traits";
2 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, autostartWidgets } from "./traits";
3
3
4 import { IScope } from "./Scope";
4 import { IScope } from "./Scope";
5 import { getScope } from "./Renderer";
5 import { getScope } from "./render";
6
6
7 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
7 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
8 private _attrs = {};
8 private _attrs = {};
9
9
10 private _children = new Array();
10 private _children = new Array();
11
11
12 private _created: boolean = false;
12 private _created: boolean = false;
13
13
14 visitNext(v: any) {
14 visitNext(v: any) {
15 if (this._created)
15 if (this._created)
16 throw new Error("The Element is already created");
16 throw new Error("The Element is already created");
17
17
18 if (isNull(v) || typeof v === "boolean")
18 if (isNull(v) || typeof v === "boolean")
19 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
19 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
20 return;
20 return;
21
21
22 if (isPlainObject(v)) {
22 if (isPlainObject(v)) {
23 mixin(this._attrs, v);
23 mixin(this._attrs, v);
24 } else if (v instanceof Array) {
24 } else if (v instanceof Array) {
25 v.forEach(x => this.visitNext(x));
25 v.forEach(x => this.visitNext(x));
26 } else {
26 } else {
27 this._children.push(v);
27 this._children.push(v);
28 }
28 }
29 }
29 }
30
30
31 ensureCreated(scope: IScope) {
31 ensureCreated(scope: IScope) {
32 if (!this._created) {
32 if (!this._created) {
33 this._create(this._attrs, this._children, scope);
33 this._create(this._attrs, this._children, scope);
34 this._children = [];
34 this._children = [];
35 this._attrs = {};
35 this._attrs = {};
36 this._created = true;
36 this._created = true;
37 }
37 }
38 }
38 }
39
39
40 /** Is rendition was instantiated to the DOM node */
40 /** Is rendition was instantiated to the DOM node */
41 isCreated() {
41 isCreated() {
42 return this._created;
42 return this._created;
43 }
43 }
44
44
45 /** Creates DOM node if not created. No additional actions are taken. */
45 /** Creates DOM node if not created. No additional actions are taken. */
46 getDomNode(scope?: IScope) {
46 getDomNode(scope?: IScope) {
47 this.ensureCreated(scope || getScope());
47 this.ensureCreated(scope || getScope());
48 return this._getDomNode();
48 return this._getDomNode();
49 }
49 }
50
50
51 /** 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
52 * and calls startup() method for all widgets contained by this node.
52 * and calls startup() method for all widgets contained by this node.
53 *
53 *
54 * @param {string | Node} refNode The reference node where the created
54 * @param {string | Node} refNode The reference node where the created
55 * DOM should be placed.
55 * DOM should be placed.
56 * @param {DojoNodePosition} position Optional parameter, specifies the
56 * @param {DojoNodePosition} position Optional parameter, specifies the
57 * position relative to refNode. Default is "last" (i.e. last child).
57 * position relative to refNode. Default is "last" (i.e. last child).
58 */
58 */
59 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
59 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
60 const domNode = this.getDomNode();
60 const domNode = this.getDomNode();
61
61
62 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode];
62 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode];
63
63
64 placeAt(domNode, refNode, position);
64 placeAt(domNode, refNode, position);
65
65
66 startupPending.forEach(autostartWidgets);
66 startupPending.forEach(autostartWidgets);
67
67
68 }
68 }
69
69
70 protected abstract _create(attrs: object, children: unknown[], scope: IScope): void;
70 protected abstract _create(attrs: object, children: unknown[], scope: IScope): void;
71
71
72 protected abstract _getDomNode(): TNode;
72 protected abstract _getDomNode(): TNode;
73 }
73 }
@@ -1,56 +1,55
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 { render } from "./Renderer";
4 import { getItemDom, render } from "./render";
5 import { RenditionBase } from "./RenditionBase";
5 import { RenditionBase } from "./RenditionBase";
6 import { IScope, Scope } from "./Scope";
6 import { IScope, Scope } from "./Scope";
7 import { Observable } from "../observable";
7 import { Observable } from "../observable";
8
8
9 const trace = TraceSource.get(mid);
9 const trace = TraceSource.get(mid);
10
10
11 const noop = () => {};
12
13 export class WatchRendition<T> extends RenditionBase<Node> {
11 export class WatchRendition<T> extends RenditionBase<Node> {
14 private readonly _factory: (arg: T) => any;
12 private readonly _component: (arg: T) => unknown;
15
13
16 private _node: Node;
14 private _node: Node;
17
15
18 private readonly _scope = new Scope();
16 private readonly _scope = new Scope();
19
17
20 private readonly _subject: Observable<T>;
18 private readonly _subject: Observable<T>;
21
19
22 constructor(component: (arg: T) => any, subject: Observable<T>) {
20 constructor(component: (arg: T) => unknown, subject: Observable<T>) {
23 super();
21 super();
24 argumentNotNull(component, "component");
22 argumentNotNull(component, "component");
25
23
26 this._factory = component;
24 this._component = component;
27
25
28 this._subject = subject;
26 this._subject = subject;
29
27
30 this._node = document.createComment("WatchRendition placeholder");
28 this._node = document.createComment("WatchRendition placeholder");
31 }
29 }
32
30
33 protected _create(attrs: object, children: any[], scope: IScope) {
31 protected _create(attrs: object, children: any[], scope: IScope) {
34 scope.own(this._scope);
32 scope.own(this._scope);
35 scope.own(this._subject.on({ next: this._onValue }));
33 scope.own(this._subject.on({ next: this._onValue }));
36 }
34 }
37
35
38 private _onValue = (value: T) => void this._render(value).catch( e => trace.error(e));
36 private _onValue = (value: T) =>
37 void this._render(value).catch( e => trace.error(e));
39
38
40 private async _render(value: T) {
39 private async _render(value: T) {
41 const prevNode = this._node;
40 const prevNode = this._node;
42 this._scope.clean();
41 this._scope.clean();
43
42
44 this._node = await render(() => this._factory(value), this._scope);
43 this._node = await render(this._component(value), this._scope);
45
44
46 this.placeAt(prevNode, "replace");
45 this.placeAt(prevNode, "replace");
47 }
46 }
48
47
49 protected _getDomNode() {
48 protected _getDomNode() {
50 if (!this._node)
49 if (!this._node)
51 throw new Error("The instance of the widget isn't created");
50 throw new Error("The instance of the widget isn't created");
52 return this._node;
51 return this._node;
53 }
52 }
54
53
55
54
56 }
55 }
@@ -1,129 +1,129
1 import { argumentNotNull } from "@implab/core-amd/safe";
1 import { argumentNotNull } from "@implab/core-amd/safe";
2 import { RenditionBase } from "./RenditionBase";
2 import { RenditionBase } from "./RenditionBase";
3 import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits";
3 import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits";
4 import registry = require("dijit/registry");
4 import registry = require("dijit/registry");
5 import ContentPane = require("dijit/layout/ContentPane");
5 import ContentPane = require("dijit/layout/ContentPane");
6 import { IScope } from "./Scope";
6 import { IScope } from "./Scope";
7 import { getItemDom, getScope } from "./Renderer";
7 import { getItemDom, getScope } from "./render";
8
8
9 // tslint:disable-next-line: class-name
9 // tslint:disable-next-line: class-name
10 export interface _Widget {
10 export interface _Widget {
11 domNode: Node;
11 domNode: Node;
12
12
13 containerNode?: Node;
13 containerNode?: Node;
14
14
15 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
15 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
16 startup?(): void;
16 startup?(): void;
17
17
18 addChild?(widget: any, index?: number): void;
18 addChild?(widget: any, index?: number): void;
19 }
19 }
20
20
21 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
21 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
22
22
23 export class WidgetRendition extends RenditionBase<Node> {
23 export class WidgetRendition extends RenditionBase<Node> {
24 readonly widgetClass: _WidgetCtor;
24 readonly widgetClass: _WidgetCtor;
25
25
26 _instance: _Widget | undefined;
26 _instance: _Widget | undefined;
27
27
28 constructor(widgetClass: _WidgetCtor) {
28 constructor(widgetClass: _WidgetCtor) {
29 super();
29 super();
30 argumentNotNull(widgetClass, "widgetClass");
30 argumentNotNull(widgetClass, "widgetClass");
31
31
32 this.widgetClass = widgetClass;
32 this.widgetClass = widgetClass;
33 }
33 }
34
34
35 _addChild(child: any, scope: IScope): void {
35 _addChild(child: any, scope: IScope): void {
36 const instance = this._getInstance();
36 const instance = this._getInstance();
37
37
38 if (instance.addChild) {
38 if (instance.addChild) {
39 if (child instanceof WidgetRendition) {
39 if (child instanceof WidgetRendition) {
40 // layout containers add custom logic to addChild methods
40 // layout containers add custom logic to addChild methods
41 instance.addChild(child.getWidgetInstance(scope));
41 instance.addChild(child.getWidgetInstance(scope));
42 } else if (isWidget(child)) {
42 } else if (isWidget(child)) {
43 instance.addChild(child);
43 instance.addChild(child);
44 } else {
44 } else {
45 const childDom = getItemDom(child, scope);
45 const childDom = getItemDom(child, scope);
46 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
46 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
47
47
48 if (w) {
48 if (w) {
49 instance.addChild(w);
49 instance.addChild(w);
50 } else {
50 } else {
51 if (!instance.containerNode)
51 if (!instance.containerNode)
52 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");
53
53
54 // 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
55 placeAt(getItemDom(child,scope), instance.containerNode, "last");
55 placeAt(getItemDom(child,scope), instance.containerNode, "last");
56 }
56 }
57 }
57 }
58 } else {
58 } else {
59 if (!instance.containerNode)
59 if (!instance.containerNode)
60 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");
61
61
62 // 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
63 placeAt(getItemDom(child, scope), instance.containerNode, "last");
63 placeAt(getItemDom(child, scope), instance.containerNode, "last");
64 }
64 }
65 }
65 }
66
66
67 protected _create(attrs: any, children: any[], scope: IScope) {
67 protected _create(attrs: any, children: any[], scope: IScope) {
68 if (this.widgetClass.prototype instanceof ContentPane) {
68 if (this.widgetClass.prototype instanceof ContentPane) {
69 // a special case for the ContentPane this is for
69 // a special case for the ContentPane this is for
70 // the compatibility with this heavy widget, all
70 // the compatibility with this heavy widget, all
71 // regular containers could be easily manipulated
71 // regular containers could be easily manipulated
72 // through `containerNode` property or `addChild` method.
72 // through `containerNode` property or `addChild` method.
73
73
74 // render children to the DocumentFragment
74 // render children to the DocumentFragment
75 const content = document.createDocumentFragment();
75 const content = document.createDocumentFragment();
76 children.forEach(child => content.appendChild(getItemDom(child, scope)));
76 children.forEach(child => content.appendChild(getItemDom(child, scope)));
77
77
78 // set the content property to the parameters of the widget
78 // set the content property to the parameters of the widget
79 const _attrs = { ...attrs, content };
79 const _attrs = { ...attrs, content };
80 this._instance = new this.widgetClass(_attrs);
80 this._instance = new this.widgetClass(_attrs);
81 } else {
81 } else {
82 this._instance = new this.widgetClass(attrs);
82 this._instance = new this.widgetClass(attrs);
83 children.forEach(x => this._addChild(x, scope));
83 children.forEach(x => this._addChild(x, scope));
84 }
84 }
85
85
86 }
86 }
87
87
88 private _getInstance() {
88 private _getInstance() {
89 if (!this._instance)
89 if (!this._instance)
90 throw new Error("The instance of the widget isn't created");
90 throw new Error("The instance of the widget isn't created");
91 return this._instance;
91 return this._instance;
92 }
92 }
93
93
94 protected _getDomNode() {
94 protected _getDomNode() {
95 if (!this._instance)
95 if (!this._instance)
96 throw new Error("The instance of the widget isn't created");
96 throw new Error("The instance of the widget isn't created");
97 return this._instance.domNode;
97 return this._instance.domNode;
98 }
98 }
99
99
100 /** Overrides default placeAt implementation. Calls placeAt of the
100 /** Overrides default placeAt implementation. Calls placeAt of the
101 * widget and then starts it.
101 * widget and then starts it.
102 *
102 *
103 * @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.
104 * @param position A position relative to refNode.
104 * @param position A position relative to refNode.
105 */
105 */
106 placeAt(refNode: string | Node, position?: DojoNodePosition) {
106 placeAt(refNode: string | Node, position?: DojoNodePosition) {
107 this.ensureCreated(getScope());
107 this.ensureCreated(getScope());
108 const instance = this._getInstance();
108 const instance = this._getInstance();
109 if (typeof instance.placeAt === "function") {
109 if (typeof instance.placeAt === "function") {
110 instance.placeAt(refNode, position);
110 instance.placeAt(refNode, position);
111
111
112 // fix the dojo startup behavior when the widget is placed
112 // fix the dojo startup behavior when the widget is placed
113 // directly to the document and doesn't have any enclosing widgets
113 // directly to the document and doesn't have any enclosing widgets
114 const parentWidget = instance.domNode.parentNode ?
114 const parentWidget = instance.domNode.parentNode ?
115 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
115 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
116 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
116 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
117 instance.startup();
117 instance.startup();
118 } else {
118 } else {
119 // the widget doesn't have a placeAt method, strange but whatever
119 // the widget doesn't have a placeAt method, strange but whatever
120 super.placeAt(refNode, position);
120 super.placeAt(refNode, position);
121 }
121 }
122 }
122 }
123
123
124 getWidgetInstance(scope?: IScope) {
124 getWidgetInstance(scope?: IScope) {
125 this.ensureCreated(scope || getScope());
125 this.ensureCreated(scope || getScope());
126 return this._getInstance();
126 return this._getInstance();
127 }
127 }
128
128
129 }
129 }
@@ -1,49 +1,60
1 import { IScope, Scope } from "./Scope";
1 import { IScope, Scope } from "./Scope";
2 import { destroy, isNode, isRendition, isWidget, 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 /** Returns the current scope */
12 export const getScope = () => _scope;
13 export const getScope = () => _scope;
13
14
14 export const render = async (rendition: () => Rendition, scope = Scope.dummy) => {
15 /** Schedules the rendition to be rendered to the DOM Node
16 * @param rendition The rendition to be rendered
17 * @param scope The scope
18 */
19 export const render = async (rendition: unknown, scope = Scope.dummy) => {
15 await beginRender();
20 await beginRender();
16 const prev = _scope;
21 const prev = _scope;
17 _scope = scope;
22 _scope = scope;
18 try {
23 try {
19 const node = rendition().getDomNode();
24 const node = getItemDom(rendition, scope);
20 scope.own(() => destroy(node));
25 scope.own(() => destroy(node));
21 return node;
26 return node;
22 } finally {
27 } finally {
23 _scope = prev;
28 _scope = prev;
24 endRender();
29 endRender();
25 }
30 }
26 }
31 }
27
32
28 /** Renders DOM element for different types of the argument. */
33 /** Renders DOM element for different types of the argument. */
29 export const getItemDom = (v: unknown, scope: IScope) => {
34 export const getItemDom = (v: unknown, scope: IScope) => {
30 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
35 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
31 // primitive types converted to the text nodes
36 // primitive types converted to the text nodes
32 return document.createTextNode(v.toString());
37 return document.createTextNode(v.toString());
33 } else if (isNode(v)) {
38 } else if (isNode(v)) {
34 // nodes are kept as is
39 // nodes are kept as is
35 return v;
40 return v;
36 } else if (isRendition(v)) {
41 } else if (isRendition(v)) {
37 // renditions are instantiated
42 // renditions are instantiated
38 return v.getDomNode(scope);
43 return v.getDomNode(scope);
39 } else if (isWidget(v)) {
44 } else if (isWidget(v)) {
40 // widgets are converted to it's markup
45 // widgets are converted to it's markup
41 return v.domNode;
46 return v.domNode;
42 } else if (typeof v === "boolean" || v === null || v === undefined) {
47 } else if (typeof v === "boolean" || v === null || v === undefined) {
43 // null | undefined | boolean are removed, converted to comments
48 // null | undefined | boolean are removed, converted to comments
44 return document.createComment(`[${typeof v} ${String(v)}]`);
49 return document.createComment(`[${typeof v} ${String(v)}]`);
50 } else if (v instanceof Array) {
51 // arrays will be translated to document fragments
52 const fragment = document.createDocumentFragment();
53 v.map(item => getItemDom(item, scope))
54 .forEach(node => fragment.appendChild(node));
55 return fragment;
45 } else {
56 } else {
46 // bug: explicit error otherwise
57 // bug: explicit error otherwise
47 throw new Error("Invalid parameter: " + v);
58 throw new Error("Invalid parameter: " + v);
48 }
59 }
49 }
60 }
General Comments 0
You need to be logged in to leave comments. Login now