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