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