##// 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 2 import { HtmlRendition } from "./tsx/HtmlRendition";
3 3 import { WidgetRendition } from "./tsx/WidgetRendition";
4 import { isWidgetConstructor, Rendition } from "./tsx/traits";
4 import { destroy, isWidgetConstructor, Rendition } from "./tsx/traits";
5 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 10 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
8 11 if (typeof elementType === "string") {
9 12 const ctx = new HtmlRendition(elementType);
10 13 if (args)
11 14 args.forEach(x => ctx.visitNext(x));
12 15
13 16 return ctx;
14 17 } else if (isWidgetConstructor(elementType)) {
15 18 const ctx = new WidgetRendition(elementType);
16 19 if (args)
17 20 args.forEach(x => ctx.visitNext(x));
18 21
19 22 return ctx;
20 23 } else if (typeof elementType === "function") {
21 24 const ctx = new FunctionRendition(elementType as (props: any) => Element);
22 25 if (args)
23 26 args.forEach(x => ctx.visitNext(x));
24 27
25 28 return ctx;
26 29 } else {
27 30 throw new Error(`The element type '${elementType}' is unsupported`);
28 31 }
29 32 }
30 33
31 34 export interface EventDetails<T = any> {
32 35 detail: T;
33 36 }
34 37
35 38 export interface EventSelector {
36 39 selectorTarget: HTMLElement;
37 40 target: HTMLElement;
38 41 }
39 42
40 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 2 import { isDestroyable } from "@implab/core-amd/safe";
3 3 import _WidgetBase = require("dijit/_WidgetBase");
4 4 import registry = require("dijit/registry");
5 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 7 type _WidgetBaseConstructor = typeof _WidgetBase;
11 8
12 9 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
13 10
14 11 export interface Rendition<TNode extends Node = Node> {
15 12 getDomNode(): TNode;
16 13
17 14 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
18 15 }
19 16
20 17 /**
21 18 * @deprecated use Rendition
22 19 */
23 20 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
24 21
25 22 export interface IRecursivelyDestroyable {
26 23 destroyRecursive(): void;
27 24 }
28 25
29 26 export function isNode(el: any): el is Node {
30 27 return el && el.nodeName && el.nodeType;
31 28 }
32 29
33 30 export function isElementNode(el: any): el is Element {
34 31 return isNode(el) && el.nodeType === 1;
35 32 }
36 33
37 34 export function isTextNode(el: any): el is Text {
38 35 return isNode(el) && el.nodeType === 3;
39 36 }
40 37
41 38 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
42 39 return isNode(el) && el.nodeType === 7;
43 40 }
44 41
45 42 export function isCommentNode(el: any): el is Comment {
46 43 return isNode(el) && el.nodeType === 8;
47 44 }
48 45
49 46 export function isDocumentNode(el: any): el is Document {
50 47 return isNode(el) && el.nodeType === 9;
51 48 }
52 49
53 50 export function isDocumentTypeNode(el: any): el is DocumentType {
54 51 return isNode(el) && el.nodeType === 10;
55 52 }
56 53
57 54 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
58 55 return isNode(el) && el.nodeType === 11;
59 56 }
60 57
61 58 export function isWidget(v: any): v is _WidgetBase {
62 59 return v && "domNode" in v;
63 60 }
64 61
65 62 export function isRendition(v: any): v is Rendition {
66 63 return v && typeof v.getDomElement === "function";
67 64 }
68 65
69 66 /**
70 67 * @deprecated use isRendition
71 68 */
72 69 export const isBuildContext = isRendition;
73 70
74 71 export function isPlainObject(v: object) {
75 72 if (typeof v !== "object")
76 73 return false;
77 74
78 75 const vp = Object.getPrototypeOf(v);
79 76 return !vp || vp === Object.prototype;
80 77 }
81 78
82 79 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
83 80 return typeof v === "function" && v.prototype && (
84 81 "domNode" in v.prototype ||
85 82 "buildRendering" in v.prototype
86 83 );
87 84 }
88 85
89 86 /** Tests whether the specified node is placed in visible dom.
90 87 * @param {Node} node The node to test
91 88 */
92 89 export function isInPage(node: Node) {
93 90 return (node === document.body) ? false : document.body.contains(node);
94 91 }
95 92
96 93 export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable {
97 94 return target && typeof target.destroyRecursive === "function";
98 95 }
99 96
100 97
101 98 /** Destroys DOM Node with all contained widgets.
102 99 * If the specified node is the root node of a widget, then the
103 100 * widget will be destroyed.
104 101 *
105 102 * @param target DOM Node or widget to destroy
106 103 */
107 104 export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) {
108 105 if (isRecursivelyDestroyable(target)) {
109 106 target.destroyRecursive();
110 107 } else if (isDestroyable(target)) {
111 108 target.destroy();
112 109 } else if (isNode(target)) {
113 110 const w = isElementNode(target) ? registry.byNode(target) : undefined;
114 111 if (w) {
115 112 w.destroyRecursive();
116 113 } else {
117 114 registry.findWidgets(target).forEach(destroy);
118 115 dom.destroy(target);
119 116 }
120 117 }
121 118 }
122 119
123 120 /** Empties a content of the specified node and destroys all contained widgets.
124 121 *
125 122 * @param target DOM node to .
126 123 */
127 124 export function emptyNode(target: Node) {
128 125 registry.findWidgets(target).forEach(destroy);
129 126 dom.empty(target);
130 127 }
131 128
132 129 /** This function starts all widgets inside the DOM node if the target is a node
133 130 * or starts widget itself if the target is the widget. If the specified node
134 131 * associated with the widget that widget will be started.
135 132 *
136 133 * @param target DOM node to find and start widgets or the widget itself.
137 134 */
138 135 export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) {
139 136 if (isNode(target)) {
140 137 const w = isElementNode(target) ? registry.byNode(target) : undefined;
141 138 if (w) {
142 139 if (w.startup)
143 140 w.startup();
144 141 } else {
145 142 registry.findWidgets(target, skipNode).forEach(x => x.startup());
146 143 }
147 144 } else {
148 145 if (target.startup)
149 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 1 import { djbase, djclass, bind, prototype, AbstractConstructor } from "../declare";
2 2
3 3 import { DjxWidgetBase } from "../tsx/DjxWidgetBase";
4 import { createElement } from "../tsx";
5 import { on } from "../tsx/traits";
4 import { createElement, on } from "../tsx";
6 5
7 6 interface MyWidgetAttrs {
8 7 title: string;
9 8
10 9 counter: number;
11 10 }
12 11
13 12 interface MyWidgetEvents {
14 13 "count-inc": Event & {
15 14 detail: number;
16 15 };
17 16
18 17 "count-dec": Event & {
19 18 detail: number;
20 19 };
21 20 }
22 21
23 22
24 23 @djclass
25 24 export class MyWidget extends djbase(DjxWidgetBase as AbstractConstructor<DjxWidgetBase<MyWidgetAttrs, MyWidgetEvents>>) {
26 25
27 26 @bind({ node: "titleNode", type: "innerHTML" })
28 27 title = "";
29 28
30 29 @prototype()
31 30 counter = 0;
32 31
33 32 render() {
34 33 const Frame = (props: any) => <div>{props.children}</div>;
35 34 return <div className="myWidget" onsubmit={e => this._onSubmit(e)} tabIndex={3} style={{ alignContent: "center", border: "1px solid" }} >
36 35 <h1 data-dojo-attach-point="titleNode"></h1>
37 36 <Frame>
38 37 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
39 38 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
40 39 </Frame>
41 40 </div>;
42 41 }
43 42
44 43 postCreate() {
45 44 super.postCreate();
46 45
47 46 this.on("click", () => {});
48 47 }
49 48
50 49 _onSubmit(e: Event) {
51 50 }
52 51
53 52 _onIncClick(e: MouseEvent) {
54 53 this.set("counter", this.counter + 1);
55 54
56 55 this.emit("count-inc", { bubbles: false });
57 56 }
58 57
59 58 _onDecClick() {
60 59 this.emit("count-dec", { bubbles: false, detail: this.counter });
61 60 }
62 61
63 62 @on("count-inc")
64 63 _onCounterInc(evt: Event & { detail: number; x?: number; }) {
65 64 }
66 65
67 66 @on("click", "keydown")
68 67 protected _onClick(event: MouseEvent | KeyboardEvent) {
69 68
70 69 }
71 70 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now