##// END OF EJS Templates
fixed DjxWidgetBase._eventHandlers initialization
cin -
r81:cc5be30e84f8 v1.2.4 default
parent child
Show More
@@ -1,110 +1,112
1 import { djbase, djclass } from "../declare";
1 import { djbase, djclass, prototype } from "../declare";
2 2 import _WidgetBase = require("dijit/_WidgetBase");
3 3 import _AttachMixin = require("dijit/_AttachMixin");
4 4 import { Rendition, isNode } from "./traits";
5 5 import registry = require("dijit/registry");
6 6
7 7 // type Handle = dojo.Handle;
8 8
9 9 export interface EventArgs {
10 10 bubbles?: boolean;
11 11
12 12 cancelable?: boolean;
13 13
14 14 composed?: boolean;
15 15 }
16 16
17 17 export interface DjxWidgetBase<Attrs = {}, Events extends { [name in keyof Events]: Event } = {}> extends
18 18 _WidgetBase<Events> {
19 19
20 20 /** This property is declared only for type inference to work, it is never assigned
21 21 * and should not be used.
22 22 */
23 23 readonly _eventMap: Events & GlobalEventHandlersEventMap;
24
25 /** The list of pairs of event and method names. When the widget is created all methods from
26 * this list will be connected to corresponding events.
27 *
28 * This property is maintained in the prototype
29 */
30 _eventHandlers: Array<{
31 eventName: string,
32 handlerMethod: keyof any;
33 }>;
24 34 }
25 35
26 36 type _super = {
27 37 startup(): void;
28 38 };
29 39
30 40 @djclass
31 41 export abstract class DjxWidgetBase<Attrs = {}, Events = {}> extends djbase<_super, _AttachMixin>(_WidgetBase, _AttachMixin) {
32 42
33 /** The list of pairs of event and method names. When the widget is created all methods from
34 * this list will be connected to corresponding events.
35 */
36 _eventHandlers: Array<{
37 eventName: string,
38 handlerMethod: keyof any;
39 }> = [];
40
41 43 buildRendering() {
42 44 this.domNode = this.render().getDomNode();
43 45 super.buildRendering();
44 46
45 47 // now we should get assigned data-dojo-attach-points
46 48 // place the contents of the original srcNode to the containerNode
47 49 const src = this.srcNodeRef;
48 50 const dest = this.containerNode;
49 51
50 52 // the donNode is constructed now we need to connect event handlers
51 53 this._connectEventHandlers();
52 54
53 55 if (src && dest) {
54 56 while (src.firstChild)
55 57 dest.appendChild(src.firstChild);
56 58 }
57 59 }
58 60
59 61 abstract render(): Rendition<HTMLElement>;
60 62
61 63 private _connectEventHandlers() {
62 64 this._eventHandlers.forEach(({eventName, handlerMethod}) => {
63 65 const handler = this[handlerMethod as keyof this];
64 66 if (typeof handler === "function")
65 67 this.on(eventName, handler.bind(this));
66 68 });
67 69 }
68 70
69 71 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
70 72 baseNode: T,
71 73 getAttrFunc: (baseNode: T, attr: string) => any,
72 74 // tslint:disable-next-line: ban-types
73 75 attachFunc: (node: T, type: string, func?: Function) => dojo.Handle
74 76 ): boolean {
75 77 if (isNode(baseNode)) {
76 78 const w = registry.byNode(baseNode);
77 79 if (w) {
78 80 // from dijit/_WidgetsInTemplateMixin
79 81 this._processTemplateNode(w,
80 82 (n, p) => n.get(p as any), // callback to get a property of a widget
81 83 (widget, type, callback) => {
82 84 if (!callback)
83 85 throw new Error("The callback must be specified");
84 86
85 87 // callback to do data-dojo-attach-event to a widget
86 88 if (type in widget) {
87 89 // back-compat, remove for 2.0
88 90 return widget.connect(widget, type, callback as EventListener);
89 91 } else {
90 92 // 1.x may never hit this branch, but it's the default for 2.0
91 93 return widget.on(type, callback);
92 94 }
93 95
94 96 });
95 97 // don't process widgets internals
96 98 return false;
97 99 }
98 100 }
99 101 return super._processTemplateNode(baseNode, getAttrFunc, attachFunc);
100 102 }
101 103
102 104 /** Starts current widget and all its supporting widgets (placed outside
103 105 * `containerNode`) and child widgets (placed inside `containerNode`)
104 106 */
105 107 startup() {
106 108 // startup supporting widgets
107 109 registry.findWidgets(this.domNode, this.containerNode).forEach(w => w.startup());
108 110 super.startup();
109 111 }
110 112 }
@@ -1,227 +1,230
1 1 import { IDestroyable, IRemovable } 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 6 import Stateful = require("dojo/Stateful");
7 7 import { FunctionRendition } from "./FunctionRendition";
8 8 import { DjxWidgetBase } from "./DjxWidgetBase";
9 9
10 10 type _WidgetBaseConstructor = typeof _WidgetBase;
11 11
12 12 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
13 13
14 14 export interface Rendition<TNode extends Node = Node> {
15 15 getDomNode(): TNode;
16 16
17 17 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
18 18 }
19 19
20 20 /**
21 21 * @deprecated use Rendition
22 22 */
23 23 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
24 24
25 25 export interface IRecursivelyDestroyable {
26 26 destroyRecursive(): void;
27 27 }
28 28
29 29 export function isNode(el: any): el is Node {
30 30 return el && el.nodeName && el.nodeType;
31 31 }
32 32
33 33 export function isElementNode(el: any): el is Element {
34 34 return isNode(el) && el.nodeType === 1;
35 35 }
36 36
37 37 export function isTextNode(el: any): el is Text {
38 38 return isNode(el) && el.nodeType === 3;
39 39 }
40 40
41 41 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
42 42 return isNode(el) && el.nodeType === 7;
43 43 }
44 44
45 45 export function isCommentNode(el: any): el is Comment {
46 46 return isNode(el) && el.nodeType === 8;
47 47 }
48 48
49 49 export function isDocumentNode(el: any): el is Document {
50 50 return isNode(el) && el.nodeType === 9;
51 51 }
52 52
53 53 export function isDocumentTypeNode(el: any): el is DocumentType {
54 54 return isNode(el) && el.nodeType === 10;
55 55 }
56 56
57 57 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
58 58 return isNode(el) && el.nodeType === 11;
59 59 }
60 60
61 61 export function isWidget(v: any): v is _WidgetBase {
62 62 return v && "domNode" in v;
63 63 }
64 64
65 65 export function isRendition(v: any): v is Rendition {
66 66 return typeof v === "object" && typeof v.getDomElement === "function";
67 67 }
68 68
69 69 /**
70 70 * @deprecated use isRendition
71 71 */
72 72 export const isBuildContext = isRendition;
73 73
74 74 export function isPlainObject(v: object) {
75 75 if (typeof v !== "object")
76 76 return false;
77 77
78 78 const vp = Object.getPrototypeOf(v);
79 79 return !vp || vp === Object.prototype;
80 80 }
81 81
82 82 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
83 83 return typeof v === "function" && v.prototype && (
84 84 "domNode" in v.prototype ||
85 85 "buildRendering" in v.prototype
86 86 );
87 87 }
88 88
89 89 /** Tests whether the specified node is placed in visible dom.
90 90 * @param {Node} node The node to test
91 91 */
92 92 export function isInPage(node: Node) {
93 93 return (node === document.body) ? false : document.body.contains(node);
94 94 }
95 95
96 96 export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable {
97 97 return target && typeof target.destroyRecursive === "function";
98 98 }
99 99
100 100
101 101 /** Destroys DOM Node with all contained widgets.
102 102 * If the specified node is the root node of a widget, then the
103 103 * widget will be destroyed.
104 104 *
105 105 * @param target DOM Node or widget to destroy
106 106 */
107 107 export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) {
108 108 if (isRecursivelyDestroyable(target)) {
109 109 target.destroyRecursive();
110 110 } else if (isDestroyable(target)) {
111 111 target.destroy();
112 112 } else if (isNode(target)) {
113 113 const w = isElementNode(target) ? registry.byNode(target) : undefined;
114 114 if (w) {
115 115 w.destroyRecursive();
116 116 } else {
117 117 registry.findWidgets(target).forEach(destroy);
118 118 dom.destroy(target);
119 119 }
120 120 }
121 121 }
122 122
123 123 /** Empties a content of the specified node and destroys all contained widgets.
124 124 *
125 125 * @param target DOM node to .
126 126 */
127 127 export function emptyNode(target: Node) {
128 128 registry.findWidgets(target).forEach(destroy);
129 129 dom.empty(target);
130 130 }
131 131
132 132 /** This function starts all widgets inside the DOM node if the target is a node
133 133 * or starts widget itself if the target is the widget. If the specified node
134 134 * associated with the widget that widget will be started.
135 135 *
136 136 * @param target DOM node to find and start widgets or the widget itself.
137 137 */
138 138 export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) {
139 139 if (isNode(target)) {
140 140 const w = isElementNode(target) ? registry.byNode(target) : undefined;
141 141 if (w) {
142 142 if (w.startup)
143 143 w.startup();
144 144 } else {
145 145 registry.findWidgets(target, skipNode).forEach(x => x.startup());
146 146 }
147 147 } else {
148 148 if (target.startup)
149 149 target.startup();
150 150 }
151 151 }
152 152
153 153
154 154 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
155 155
156 156 type CleanFn = (instance: IRemovable | IDestroyable) => void;
157 157
158 158 /**
159 159 * Observers the property and calls render callback each change.
160 160 *
161 161 * @param target The target object which property will be observed.
162 162 * @param prop The name of the property.
163 163 * @param render The callback which will be called every time the value is changed
164 164 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
165 165 * @returns Rendition which is created instantly
166 166 */
167 167 export function watch<W extends _WidgetBase, K extends keyof W>(
168 168 target: W,
169 169 prop: K,
170 170 render: (model: W[K]) => any,
171 171 cleanupOrOwner?: { own: CleanFn } | CleanFn
172 172 ): Rendition;
173 173 /**
174 174 * Observers the property and calls render callback each change.
175 175 *
176 176 * @param target The target object which property will be observed.
177 177 * @param prop The name of the property.
178 178 * @param render The callback which will be called every time the value is changed
179 179 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
180 180 * @returns Rendition which is created instantly
181 181 */
182 182 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
183 183 target: T,
184 184 prop: K,
185 185 render: (model: StatefulProps<T>[K]) => any,
186 186 cleanupOrOwner?: { own: CleanFn } | CleanFn
187 187 ): Rendition;
188 188 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
189 189 target: T,
190 190 prop: K,
191 191 render: (model: StatefulProps<T>[K]) => any,
192 192 cleanupOrOwner: { own: CleanFn } | CleanFn = () => { }
193 193 ) {
194 194 let rendition = new FunctionRendition(() => render(target.get(prop)));
195 195 const _own = cleanupOrOwner instanceof Function ? cleanupOrOwner : (x: IRemovable) => cleanupOrOwner.own(x);
196 196 _own(target.watch(prop, (_name, oldValue, newValue) => {
197 197 if (oldValue !== newValue) {
198 198 const newRendition = new FunctionRendition(() => render(newValue));
199 199 newRendition.placeAt(rendition.getDomNode(), "replace");
200 200 destroy(rendition.getDomNode());
201 201 rendition = newRendition;
202 202 }
203 203 }));
204 204 return rendition;
205 205 }
206 206
207 207 /** Decorates the method which will be registered as the handle for the specified event.
208 208 * This decorator can be applied to DjxWidgetBase subclass methods.
209 209 *
210 210 * ```
211 211 * @on("click")
212 212 * _onClick(eventObj: MouseEvent) {
213 213 * // ...
214 214 * }
215 215 * ```
216 216 */
217 217 export const on = <E extends string>(eventName: E) =>
218 218 <K extends string,
219 219 T extends DjxWidgetBase<any, { [p in E]: EV }>,
220 220 EV extends Event
221 221 >(
222 222 target: T,
223 223 key: K,
224 224 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
225 225 ): any => {
226 if(!target._eventHandlers)
227 target._eventHandlers = [{ eventName, handlerMethod: key }];
228 else
226 229 target._eventHandlers.push({ eventName, handlerMethod: key });
227 230 };
General Comments 0
You need to be logged in to leave comments. Login now