##// END OF EJS Templates
refactoring, adding scope to rendering methods
cin -
r96:a316cfea8bb1 v1.3
parent child
Show More
@@ -0,0 +1,34
1 import { IDestroyable } from "@implab/core-amd/interfaces";
2
3 export interface Sink<T> {
4 next: (value: T) => void;
5 error: (e: unknown) => void;
6 complete: () => void;
7 }
8
9 export type Consumer<T> = Partial<Sink<T>>;
10
11 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
12
13 export interface Observable<T> {
14 on(sink: Partial<Sink<T>>): IDestroyable;
15 }
16
17 const noop = () => {};
18
19 const sink = <T>(consumer: Consumer<T>) => {
20 const { next = noop, error = noop, complete = noop } = consumer;
21 let done = false;
22
23 return {
24 next: (value: T) => done && next(value),
25 error: (e: unknown) => done && (done = true, error(e)),
26 complete: () => done && (done = true, complete())
27 };
28 }
29
30 export const observe = <T>(producer: Producer<T>) => ({
31 on: (consumer: Consumer<T>) => ({
32 destroy: producer(sink(consumer)) ?? noop
33 })
34 });
@@ -1,11 +1,13
1 import { Constructor, IDestroyable, IRemovable } from "@implab/core-amd/interfaces";
1 import { Constructor } from "@implab/core-amd/interfaces";
2 import { HtmlRendition } from "./tsx/HtmlRendition";
2 import { HtmlRendition } from "./tsx/HtmlRendition";
3 import { WidgetRendition } from "./tsx/WidgetRendition";
3 import { WidgetRendition } from "./tsx/WidgetRendition";
4 import { destroy, isWidgetConstructor, Rendition } from "./tsx/traits";
4 import { isWidgetConstructor, Rendition } from "./tsx/traits";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
6 import Stateful = require("dojo/Stateful");
6 import Stateful = require("dojo/Stateful");
7 import _WidgetBase = require("dijit/_WidgetBase");
7 import _WidgetBase = require("dijit/_WidgetBase");
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
9 import { WatchRendition } from "./tsx/WatchRendition";
10 import { observe } from "./observable";
9
11
10 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
12 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
11 if (typeof elementType === "string") {
13 if (typeof elementType === "string") {
@@ -44,7 +46,6 export type DojoMouseEvent<T = any> = Mo
44
46
45 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
47 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
46
48
47 type CleanFn = (instance: IRemovable | IDestroyable) => void;
48
49
49 /**
50 /**
50 * Observers the property and calls render callback each change.
51 * Observers the property and calls render callback each change.
@@ -52,14 +53,12 type CleanFn = (instance: IRemovable | I
52 * @param target The target object which property will be observed.
53 * @param target The target object which property will be observed.
53 * @param prop The name of the property.
54 * @param prop The name of the property.
54 * @param render The callback which will be called every time the value is changed
55 * @param render The callback which will be called every time the value is changed
55 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
56 * @returns Rendition which is created instantly
56 * @returns Rendition which is created instantly
57 */
57 */
58 export function watch<W extends _WidgetBase, K extends keyof W>(
58 export function watch<W extends _WidgetBase, K extends keyof W>(
59 target: W,
59 target: W,
60 prop: K,
60 prop: K,
61 render: (model: W[K]) => any,
61 render: (model: W[K]) => any
62 cleanupOrOwner?: { own: CleanFn } | CleanFn
63 ): Rendition;
62 ): Rendition;
64 /**
63 /**
65 * Observers the property and calls render callback each change.
64 * Observers the property and calls render callback each change.
@@ -67,32 +66,28 export function watch<W extends _WidgetB
67 * @param target The target object which property will be observed.
66 * @param target The target object which property will be observed.
68 * @param prop The name of the property.
67 * @param prop The name of the property.
69 * @param render The callback which will be called every time the value is changed
68 * @param render The callback which will be called every time the value is changed
70 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
71 * @returns Rendition which is created instantly
69 * @returns Rendition which is created instantly
72 */
70 */
73 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
71 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
74 target: T,
72 target: T,
75 prop: K,
73 prop: K,
76 render: (model: StatefulProps<T>[K]) => any,
74 render: (model: StatefulProps<T>[K]) => any
77 cleanupOrOwner?: { own: CleanFn } | CleanFn
78 ): Rendition;
75 ): Rendition;
79 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
76 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
80 target: T,
77 target: T,
81 prop: K,
78 prop: K,
82 render: (model: StatefulProps<T>[K]) => any,
79 render: (model: StatefulProps<T>[K]) => any
83 cleanupOrOwner: { own: CleanFn } | CleanFn = () => { }
84 ) {
80 ) {
85 let rendition = new FunctionRendition(() => render(target.get(prop)));
81 return new WatchRendition(
86 const _own = cleanupOrOwner instanceof Function ? cleanupOrOwner : (x: IRemovable) => cleanupOrOwner.own(x);
82 render,
87 _own(target.watch(prop, (_name, oldValue, newValue) => {
83 observe(({next}) => {
88 if (oldValue !== newValue) {
84 const h = target.watch(
89 const newRendition = new FunctionRendition(() => render(newValue));
85 prop,
90 newRendition.placeAt(rendition.getDomNode(), "replace");
86 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
91 destroy(rendition.getDomNode());
87 );
92 rendition = newRendition;
88 return () => h.remove();
93 }
89 })
94 }));
90 )
95 return rendition;
96 }
91 }
97
92
98 /** Decorates the method which will be registered as the handle for the specified event.
93 /** Decorates the method which will be registered as the handle for the specified event.
@@ -1,5 +1,7
1 import { argumentNotNull } from "@implab/core-amd/safe";
1 import { argumentNotNull } from "@implab/core-amd/safe";
2 import { getItemDom } from "./Renderer";
2 import { RenditionBase } from "./RenditionBase";
3 import { RenditionBase } from "./RenditionBase";
4 import { IScope } from "./Scope";
3
5
4 export class FunctionRendition extends RenditionBase<Node> {
6 export class FunctionRendition extends RenditionBase<Node> {
5 private _component: (...args: any[]) => any;
7 private _component: (...args: any[]) => any;
@@ -13,11 +15,12 export class FunctionRendition extends R
13 this._component = component;
15 this._component = component;
14 }
16 }
15
17
16 protected _create(attrs: object, children: any[]) {
18 protected _create(attrs: object, children: any[], scope: IScope) {
17 const _attrs: any = attrs || {};
19 const _attrs: any = attrs || {};
18 const _children = children.map(x => this.getItemDom(x));
20 const _children = children.map(x => getItemDom(x, scope));
19 this._node = this.getItemDom(
21 this._node = getItemDom(
20 this._component.call(null, { ..._attrs, children: _children })
22 this._component.call(null, { ..._attrs, children: _children }),
23 scope
21 );
24 );
22 }
25 }
23
26
@@ -1,6 +1,9
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";
5 import { IScope } from "./Scope";
6 import { getItemDom } from "./Renderer";
4
7
5 export class HtmlRendition extends RenditionBase<HTMLElement> {
8 export class HtmlRendition extends RenditionBase<HTMLElement> {
6 elementType: string;
9 elementType: string;
@@ -14,16 +17,16 export class HtmlRendition extends Rendi
14 this.elementType = elementType;
17 this.elementType = elementType;
15 }
18 }
16
19
17 _addChild(child: any): void {
20 _addChild(child: unknown, scope: IScope): void {
18 if (!this._element)
21 if (!this._element)
19 throw new Error("The HTML element isn't created");
22 throw new Error("The HTML element isn't created");
20 dom.place(this.getItemDom(child), this._element);
23 placeAt(getItemDom(child, scope), this._element);
21 }
24 }
22
25
23 _create(attrs: object, children: any[]) {
26 _create(attrs: object, children: unknown[], scope: IScope) {
24 this._element = dom.create(this.elementType, attrs);
27 this._element = dom.create(this.elementType, attrs);
25
28
26 children.forEach(v => this._addChild(v));
29 children.forEach(v => this._addChild(v, scope));
27 }
30 }
28
31
29 _getDomNode() {
32 _getDomNode() {
@@ -1,5 +1,5
1 import { Scope } from "./Scope";
1 import { IScope, Scope } from "./Scope";
2 import { destroy, 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
@@ -24,3 +24,26 export const render = async (rendition:
24 endRender();
24 endRender();
25 }
25 }
26 }
26 }
27
28 /** Renders DOM element for different types of the argument. */
29 export const getItemDom = (v: unknown, scope: IScope) => {
30 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
31 // primitive types converted to the text nodes
32 return document.createTextNode(v.toString());
33 } else if (isNode(v)) {
34 // nodes are kept as is
35 return v;
36 } else if (isRendition(v)) {
37 // renditions are instantiated
38 return v.getDomNode(scope);
39 } else if (isWidget(v)) {
40 // widgets are converted to it's markup
41 return v.domNode;
42 } else if (typeof v === "boolean" || v === null || v === undefined) {
43 // null | undefined | boolean are removed, converted to comments
44 return document.createComment(`[${typeof v} ${String(v)}]`);
45 } else {
46 // bug: explicit error otherwise
47 throw new Error("Invalid parameter: " + v);
48 }
49 }
@@ -1,9 +1,8
1 import { isNull, mixin } from "@implab/core-amd/safe";
1 import { isNull, mixin } from "@implab/core-amd/safe";
2 import { isPlainObject, isNode, isRendition, DojoNodePosition, Rendition, isInPage, isWidget, isDocumentFragmentNode, startupWidgets } from "./traits";
2 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, autostartWidgets } from "./traits";
3
3
4 import dom = require("dojo/dom-construct");
4 import { IScope } from "./Scope";
5 import registry = require("dijit/registry");
5 import { getScope } from "./Renderer";
6
7
6
8 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
7 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
9 private _attrs = {};
8 private _attrs = {};
@@ -29,34 +28,9 export abstract class RenditionBase<TNod
29 }
28 }
30 }
29 }
31
30
32 /** Renders DOM element for different types of the argument. */
31 ensureCreated(scope: IScope) {
33 protected getItemDom(v: any) {
34 const tv = typeof v;
35
36 if (tv === "string" || tv === "number" || v instanceof RegExp || v instanceof Date) {
37 // primitive types converted to the text nodes
38 return document.createTextNode(v.toString());
39 } else if (isNode(v)) {
40 // nodes are kept as is
41 return v;
42 } else if (isRendition(v)) {
43 // renditions are instantiated
44 return v.getDomNode();
45 } else if (isWidget(v)) {
46 // widgets are converted to it's markup
47 return v.domNode;
48 } else if (tv === "boolean" || v === null || v === undefined) {
49 // null | undefined | boolean are removed, converted to comments
50 return document.createComment(`[${tv} ${String(v)}]`);
51 } else {
52 // bug: explicit error otherwise
53 throw new Error("Invalid parameter: " + v);
54 }
55 }
56
57 ensureCreated() {
58 if (!this._created) {
32 if (!this._created) {
59 this._create(this._attrs, this._children);
33 this._create(this._attrs, this._children, scope);
60 this._children = [];
34 this._children = [];
61 this._attrs = {};
35 this._attrs = {};
62 this._created = true;
36 this._created = true;
@@ -69,8 +43,8 export abstract class RenditionBase<TNod
69 }
43 }
70
44
71 /** Creates DOM node if not created. No additional actions are taken. */
45 /** Creates DOM node if not created. No additional actions are taken. */
72 getDomNode() {
46 getDomNode(scope?: IScope) {
73 this.ensureCreated();
47 this.ensureCreated(scope || getScope());
74 return this._getDomNode();
48 return this._getDomNode();
75 }
49 }
76
50
@@ -82,36 +56,18 export abstract class RenditionBase<TNod
82 * @param {DojoNodePosition} position Optional parameter, specifies the
56 * @param {DojoNodePosition} position Optional parameter, specifies the
83 * position relative to refNode. Default is "last" (i.e. last child).
57 * position relative to refNode. Default is "last" (i.e. last child).
84 */
58 */
85 placeAt(refNode: string | Node, position?: DojoNodePosition) {
59 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
86 const domNode = this.getDomNode();
60 const domNode = this.getDomNode();
87
61
88 const collect = (collection: HTMLCollection) => {
62 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode];
89 const items = [];
90 for (let i = 0, n = collection.length; i < n; i++) {
91 items.push(collection[i]);
92 }
93 return items;
94 };
95
63
96 const startup = (node: Node) => {
64 placeAt(domNode, refNode, position);
97 if (node.parentNode) {
98 const parentWidget = registry.getEnclosingWidget(node.parentNode);
99 if (parentWidget && parentWidget._started)
100 return startupWidgets(node);
101 }
102 if (isInPage(node))
103 startupWidgets(node);
104 };
105
65
106 const startupPending = isDocumentFragmentNode(domNode) ? collect(domNode.children) : [domNode];
66 startupPending.forEach(autostartWidgets);
107
108 dom.place(domNode, refNode, position);
109
110 startupPending.forEach(startup);
111
67
112 }
68 }
113
69
114 protected abstract _create(attrs: object, children: any[]): void;
70 protected abstract _create(attrs: object, children: unknown[], scope: IScope): void;
115
71
116 protected abstract _getDomNode(): TNode;
72 protected abstract _getDomNode(): TNode;
117 }
73 }
@@ -1,11 +1,10
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 { place } from "dojo/dom-construct";
4 import { render } from "./Renderer";
5 import { getScope, render } from "./Renderer";
6 import { RenditionBase } from "./RenditionBase";
5 import { RenditionBase } from "./RenditionBase";
7 import { Scope } from "./Scope";
6 import { IScope, Scope } from "./Scope";
8 import { locateNode } from "./traits";
7 import { Observable } from "../observable";
9
8
10 const trace = TraceSource.get(mid);
9 const trace = TraceSource.get(mid);
11
10
@@ -16,36 +15,33 export class WatchRendition<T> extends R
16
15
17 private readonly _scope = new Scope();
16 private readonly _scope = new Scope();
18
17
19 constructor(component: (arg: T) => any, subject: any) {
18 private readonly _subject: Observable<T>;
19
20 constructor(component: (arg: T) => any, subject: Observable<T>) {
20 super();
21 super();
21 argumentNotNull(component, "component");
22 argumentNotNull(component, "component");
22
23
23 this._factory = component;
24 this._factory = component;
24
25
26 this._subject = subject;
27
25 this._node = document.createComment("WatchRendition placeholder");
28 this._node = document.createComment("WatchRendition placeholder");
26 }
29 }
27
30
28 protected _create(attrs: object, children: any[]) {
31 protected _create(attrs: object, children: any[], scope: IScope) {
29 const _attrs: any = attrs || {};
30 const _children = children.map(x => this.getItemDom(x));
31 this._node = this.getItemDom(
32 this._factory.call(null, { ..._attrs, children: _children })
33 );
34
35 const scope = getScope();
36 scope.own(this._scope);
32 scope.own(this._scope);
37
33 scope.own(this._subject.on({ next: this._onValue }))
38 // если отрендерили текст? или DocumentFragment
39 }
34 }
40
35
36 private _onValue = (value: T) => void this._render(value).catch( e => trace.error(e));
37
41 private async _render(value: T) {
38 private async _render(value: T) {
42 const [refNode, position] = locateNode(this._node);
39 const prevNode = this._node;
43 this._scope.clean();
40 this._scope.clean();
44
41
45 this._node = await render(() => this._factory(value), this._scope);
42 this._node = await render(() => this._factory(value), this._scope);
46
43
47 if (refNode)
44 this.placeAt(prevNode, "replace");
48 place(this._node, refNode, position);
49 }
45 }
50
46
51 protected _getDomNode() {
47 protected _getDomNode() {
@@ -1,9 +1,10
1 import dom = require("dojo/dom-construct");
2 import { argumentNotNull } from "@implab/core-amd/safe";
1 import { argumentNotNull } from "@implab/core-amd/safe";
3 import { RenditionBase } from "./RenditionBase";
2 import { getItemDom, RenditionBase } from "./RenditionBase";
4 import { DojoNodePosition, isElementNode, isInPage, isWidget } from "./traits";
3 import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits";
5 import registry = require("dijit/registry");
4 import registry = require("dijit/registry");
6 import ContentPane = require("dijit/layout/ContentPane");
5 import ContentPane = require("dijit/layout/ContentPane");
6 import { IScope } from "./Scope";
7 import { getScope } from "./Renderer";
7
8
8 // tslint:disable-next-line: class-name
9 // tslint:disable-next-line: class-name
9 export interface _Widget {
10 export interface _Widget {
@@ -31,17 +32,17 export class WidgetRendition extends Ren
31 this.widgetClass = widgetClass;
32 this.widgetClass = widgetClass;
32 }
33 }
33
34
34 _addChild(child: any): void {
35 _addChild(child: any, scope: IScope): void {
35 const instance = this._getInstance();
36 const instance = this._getInstance();
36
37
37 if (instance.addChild) {
38 if (instance.addChild) {
38 if (child instanceof WidgetRendition) {
39 if (child instanceof WidgetRendition) {
39 // layout containers add custom logic to addChild methods
40 // layout containers add custom logic to addChild methods
40 instance.addChild(child.getWidgetInstance());
41 instance.addChild(child.getWidgetInstance(scope));
41 } else if (isWidget(child)) {
42 } else if (isWidget(child)) {
42 instance.addChild(child);
43 instance.addChild(child);
43 } else {
44 } else {
44 const childDom = this.getItemDom(child);
45 const childDom = getItemDom(child, scope);
45 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
46 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
46
47
47 if (w) {
48 if (w) {
@@ -51,7 +52,7 export class WidgetRendition extends Ren
51 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");
52
53
53 // 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
54 dom.place(this.getItemDom(child), instance.containerNode);
55 placeAt(getItemDom(child,scope), instance.containerNode, "last");
55 }
56 }
56 }
57 }
57 } else {
58 } else {
@@ -59,11 +60,11 export class WidgetRendition extends Ren
59 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");
60
61
61 // 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
62 dom.place(this.getItemDom(child), instance.containerNode);
63 placeAt(getItemDom(child, scope), instance.containerNode, "last");
63 }
64 }
64 }
65 }
65
66
66 protected _create(attrs: any, children: any[]) {
67 protected _create(attrs: any, children: any[], scope: IScope) {
67 if (this.widgetClass.prototype instanceof ContentPane) {
68 if (this.widgetClass.prototype instanceof ContentPane) {
68 // a special case for the ContentPane this is for
69 // a special case for the ContentPane this is for
69 // the compatibility with this heavy widget, all
70 // the compatibility with this heavy widget, all
@@ -72,14 +73,14 export class WidgetRendition extends Ren
72
73
73 // render children to the DocumentFragment
74 // render children to the DocumentFragment
74 const content = document.createDocumentFragment();
75 const content = document.createDocumentFragment();
75 children.forEach(child => content.appendChild(this.getItemDom(child)));
76 children.forEach(child => content.appendChild(getItemDom(child, scope)));
76
77
77 // set the content property to the parameters of the widget
78 // set the content property to the parameters of the widget
78 const _attrs = { ...attrs, content };
79 const _attrs = { ...attrs, content };
79 this._instance = new this.widgetClass(_attrs);
80 this._instance = new this.widgetClass(_attrs);
80 } else {
81 } else {
81 this._instance = new this.widgetClass(attrs);
82 this._instance = new this.widgetClass(attrs);
82 children.forEach(x => this._addChild(x));
83 children.forEach(x => this._addChild(x, scope));
83 }
84 }
84
85
85 }
86 }
@@ -103,7 +104,7 export class WidgetRendition extends Ren
103 * @param position A position relative to refNode.
104 * @param position A position relative to refNode.
104 */
105 */
105 placeAt(refNode: string | Node, position?: DojoNodePosition) {
106 placeAt(refNode: string | Node, position?: DojoNodePosition) {
106 this.ensureCreated();
107 this.ensureCreated(getScope());
107 const instance = this._getInstance();
108 const instance = this._getInstance();
108 if (typeof instance.placeAt === "function") {
109 if (typeof instance.placeAt === "function") {
109 instance.placeAt(refNode, position);
110 instance.placeAt(refNode, position);
@@ -120,8 +121,8 export class WidgetRendition extends Ren
120 }
121 }
121 }
122 }
122
123
123 getWidgetInstance() {
124 getWidgetInstance(scope?: IScope) {
124 this.ensureCreated();
125 this.ensureCreated(scope || getScope());
125 return this._getInstance();
126 return this._getInstance();
126 }
127 }
127
128
@@ -2,16 +2,16 import { IDestroyable } from "@implab/co
2 import { isDestroyable } from "@implab/core-amd/safe";
2 import { isDestroyable } from "@implab/core-amd/safe";
3 import _WidgetBase = require("dijit/_WidgetBase");
3 import _WidgetBase = require("dijit/_WidgetBase");
4 import registry = require("dijit/registry");
4 import registry = require("dijit/registry");
5 import dom = require("dojo/dom-construct");
5 import { IScope } from "./Scope";
6
6
7 type _WidgetBaseConstructor = typeof _WidgetBase;
7 type _WidgetBaseConstructor = typeof _WidgetBase;
8
8
9 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
9 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
10
10
11 export type DojoNodeLocation = [Node | null, DojoNodePosition];
11 export type DojoNodeLocation = [Node, DojoNodePosition];
12
12
13 export interface Rendition<TNode extends Node = Node> {
13 export interface Rendition<TNode extends Node = Node> {
14 getDomNode(): TNode;
14 getDomNode(scope?: IScope): TNode;
15
15
16 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
16 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
17 }
17 }
@@ -25,52 +25,32 export interface IRecursivelyDestroyable
25 destroyRecursive(): void;
25 destroyRecursive(): void;
26 }
26 }
27
27
28 export function isNode(el: any): el is Node {
28 export const isNode = (el: unknown): el is Node => !!(el && (el as Node).nodeName && (el as Node).nodeType);
29 return el && el.nodeName && el.nodeType;
30 }
31
29
32 export function isElementNode(el: any): el is Element {
30 export const isElementNode = (el: unknown): el is Element => isNode(el) && el.nodeType === 1;
33 return isNode(el) && el.nodeType === 1;
34 }
35
31
36 export function isTextNode(el: any): el is Text {
32 export const isTextNode = (el: unknown): el is Text => isNode(el) && el.nodeType === 3;
37 return isNode(el) && el.nodeType === 3;
38 }
39
33
40 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
34 export const isProcessingInstructionNode = (el: unknown): el is ProcessingInstruction => isNode(el) && el.nodeType === 7;
41 return isNode(el) && el.nodeType === 7;
42 }
43
35
44 export function isCommentNode(el: any): el is Comment {
36 export const isCommentNode = (el: unknown): el is Comment => isNode(el) && el.nodeType === 8;
45 return isNode(el) && el.nodeType === 8;
46 }
47
37
48 export function isDocumentNode(el: any): el is Document {
38 export const isDocumentNode = (el: unknown): el is Document => isNode(el) && el.nodeType === 9;
49 return isNode(el) && el.nodeType === 9;
50 }
51
39
52 export function isDocumentTypeNode(el: any): el is DocumentType {
40 export const isDocumentTypeNode = (el: unknown): el is DocumentType => isNode(el) && el.nodeType === 10;
53 return isNode(el) && el.nodeType === 10;
54 }
55
41
56 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
42 export const isDocumentFragmentNode = (el: any): el is DocumentFragment => isNode(el) && el.nodeType === 11;
57 return isNode(el) && el.nodeType === 11;
58 }
59
43
60 export function isWidget(v: any): v is _WidgetBase {
44 export const isWidget = (v: unknown): v is _WidgetBase => !!(v && "domNode" in (v as _WidgetBase));
61 return v && "domNode" in v;
62 }
63
45
64 export function isRendition(v: any): v is Rendition {
46 export const isRendition = (v: unknown): v is Rendition => !!(v && typeof (v as Rendition).getDomNode === "function");
65 return v && typeof v.getDomElement === "function";
66 }
67
47
68 /**
48 /**
69 * @deprecated use isRendition
49 * @deprecated use isRendition
70 */
50 */
71 export const isBuildContext = isRendition;
51 export const isBuildContext = isRendition;
72
52
73 export function isPlainObject(v: object) {
53 export const isPlainObject = (v: object) => {
74 if (typeof v !== "object")
54 if (typeof v !== "object")
75 return false;
55 return false;
76
56
@@ -78,23 +58,21 export function isPlainObject(v: object)
78 return !vp || vp === Object.prototype;
58 return !vp || vp === Object.prototype;
79 }
59 }
80
60
81 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
61 export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor =>
82 return typeof v === "function" && v.prototype && (
62 typeof v === "function" && v.prototype && (
83 "domNode" in v.prototype ||
63 "domNode" in v.prototype ||
84 "buildRendering" in v.prototype
64 "buildRendering" in v.prototype
85 );
65 );
86 }
66
87
67
88 /** Tests whether the specified node is placed in visible dom.
68 /** Tests whether the specified node is placed in visible dom.
89 * @param {Node} node The node to test
69 * @param {Node} node The node to test
90 */
70 */
91 export function isInPage(node: Node) {
71 export const isInPage = (node: Node) => node === document.body ? false : document.body.contains(node);
92 return (node === document.body) ? false : document.body.contains(node);
93 }
94
72
95 export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable {
73 export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable =>
96 return target && typeof target.destroyRecursive === "function";
74 !!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function");
97 }
75
98
76
99
77
100 /** Destroys DOM Node with all contained widgets.
78 /** Destroys DOM Node with all contained widgets.
@@ -103,7 +81,7 export function isRecursivelyDestroyable
103 *
81 *
104 * @param target DOM Node or widget to destroy
82 * @param target DOM Node or widget to destroy
105 */
83 */
106 export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) {
84 export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => {
107 if (isRecursivelyDestroyable(target)) {
85 if (isRecursivelyDestroyable(target)) {
108 target.destroyRecursive();
86 target.destroyRecursive();
109 } else if (isDestroyable(target)) {
87 } else if (isDestroyable(target)) {
@@ -114,8 +92,10 export function destroy(target: Node | I
114 if (w) {
92 if (w) {
115 w.destroyRecursive();
93 w.destroyRecursive();
116 } else {
94 } else {
117 registry.findWidgets(target).forEach(destroy);
95 emptyNode(target);
118 dom.destroy(target);
96 const parent = target.parentNode;
97 if (parent)
98 parent.removeChild(target);
119 }
99 }
120 }
100 }
121 }
101 }
@@ -123,11 +103,14 export function destroy(target: Node | I
123
103
124 /** Empties a content of the specified node and destroys all contained widgets.
104 /** Empties a content of the specified node and destroys all contained widgets.
125 *
105 *
126 * @param target DOM node to .
106 * @param target DOM node to empty.
127 */
107 */
128 export function emptyNode(target: Node) {
108 export const emptyNode = (target: Node) => {
129 registry.findWidgets(target).forEach(destroy);
109 registry.findWidgets(target).forEach(destroy);
130 dom.empty(target);
110
111 for(let c; c = target.lastChild;){ // intentional assignment
112 target.removeChild(c);
113 }
131 }
114 }
132
115
133 /** This function starts all widgets inside the DOM node if the target is a node
116 /** This function starts all widgets inside the DOM node if the target is a node
@@ -136,7 +119,7 export function emptyNode(target: Node)
136 *
119 *
137 * @param target DOM node to find and start widgets or the widget itself.
120 * @param target DOM node to find and start widgets or the widget itself.
138 */
121 */
139 export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) {
122 export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => {
140 if (isNode(target)) {
123 if (isNode(target)) {
141 if (isElementNode(target)) {
124 if (isElementNode(target)) {
142 const w = registry.byNode(target);
125 const w = registry.byNode(target);
@@ -153,15 +136,62 export function startupWidgets(target: N
153 }
136 }
154 }
137 }
155
138
156 export function locateNode(node: Node): DojoNodeLocation {
139 /** Places the specified DOM node at the specified location.
157 const next = node.nextSibling;
140 *
158 return next ?
141 * @param node The node which should be placed
159 [next, "before"] :
142 * @param refNodeOrId The reference node where the created
160 [node.parentNode, "last"];
143 * DOM should be placed.
144 * @param position Optional parameter, specifies the
145 * position relative to refNode. Default is "last" (i.e. last child).
146 */
147 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => {
148 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
149 if (!ref)
150 return;
151
152 const parent = ref.parentNode;
153
154 const insertBefore = (node: Node, refNode: Node | null) => parent && parent.insertBefore(node, refNode);
155
156 if (typeof position == "number") {
157 if (ref.childNodes.length <= position) {
158 ref.appendChild(node);
159 } else {
160 ref.insertBefore(node, ref.childNodes[position]);
161 }
162 } else {
163 switch (position) {
164 case "before":
165 insertBefore(node, ref);
166 break;
167 case "after":
168 insertBefore(node, ref.nextSibling);
169 break;
170 case "first":
171 insertBefore(node, parent && parent.firstChild);
172 break;
173 case "last":
174 insertBefore(node, null);
175 break;
176 case "only":
177 emptyNode(ref);
178 ref.appendChild(node);
179 break;
180 case "replace":
181 if (parent)
182 parent.replaceChild(node, ref);
183 destroy(ref);
184 break;
185 }
186 }
161 }
187 }
162
188
163 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition) => {
189 /** Collects nodes from collection to an array.
164 const collect = (collection: HTMLCollection) => {
190 *
191 * @param collection The collection of nodes.
192 * @returns The array of nodes.
193 */
194 export const collectNodes = (collection: HTMLCollection) => {
165 const items = [];
195 const items = [];
166 for (let i = 0, n = collection.length; i < n; i++) {
196 for (let i = 0, n = collection.length; i < n; i++) {
167 items.push(collection[i]);
197 items.push(collection[i]);
@@ -169,7 +199,11 export const placeAt = (node: Node, refN
169 return items;
199 return items;
170 };
200 };
171
201
172 const startup = (node: Node) => {
202 /** Starts widgets if the node contained in the document or in the started widget.
203 *
204 * @param node The node to start.
205 */
206 export const autostartWidgets = (node: Node) => {
173 if (node.parentNode) {
207 if (node.parentNode) {
174 const parentWidget = registry.getEnclosingWidget(node.parentNode);
208 const parentWidget = registry.getEnclosingWidget(node.parentNode);
175 if (parentWidget && parentWidget._started)
209 if (parentWidget && parentWidget._started)
@@ -177,30 +211,4 export const placeAt = (node: Node, refN
177 }
211 }
178 if (isInPage(node))
212 if (isInPage(node))
179 startupWidgets(node);
213 startupWidgets(node);
180 };
214 }; No newline at end of file
181
182 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
183 if (!ref)
184 return;
185
186 const parent = ref.parentNode;
187
188 if (typeof position == "number") {
189
190 } else {
191 switch(position) {
192 case "before":
193 if (parent)
194 parent.insertBefore(node,ref);
195 case "after":
196 if (parent)
197 parent.insertBefore(node, ref.nextSibling);
198 }
199 }
200
201 const startupPending = isDocumentFragmentNode(node) ? collect(node.children) : [node];
202
203 dom.place(node, refNodeOrId, position);
204
205 startupPending.forEach(startup);
206 } No newline at end of file
@@ -2,7 +2,9
2
2
3 declare namespace JSX {
3 declare namespace JSX {
4
4
5 interface DjxIntrinsicAttributes {
5 type Ref<T> = (value: T) => void;
6
7 interface DjxIntrinsicAttributes<E> {
6 /** alias for className */
8 /** alias for className */
7 class: string;
9 class: string;
8
10
@@ -14,6 +16,8 declare namespace JSX {
14 /** specifies handlers map for the events */
16 /** specifies handlers map for the events */
15 "data-dojo-attach-event": string;
17 "data-dojo-attach-event": string;
16
18
19 ref: Ref<E>;
20
17 /** @deprecated */
21 /** @deprecated */
18 [attr: string]: any;
22 [attr: string]: any;
19 }
23 }
@@ -56,7 +60,7 declare namespace JSX {
56
60
57 type LaxElement<E extends object> =
61 type LaxElement<E extends object> =
58 Pick<E, AssignableElementAttrNames<E>> &
62 Pick<E, AssignableElementAttrNames<E>> &
59 DjxIntrinsicAttributes;
63 DjxIntrinsicAttributes<E>;
60
64
61 type LaxIntrinsicElementsMap = {
65 type LaxIntrinsicElementsMap = {
62 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
66 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
@@ -65,4 +69,12 declare namespace JSX {
65 type IntrinsicElements = {
69 type IntrinsicElements = {
66 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
70 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
67 }
71 }
72
73 interface ElementChildrenAttribute {
74 children: {};
68 }
75 }
76
77 interface IntrinsicClassAttributes<T> {
78 ref: (value: T) => void;
79 }
80 }
@@ -30,10 +30,10 export class MyWidget extends djbase(Djx
30 counter = 0;
30 counter = 0;
31
31
32 render() {
32 render() {
33 const Frame = (props: any) => <div>{props.children}</div>;
33 const Frame = ({children, ref}: {ref: JSX.Ref<HTMLDivElement>, children: any[]}) => <div ref={ref} >{children}</div>;
34 return <div className="myWidget" onsubmit={e => this._onSubmit(e)} tabIndex={3} style={{ alignContent: "center", border: "1px solid" }} >
34 return <div className="myWidget" onsubmit={e => this._onSubmit(e)} tabIndex={3} style={{ alignContent: "center", border: "1px solid" }} >
35 <h1 data-dojo-attach-point="titleNode"></h1>
35 <h1 data-dojo-attach-point="titleNode"></h1>
36 <Frame>
36 <Frame ref={ v => {}}>
37 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
37 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
38 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
38 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
39 </Frame>
39 </Frame>
General Comments 0
You need to be logged in to leave comments. Login now