##// END OF EJS Templates
fixed cyclic module references tsx/traits, tsx/FunctionRendition, tsx/RenditionBase
cin -
r89:367f8caa5bf8 v1.2.8 default
parent child
Show More
@@ -1,40 +1,119
1 import { Constructor } from "@implab/core-amd/interfaces";
1 import { Constructor, IDestroyable, IRemovable } 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 { isWidgetConstructor, Rendition } from "./tsx/traits";
4 import { destroy, isWidgetConstructor, Rendition } from "./tsx/traits";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
6 import Stateful = require("dojo/Stateful");
7 import _WidgetBase = require("dijit/_WidgetBase");
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
6
9
7 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
10 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
8 if (typeof elementType === "string") {
11 if (typeof elementType === "string") {
9 const ctx = new HtmlRendition(elementType);
12 const ctx = new HtmlRendition(elementType);
10 if (args)
13 if (args)
11 args.forEach(x => ctx.visitNext(x));
14 args.forEach(x => ctx.visitNext(x));
12
15
13 return ctx;
16 return ctx;
14 } else if (isWidgetConstructor(elementType)) {
17 } else if (isWidgetConstructor(elementType)) {
15 const ctx = new WidgetRendition(elementType);
18 const ctx = new WidgetRendition(elementType);
16 if (args)
19 if (args)
17 args.forEach(x => ctx.visitNext(x));
20 args.forEach(x => ctx.visitNext(x));
18
21
19 return ctx;
22 return ctx;
20 } else if (typeof elementType === "function") {
23 } else if (typeof elementType === "function") {
21 const ctx = new FunctionRendition(elementType as (props: any) => Element);
24 const ctx = new FunctionRendition(elementType as (props: any) => Element);
22 if (args)
25 if (args)
23 args.forEach(x => ctx.visitNext(x));
26 args.forEach(x => ctx.visitNext(x));
24
27
25 return ctx;
28 return ctx;
26 } else {
29 } else {
27 throw new Error(`The element type '${elementType}' is unsupported`);
30 throw new Error(`The element type '${elementType}' is unsupported`);
28 }
31 }
29 }
32 }
30
33
31 export interface EventDetails<T = any> {
34 export interface EventDetails<T = any> {
32 detail: T;
35 detail: T;
33 }
36 }
34
37
35 export interface EventSelector {
38 export interface EventSelector {
36 selectorTarget: HTMLElement;
39 selectorTarget: HTMLElement;
37 target: HTMLElement;
40 target: HTMLElement;
38 }
41 }
39
42
40 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
43 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
44
45 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
46
47 type CleanFn = (instance: IRemovable | IDestroyable) => void;
48
49 /**
50 * Observers the property and calls render callback each change.
51 *
52 * @param target The target object which property will be observed.
53 * @param prop The name of the property.
54 * @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
57 */
58 export function watch<W extends _WidgetBase, K extends keyof W>(
59 target: W,
60 prop: K,
61 render: (model: W[K]) => any,
62 cleanupOrOwner?: { own: CleanFn } | CleanFn
63 ): Rendition;
64 /**
65 * Observers the property and calls render callback each change.
66 *
67 * @param target The target object which property will be observed.
68 * @param prop The name of the property.
69 * @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
72 */
73 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
74 target: T,
75 prop: K,
76 render: (model: StatefulProps<T>[K]) => any,
77 cleanupOrOwner?: { own: CleanFn } | CleanFn
78 ): Rendition;
79 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
80 target: T,
81 prop: K,
82 render: (model: StatefulProps<T>[K]) => any,
83 cleanupOrOwner: { own: CleanFn } | CleanFn = () => { }
84 ) {
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;
96 }
97
98 /** Decorates the method which will be registered as the handle for the specified event.
99 * This decorator can be applied to DjxWidgetBase subclass methods.
100 *
101 * ```
102 * @on("click")
103 * _onClick(eventObj: MouseEvent) {
104 * // ...
105 * }
106 * ```
107 */
108 export const on = <E extends string>(...eventNames: E[]) =>
109 <K extends string,
110 T extends DjxWidgetBase<any, { [p in E]: EV }>,
111 EV extends Event
112 >(
113 target: T,
114 key: K,
115 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
116 ): any => {
117 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }))
118 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
119 };
@@ -1,228 +1,148
1 import { IDestroyable, IRemovable } from "@implab/core-amd/interfaces";
1 import { IDestroyable } from "@implab/core-amd/interfaces";
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 dom = require("dojo/dom-construct");
6 import Stateful = require("dojo/Stateful");
7 import { FunctionRendition } from "./FunctionRendition";
8 import { DjxWidgetBase } from "./DjxWidgetBase";
9
6
10 type _WidgetBaseConstructor = typeof _WidgetBase;
7 type _WidgetBaseConstructor = typeof _WidgetBase;
11
8
12 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
9 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
13
10
14 export interface Rendition<TNode extends Node = Node> {
11 export interface Rendition<TNode extends Node = Node> {
15 getDomNode(): TNode;
12 getDomNode(): TNode;
16
13
17 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
14 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
18 }
15 }
19
16
20 /**
17 /**
21 * @deprecated use Rendition
18 * @deprecated use Rendition
22 */
19 */
23 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
20 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
24
21
25 export interface IRecursivelyDestroyable {
22 export interface IRecursivelyDestroyable {
26 destroyRecursive(): void;
23 destroyRecursive(): void;
27 }
24 }
28
25
29 export function isNode(el: any): el is Node {
26 export function isNode(el: any): el is Node {
30 return el && el.nodeName && el.nodeType;
27 return el && el.nodeName && el.nodeType;
31 }
28 }
32
29
33 export function isElementNode(el: any): el is Element {
30 export function isElementNode(el: any): el is Element {
34 return isNode(el) && el.nodeType === 1;
31 return isNode(el) && el.nodeType === 1;
35 }
32 }
36
33
37 export function isTextNode(el: any): el is Text {
34 export function isTextNode(el: any): el is Text {
38 return isNode(el) && el.nodeType === 3;
35 return isNode(el) && el.nodeType === 3;
39 }
36 }
40
37
41 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
38 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
42 return isNode(el) && el.nodeType === 7;
39 return isNode(el) && el.nodeType === 7;
43 }
40 }
44
41
45 export function isCommentNode(el: any): el is Comment {
42 export function isCommentNode(el: any): el is Comment {
46 return isNode(el) && el.nodeType === 8;
43 return isNode(el) && el.nodeType === 8;
47 }
44 }
48
45
49 export function isDocumentNode(el: any): el is Document {
46 export function isDocumentNode(el: any): el is Document {
50 return isNode(el) && el.nodeType === 9;
47 return isNode(el) && el.nodeType === 9;
51 }
48 }
52
49
53 export function isDocumentTypeNode(el: any): el is DocumentType {
50 export function isDocumentTypeNode(el: any): el is DocumentType {
54 return isNode(el) && el.nodeType === 10;
51 return isNode(el) && el.nodeType === 10;
55 }
52 }
56
53
57 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
54 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
58 return isNode(el) && el.nodeType === 11;
55 return isNode(el) && el.nodeType === 11;
59 }
56 }
60
57
61 export function isWidget(v: any): v is _WidgetBase {
58 export function isWidget(v: any): v is _WidgetBase {
62 return v && "domNode" in v;
59 return v && "domNode" in v;
63 }
60 }
64
61
65 export function isRendition(v: any): v is Rendition {
62 export function isRendition(v: any): v is Rendition {
66 return v && typeof v.getDomElement === "function";
63 return v && typeof v.getDomElement === "function";
67 }
64 }
68
65
69 /**
66 /**
70 * @deprecated use isRendition
67 * @deprecated use isRendition
71 */
68 */
72 export const isBuildContext = isRendition;
69 export const isBuildContext = isRendition;
73
70
74 export function isPlainObject(v: object) {
71 export function isPlainObject(v: object) {
75 if (typeof v !== "object")
72 if (typeof v !== "object")
76 return false;
73 return false;
77
74
78 const vp = Object.getPrototypeOf(v);
75 const vp = Object.getPrototypeOf(v);
79 return !vp || vp === Object.prototype;
76 return !vp || vp === Object.prototype;
80 }
77 }
81
78
82 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
79 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
83 return typeof v === "function" && v.prototype && (
80 return typeof v === "function" && v.prototype && (
84 "domNode" in v.prototype ||
81 "domNode" in v.prototype ||
85 "buildRendering" in v.prototype
82 "buildRendering" in v.prototype
86 );
83 );
87 }
84 }
88
85
89 /** Tests whether the specified node is placed in visible dom.
86 /** Tests whether the specified node is placed in visible dom.
90 * @param {Node} node The node to test
87 * @param {Node} node The node to test
91 */
88 */
92 export function isInPage(node: Node) {
89 export function isInPage(node: Node) {
93 return (node === document.body) ? false : document.body.contains(node);
90 return (node === document.body) ? false : document.body.contains(node);
94 }
91 }
95
92
96 export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable {
93 export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable {
97 return target && typeof target.destroyRecursive === "function";
94 return target && typeof target.destroyRecursive === "function";
98 }
95 }
99
96
100
97
101 /** Destroys DOM Node with all contained widgets.
98 /** Destroys DOM Node with all contained widgets.
102 * If the specified node is the root node of a widget, then the
99 * If the specified node is the root node of a widget, then the
103 * widget will be destroyed.
100 * widget will be destroyed.
104 *
101 *
105 * @param target DOM Node or widget to destroy
102 * @param target DOM Node or widget to destroy
106 */
103 */
107 export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) {
104 export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) {
108 if (isRecursivelyDestroyable(target)) {
105 if (isRecursivelyDestroyable(target)) {
109 target.destroyRecursive();
106 target.destroyRecursive();
110 } else if (isDestroyable(target)) {
107 } else if (isDestroyable(target)) {
111 target.destroy();
108 target.destroy();
112 } else if (isNode(target)) {
109 } else if (isNode(target)) {
113 const w = isElementNode(target) ? registry.byNode(target) : undefined;
110 const w = isElementNode(target) ? registry.byNode(target) : undefined;
114 if (w) {
111 if (w) {
115 w.destroyRecursive();
112 w.destroyRecursive();
116 } else {
113 } else {
117 registry.findWidgets(target).forEach(destroy);
114 registry.findWidgets(target).forEach(destroy);
118 dom.destroy(target);
115 dom.destroy(target);
119 }
116 }
120 }
117 }
121 }
118 }
122
119
123 /** Empties a content of the specified node and destroys all contained widgets.
120 /** Empties a content of the specified node and destroys all contained widgets.
124 *
121 *
125 * @param target DOM node to .
122 * @param target DOM node to .
126 */
123 */
127 export function emptyNode(target: Node) {
124 export function emptyNode(target: Node) {
128 registry.findWidgets(target).forEach(destroy);
125 registry.findWidgets(target).forEach(destroy);
129 dom.empty(target);
126 dom.empty(target);
130 }
127 }
131
128
132 /** This function starts all widgets inside the DOM node if the target is a node
129 /** This function starts all widgets inside the DOM node if the target is a node
133 * or starts widget itself if the target is the widget. If the specified node
130 * or starts widget itself if the target is the widget. If the specified node
134 * associated with the widget that widget will be started.
131 * associated with the widget that widget will be started.
135 *
132 *
136 * @param target DOM node to find and start widgets or the widget itself.
133 * @param target DOM node to find and start widgets or the widget itself.
137 */
134 */
138 export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) {
135 export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) {
139 if (isNode(target)) {
136 if (isNode(target)) {
140 const w = isElementNode(target) ? registry.byNode(target) : undefined;
137 const w = isElementNode(target) ? registry.byNode(target) : undefined;
141 if (w) {
138 if (w) {
142 if (w.startup)
139 if (w.startup)
143 w.startup();
140 w.startup();
144 } else {
141 } else {
145 registry.findWidgets(target, skipNode).forEach(x => x.startup());
142 registry.findWidgets(target, skipNode).forEach(x => x.startup());
146 }
143 }
147 } else {
144 } else {
148 if (target.startup)
145 if (target.startup)
149 target.startup();
146 target.startup();
150 }
147 }
151 }
148 }
152
153
154 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
155
156 type CleanFn = (instance: IRemovable | IDestroyable) => void;
157
158 /**
159 * Observers the property and calls render callback each change.
160 *
161 * @param target The target object which property will be observed.
162 * @param prop The name of the property.
163 * @param render The callback which will be called every time the value is changed
164 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
165 * @returns Rendition which is created instantly
166 */
167 export function watch<W extends _WidgetBase, K extends keyof W>(
168 target: W,
169 prop: K,
170 render: (model: W[K]) => any,
171 cleanupOrOwner?: { own: CleanFn } | CleanFn
172 ): Rendition;
173 /**
174 * Observers the property and calls render callback each change.
175 *
176 * @param target The target object which property will be observed.
177 * @param prop The name of the property.
178 * @param render The callback which will be called every time the value is changed
179 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
180 * @returns Rendition which is created instantly
181 */
182 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
183 target: T,
184 prop: K,
185 render: (model: StatefulProps<T>[K]) => any,
186 cleanupOrOwner?: { own: CleanFn } | CleanFn
187 ): Rendition;
188 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
189 target: T,
190 prop: K,
191 render: (model: StatefulProps<T>[K]) => any,
192 cleanupOrOwner: { own: CleanFn } | CleanFn = () => { }
193 ) {
194 let rendition = new FunctionRendition(() => render(target.get(prop)));
195 const _own = cleanupOrOwner instanceof Function ? cleanupOrOwner : (x: IRemovable) => cleanupOrOwner.own(x);
196 _own(target.watch(prop, (_name, oldValue, newValue) => {
197 if (oldValue !== newValue) {
198 const newRendition = new FunctionRendition(() => render(newValue));
199 newRendition.placeAt(rendition.getDomNode(), "replace");
200 destroy(rendition.getDomNode());
201 rendition = newRendition;
202 }
203 }));
204 return rendition;
205 }
206
207 /** Decorates the method which will be registered as the handle for the specified event.
208 * This decorator can be applied to DjxWidgetBase subclass methods.
209 *
210 * ```
211 * @on("click")
212 * _onClick(eventObj: MouseEvent) {
213 * // ...
214 * }
215 * ```
216 */
217 export const on = <E extends string>(...eventNames: E[]) =>
218 <K extends string,
219 T extends DjxWidgetBase<any, { [p in E]: EV }>,
220 EV extends Event
221 >(
222 target: T,
223 key: K,
224 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
225 ): any => {
226 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }))
227 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
228 };
@@ -1,71 +1,70
1 import { djbase, djclass, bind, prototype, AbstractConstructor } from "../declare";
1 import { djbase, djclass, bind, prototype, AbstractConstructor } from "../declare";
2
2
3 import { DjxWidgetBase } from "../tsx/DjxWidgetBase";
3 import { DjxWidgetBase } from "../tsx/DjxWidgetBase";
4 import { createElement } from "../tsx";
4 import { createElement, on } from "../tsx";
5 import { on } from "../tsx/traits";
6
5
7 interface MyWidgetAttrs {
6 interface MyWidgetAttrs {
8 title: string;
7 title: string;
9
8
10 counter: number;
9 counter: number;
11 }
10 }
12
11
13 interface MyWidgetEvents {
12 interface MyWidgetEvents {
14 "count-inc": Event & {
13 "count-inc": Event & {
15 detail: number;
14 detail: number;
16 };
15 };
17
16
18 "count-dec": Event & {
17 "count-dec": Event & {
19 detail: number;
18 detail: number;
20 };
19 };
21 }
20 }
22
21
23
22
24 @djclass
23 @djclass
25 export class MyWidget extends djbase(DjxWidgetBase as AbstractConstructor<DjxWidgetBase<MyWidgetAttrs, MyWidgetEvents>>) {
24 export class MyWidget extends djbase(DjxWidgetBase as AbstractConstructor<DjxWidgetBase<MyWidgetAttrs, MyWidgetEvents>>) {
26
25
27 @bind({ node: "titleNode", type: "innerHTML" })
26 @bind({ node: "titleNode", type: "innerHTML" })
28 title = "";
27 title = "";
29
28
30 @prototype()
29 @prototype()
31 counter = 0;
30 counter = 0;
32
31
33 render() {
32 render() {
34 const Frame = (props: any) => <div>{props.children}</div>;
33 const Frame = (props: any) => <div>{props.children}</div>;
35 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" }} >
36 <h1 data-dojo-attach-point="titleNode"></h1>
35 <h1 data-dojo-attach-point="titleNode"></h1>
37 <Frame>
36 <Frame>
38 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
37 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
39 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
38 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
40 </Frame>
39 </Frame>
41 </div>;
40 </div>;
42 }
41 }
43
42
44 postCreate() {
43 postCreate() {
45 super.postCreate();
44 super.postCreate();
46
45
47 this.on("click", () => {});
46 this.on("click", () => {});
48 }
47 }
49
48
50 _onSubmit(e: Event) {
49 _onSubmit(e: Event) {
51 }
50 }
52
51
53 _onIncClick(e: MouseEvent) {
52 _onIncClick(e: MouseEvent) {
54 this.set("counter", this.counter + 1);
53 this.set("counter", this.counter + 1);
55
54
56 this.emit("count-inc", { bubbles: false });
55 this.emit("count-inc", { bubbles: false });
57 }
56 }
58
57
59 _onDecClick() {
58 _onDecClick() {
60 this.emit("count-dec", { bubbles: false, detail: this.counter });
59 this.emit("count-dec", { bubbles: false, detail: this.counter });
61 }
60 }
62
61
63 @on("count-inc")
62 @on("count-inc")
64 _onCounterInc(evt: Event & { detail: number; x?: number; }) {
63 _onCounterInc(evt: Event & { detail: number; x?: number; }) {
65 }
64 }
66
65
67 @on("click", "keydown")
66 @on("click", "keydown")
68 protected _onClick(event: MouseEvent | KeyboardEvent) {
67 protected _onClick(event: MouseEvent | KeyboardEvent) {
69
68
70 }
69 }
71 } No newline at end of file
70 }
General Comments 0
You need to be logged in to leave comments. Login now