##// END OF EJS Templates
fixed tslint errors, added support for private methods to @on() decorator
cin -
r79:e5bb5e80ce96 v1.2.3 default
parent child
Show More
@@ -0,0 +1,25
1 {
2 "env": {
3 "browser": true,
4 "amd": true
5 },
6 "parserOptions": {
7 "ecmaFeatures": {
8 "jsx": true
9 },
10 "sourceType": "script"
11 },
12 "extends": "eslint:recommended",
13 "rules": {
14 "no-const-assign": "warn",
15 "no-this-before-super": "warn",
16 "no-undef": "error",
17 "no-unreachable": "warn",
18 "no-unused-vars": "warn",
19 "constructor-super": "warn",
20 "valid-typeof": "warn",
21 "semi" : "warn",
22 "no-invalid-this" : "error",
23 "no-console": "off"
24 }
25 } No newline at end of file
@@ -0,0 +1,48
1 {
2 "extends": "tslint:recommended",
3 "defaultSeverity": "warn",
4 "rules": {
5 "align": [
6 true,
7 "parameters",
8 "statements"
9 ],
10 "interface-name": [
11 false
12 ],
13 "max-line-length": [
14 true,
15 185
16 ],
17 "quotemark": [true, "double", "avoid-escape"],
18 "member-access": false,
19 "semicolon": [true, "always", "ignore-bound-class-methods"],
20 "no-bitwise": false,
21 "no-empty": false,
22 "no-namespace": false,
23 "ordered-imports": false,
24 "no-return-await": true,
25 "no-floating-promises": true,
26 "one-line": [
27 true,
28 "check-open-brace",
29 "check-catch",
30 "check-whitespace"
31 ],
32 "object-literal-sort-keys": false,
33 "trailing-comma": [
34 true,
35 {
36 "singleline": "never",
37 "multiline": "never"
38 }
39 ],
40 "variable-name": false,
41 "curly": false,
42 "array-type": false,
43 "arrow-parens": [
44 true,
45 "ban-single-arg-parens"
46 ]
47 }
48 } No newline at end of file
@@ -1,77 +1,78
1 1 plugins {
2 2 id "org.implab.gradle-typescript" version "1.3.3"
3 3 id "ivy-publish"
4 4 }
5 5
6 6 typescript {
7 7 compilerOptions {
8 8 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
9 9 //listFiles = true
10 10 declaration = true
11 11 strict = true
12 12 types = []
13 13 module = "amd"
14 14 it.target = "es5"
15 15 experimentalDecorators = true
16 noUnusedLocals = true
16 17 jsx = "react"
17 18 jsxFactory = "createElement"
18 19 moduleResolution = "node"
19 20 // dojo-typings are sick
20 21 skipLibCheck = true
21 22 // traceResolution = true
22 23 // baseUrl = "./"
23 24 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
24 25 // baseUrl = "$projectDir/src/typings"
25 26 // typeRoots = ["$projectDir/src/typings"]
26 27 }
27 28
28 29 tscCmd = "$projectDir/node_modules/.bin/tsc"
29 30 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
30 31 esLintCmd = "$projectDir/node_modules/.bin/eslint"
31 32 }
32 33
33 34 configureTsMain {
34 35 sourceFiles {
35 36 from sources.main.typings
36 37 }
37 38 compilerOptions {
38 39 // baseUrl = "$projectDir/src"
39 40 /*paths = [
40 41 "dojo/*" : [ "typings/dojo/*" ],
41 42 "dijit/*" : [ "typings/dijit/*" ]
42 43 ]*/
43 44 types = ["requirejs", "@implab/dojo-typings"]
44 45 }
45 46 }
46 47
47 48 configureTsTest {
48 49 compilerOptions {
49 50 typeRoots = []
50 51 types = ["requirejs", sources.main.output.typingsDir.get().toString() ]
51 52 }
52 53 }
53 54
54 55 npmPackMeta {
55 56 meta {
56 57 name = "@$npmScope/$project.name"
57 58 }
58 59 }
59 60
60 61 task npmPackTypings(type: Copy) {
61 62 dependsOn typings
62 63
63 64 npmPackContents.dependsOn it
64 65
65 66 from typescript.typingsDir
66 67 into npm.packageDir
67 68 }
68 69
69 70 task printVersion {
70 71 doLast {
71 72 println "packageName: ${npmPackMeta.metadata.get().name}";
72 73 println "version: $version";
73 74 println "target: $typescript.compilerOptions.target";
74 75 println "module: $typescript.compilerOptions.module";
75 76 println "symbols: $symbols";
76 77 }
77 78 } No newline at end of file
@@ -1,81 +1,73
1 1 import { MapOf, PromiseOrValue } from "@implab/core-amd/interfaces";
2 2 import { argumentNotEmptyString, isPromise, mixin } from "@implab/core-amd/safe";
3 import { id as mid } from "module";
4 import { TraceSource } from "@implab/core-amd/log/TraceSource";
5
6 const trace = TraceSource.get(mid);
7 3
8 4 export type LocaleProvider<T> = () => PromiseOrValue<T | { default: T }>;
9 5
10 6 type ResolveCallback<T> = () => PromiseOrValue<T>;
11 7
12 8 function when<T, T2>(value: PromiseOrValue<T>, cb: (v: T) => PromiseOrValue<T2>): PromiseOrValue<T2> {
13 9 return isPromise(value) ?
14 10 value.then(cb) :
15 11 cb(value);
16 12 }
17 13
18 14 function isCallback<T>(v: ResolveCallback<T> | PromiseOrValue<T>): v is ResolveCallback<T> {
19 15 return typeof v === "function";
20 16 }
21 17
22 function defaultResolver(module: string) {
23 return import(module).then(x => x && x.default ? x.default : x);
24 }
25
26 18 function chainObjects<T extends object>(o1: T, o2: T) {
27 19 if (!o1)
28 20 return o2;
29 21 if (!o2)
30 22 return o1;
31 23
32 24 return mixin(Object.create(o1) as T, o2);
33 25 }
34 26
35 27 export class NlsBundle<T extends object> {
36 28 private _locales: MapOf<LocaleProvider<Partial<T>>>;
37 29
38 30 private _default: T;
39 31
40 32 private _cache: MapOf<PromiseOrValue<T>>;
41 33
42 34 constructor(defNls: T, locales?: MapOf<LocaleProvider<Partial<T>>>) {
43 35 this._default = defNls;
44 36 this._locales = locales || {};
45 37 this._cache = {};
46 38 }
47 39
48 40 getLocale(locale: string) {
49 41 argumentNotEmptyString(locale, "locale");
50 42 const _loc = locale;
51 43
52 44 // en-US => ["en", "en-US"]
53 45 const locales = _loc.split(/-|_/).map((x, i, a) => a.slice(0, i + 1).join("-"));
54 46 return this._resolveLocale(locales);
55 47 }
56 48
57 49 _resolveLocale(locales: string[]): PromiseOrValue<T> {
58 50 if (!locales.length)
59 51 return this._default;
60 52
61 53 const locale = locales.pop();
62 54 if (!locale)
63 55 throw new Error("The locale can't be empty");
64 56
65 57 if (this._cache[locale])
66 58 return this._cache[locale];
67 59
68 60 const data = this._loadPackage(this._locales[locale]);
69 61 const parent = this._resolveLocale(locales);
70 62
71 63 return this._cache[locale] = when(data, x => {
72 64 return when(parent, y => this._cache[locale] = chainObjects(y, x));
73 65 });
74 66 }
75 67
76 68 _loadPackage(localeData: any) {
77 69 if (isCallback(localeData))
78 70 return when(localeData(), data => data && "default" in data ? data.default : data);
79 71 return localeData;
80 72 }
81 73 }
@@ -1,256 +1,253
1 1 import declare = require("dojo/_base/declare");
2 2 import { each } from "@implab/core-amd/safe";
3 3 import { Constructor } from "@implab/core-amd/interfaces";
4 import dojo = require("dojo/_base/kernel");
5 4
6 5 // declare const declare: any;
7 6
8 7 type DeclareConstructor<T> = dojo._base.DeclareConstructor<T>;
9 8
10 9 export interface AbstractConstructor<T = {}> {
11 10 prototype: T;
12 11 }
13 12
14 13 interface DjMockConstructor<T = {}> {
15 14 new(...args: any[]): T;
16 15 mock: boolean;
17 16 bases: AbstractConstructor[];
18 17 }
19 18
20 19 export function djbase<T>(
21 20 b0?: AbstractConstructor<T>
22 21 ): DeclareConstructor<T>;
23 22
24 23 export function djbase<T0, T1>(
25 24 b0: AbstractConstructor<T0>,
26 25 b1: AbstractConstructor<T1>
27 26 ): DeclareConstructor<T0 & T1>;
28 27
29 28 export function djbase<T0, T1, T2>(
30 29 b0: AbstractConstructor<T0>,
31 30 b1: AbstractConstructor<T1>,
32 31 b2: AbstractConstructor<T2>
33 32 ): DeclareConstructor<T0 & T1 & T2>;
34 33
35 34 export function djbase<T0, T1, T2, T3>(
36 35 b0: AbstractConstructor<T0>,
37 36 b1: AbstractConstructor<T1>,
38 37 b2: AbstractConstructor<T2>,
39 38 b3: AbstractConstructor<T3>
40 39 ): DeclareConstructor<T0 & T1 & T2 & T3>;
41 40
42 41 export function djbase<T0, T1, T2, T3, T4>(
43 42 b0: AbstractConstructor<T0>,
44 43 b1: AbstractConstructor<T1>,
45 44 b2: AbstractConstructor<T2>,
46 45 b3: AbstractConstructor<T3>,
47 46 b4: AbstractConstructor<T4>
48 47 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4>;
49 48
50 49 export function djbase<T0, T1, T2, T3, T4, T5>(
51 50 b0: AbstractConstructor<T0>,
52 51 b1: AbstractConstructor<T1>,
53 52 b2: AbstractConstructor<T2>,
54 53 b3: AbstractConstructor<T3>,
55 54 b4: AbstractConstructor<T4>,
56 55 b5: AbstractConstructor<T5>
57 56 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5>;
58 57
59 58 export function djbase<T0, T1, T2, T3, T4, T5, T6>(
60 59 b0: AbstractConstructor<T0>,
61 60 b1: AbstractConstructor<T1>,
62 61 b2: AbstractConstructor<T2>,
63 62 b3: AbstractConstructor<T3>,
64 63 b4: AbstractConstructor<T4>,
65 64 b5: AbstractConstructor<T5>,
66 65 b6: AbstractConstructor<T6>
67 66 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6>;
68 67
69 68 export function djbase<T0, T1, T2, T3, T4, T5, T6, T7>(
70 69 b0: AbstractConstructor<T0>,
71 70 b1: AbstractConstructor<T1>,
72 71 b2: AbstractConstructor<T2>,
73 72 b3: AbstractConstructor<T3>,
74 73 b4: AbstractConstructor<T4>,
75 74 b5: AbstractConstructor<T5>,
76 75 b6: AbstractConstructor<T6>,
77 76 b7: AbstractConstructor<T7>
78 77 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6 & T7>;
79 78
80 79 /** Создает конструктор-заглушку из списка базовых классов, используется
81 80 * для объявления классов при помощи `dojo/_base/declare`.
82 81 *
83 82 * Создает пустой конструктор, с пустым стандартным прототипом, это нужно,
84 83 * поскольку в унаследованном классе конструктор обязательно должен вызвать
85 84 * `super(...)`, таким образом он вызовет пустую функцию.
86 85 *
87 86 * Созданный конструктор хранит в себе список базовых классов, который будет
88 87 * использован декоратором `djclass`, который вернет класс, объявленный при
89 88 * помощи `dojo/_base/declare`.
90 89 *
91 90 * @param bases список базовых классов, от которых требуется унаследовать
92 91 * новый класс.
93 92 *
94 93 */
95 94 export function djbase(...bases: any[]): Constructor {
96 95
97 96 const t = class {
98 97 static mock: boolean;
99 98 static bases: AbstractConstructor[];
100 99 };
101 100
102 101 t.mock = true;
103 102 t.bases = bases;
104 103
105 104 return t as any;
106 105 }
107 106
108 107 function isMockConstructor<T extends {}>(v: AbstractConstructor<T>): v is DjMockConstructor<T> {
109 108 return v && "mock" in v;
110 109 }
111 110
112 111 /** Создает класс при помощи `dojo/_base/declare`. Для этого исходный класс
113 112 * должен быть унаследован от `djbase(...)`.
114 113 *
115 114 * @param target Класс, который нужно объявить при помощи `dojo/_base/declare`
116 115 */
117 116 export function djclass<T extends AbstractConstructor>(target: T): T {
118 117 // получаем базовый конструктор и его прототип
119 118 let bp = target && target.prototype && Object.getPrototypeOf(target.prototype);
120 119 const bc = bp && bp.constructor;
121 120
122 121 // проверка того, что класс унаследован от специальной заглушки
123 122 if (isMockConstructor(bc)) {
124 // t - базовый класс, объявленный при помощи dojo/_base/declare
125 const t = bc.bases;
126
123 // bc.bases - базовый класс, объявленный при помощи dojo/_base/declare
127 124 const cls = declare<any>(bc.bases, target.prototype);
128 125
129 126 // bc - базовый класс, bc.prototype используется как super
130 127 // при вызове базовых методов. Нужно создать bc.prototype
131 128 // таким образом, чтобы он вызывал this.inherited().
132 129
133 130 // создаем новый порототип, он не в цепочке прототипов у текущего
134 131 // класса, но super.some_method будет использовать именно его.
135 132 // в этом объекте будут размещены прокси для переопределенных
136 133 // методов.
137 134 bp = bc.prototype = Object.create(cls.prototype);
138 135 bp.constructor = bc;
139 136
140 137 // proxy - фабрика для создания прокси-методов, которые внутри
141 138 // себя вызовут this.inherited с правильными параметрами.
142 139 const proxy = (m: (...args: any[]) => any) => function (this: any) {
143 140 const f = this.getInherited({ callee: m });
144 141 return f && f.apply(this, arguments);
145 142
146 143 // так сделать можно только dojo 1.15+
147 144 // return this.inherited(m, arguments);
148 145 };
149 146
150 147 // у текущего класса прототип содержит методы, объявленные в этом
151 148 // классе и его конструктор. Нужно пройти по всем методам и
152 149 // создать для них прокси.
153 150 // При этом только те, методы, которые есть в базовых классах
154 151 // могут быть переопределены.
155 152 each(target.prototype, (m: any, p: string | number | symbol) => {
156 153 if (typeof m === "function" &&
157 154 p !== "constructor" &&
158 155 target.prototype.hasOwnProperty(p)
159 156 ) {
160 157 bp[p] = proxy(m);
161 158 }
162 159 });
163 160
164 161 // TODO mixin static members
165 162 return cls as any;
166 163 } else {
167 164 return target as any;
168 165 }
169 166 }
170 167
171 168 function makeSetterName(prop: string) {
172 169 return [
173 170 "_set",
174 171 prop.replace(/^./, x => x.toUpperCase()),
175 172 "Attr"
176 173 ].join("");
177 174 }
178 175
179 176 function makeGetterName(prop: string) {
180 177 return [
181 178 "_get",
182 179 prop.replace(/^./, x => x.toUpperCase()),
183 180 "Attr"
184 181 ].join("");
185 182 }
186 183
187 184 interface NodeBindSpec {
188 185 node: string;
189 186 type: "attribute" | "innerText" | "textContent" | "innerHTML" | "class" | "toggleClass";
190 187 attribute?: string;
191 188
192 189 className?: string;
193 190 }
194 191
195 192 /**
196 193 * Описание привязки свойства виджета к свойству внутреннего объекта.
197 194 */
198 195 interface MemberBindSpec {
199 196 /**
200 197 * Имя свойства со ссылкой на объект, к которому .
201 198 */
202 199 member: string;
203 200 /**
204 201 * Свойство объекта к которому нужно осуществить привязку.
205 202 */
206 203 property: string;
207 204
208 205 /**
209 206 * Привязка осуществляется не только на запись но и на чтение свойства.
210 207 */
211 208 getter?: boolean;
212 209 }
213 210
214 211 function isNodeBindSpec(v: any): v is NodeBindSpec {
215 212 return "node" in v;
216 213 }
217 214
218 215 /** Декорирует свойства виджета для привязки их к внутренним членам, либо DOM
219 216 * элементам, либо свойству внутреннего объекта.
220 217 *
221 218 * @param {NodeBindSpec | MemberBindSpec} params Параметры связывания.
222 219 */
223 220 export function bind(params: NodeBindSpec | MemberBindSpec) {
224 221 if (isNodeBindSpec(params)) {
225 222 return (target: any, name: string) => {
226 223 target[makeSetterName(name)] = params;
227 224 };
228 225 } else {
229 226 return (target: any, name: string) => {
230 227 target[name] = null;
231 228 target[makeSetterName(name)] = function (v: any) {
232 229 this._set(name, v);
233 230 this[params.member].set(params.property, v);
234 231 };
235 232 if (params.getter)
236 233 target[makeGetterName(name)] = function () {
237 234 return this[params.member].get(params.property);
238 235 };
239 236 };
240 237 }
241 238 }
242 239
243 240 /** Создает в прототипе указанное свойство со значение `undefined`, данный
244 241 * декоратор следует использовать для свойств, у которых нет значения по-умолчанию
245 242 * и они не могут быть `null | undefined`
246 243 */
247 244 export function prototype(): (p: any, name: string) => void;
248 245 /** Создает в прототипе свойство с указанным значением.
249 246 * @param value Значение, которое будет указано в прототипе
250 247 */
251 248 export function prototype<T>(value: T): <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => void;
252 249 export function prototype<T>(value?: T) {
253 250 return <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => {
254 251 p[name] = value as any;
255 252 };
256 253 }
@@ -1,100 +1,100
1 1 import { id as mid } from "module";
2 2 import { TraceSource } from "@implab/core-amd/log/TraceSource";
3 3 import { MapOf } from "@implab/core-amd/interfaces";
4 4 import { mixin } from "@implab/core-amd/safe";
5 5
6 6 const trace = TraceSource.get(mid);
7 7
8 8
9 9 function on<T extends keyof HTMLElementEventMap>(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any): () => void {
10 10 // Add an event listener to a DOM node
11 11 node.addEventListener(eventName, handler, false);
12 12
13 13 return () => node.removeEventListener(eventName, handler, false);
14 14 }
15 15
16 16 interface NodeLoadResult {
17 17 node: HTMLElement;
18 18 }
19 19
20 20 class DomInject {
21 21 injectionPoint?: HTMLElement;
22 22 injectAfter?: HTMLElement;
23 23
24 24 _map: MapOf<Promise<NodeLoadResult>> = {};
25 25
26 26 _inject<T extends keyof HTMLElementTagNameMap>(name: T, attr: Partial<HTMLElementTagNameMap[T]>) {
27 27 const node = document.createElement(name);
28 28
29 29 return new Promise<NodeLoadResult>((ok, fail) => {
30 30
31 31 const cleanup = () => {
32 32 noerr();
33 33 noload();
34 34 };
35 35
36 36 const noload = on(node, "load", () => {
37 37 ok({ node });
38 38 cleanup();
39 39 });
40 40
41 41 const noerr = on(node, "error", e => {
42 42 fail({
43 43 error: e,
44 44 node
45 45 });
46 46 cleanup();
47 47 });
48 48
49 49 mixin(node, attr);
50 50
51 51 const _injectionPoint = this.injectionPoint || document.head;
52 52 const _injectBefore = this.injectAfter ? this.injectAfter.nextSibling : null;
53 53
54 54 _injectionPoint.insertBefore(node, _injectBefore);
55 55 });
56 56 }
57 57
58 58 async injectScript(url: string) {
59 59 let d = this._map[url];
60 60 if (!d) {
61 61 trace.log("js {0}", url);
62 62 d = this._inject("script", {
63 63 type: "text/javascript",
64 64 charset: "utf-8",
65 65 src: url
66 66 });
67 67 this._map[url] = d;
68 68 }
69 69 try {
70 70 await d;
71 71 trace.log("done {0}", url);
72 72 } catch (e) {
73 73 trace.error("failed {0}: {1}", url, e);
74 74 throw e;
75 75 }
76 76 }
77 77
78 78 async injectStylesheet(url: string) {
79 79 let d = this._map[url];
80 80 if (!d) {
81 81 trace.log("js {0}", url);
82 82 d = this._inject("link", {
83 83 type: "text/css",
84 84 rel: "stylesheet",
85 85 href: url
86 86 });
87 87 this._map[url] = d;
88 88 }
89 89 try {
90 90 await d;
91 91 trace.log("done {0}", url);
92 92 } catch (e) {
93 93 trace.error("failed {0}: {1}", url, e);
94 94 throw e;
95 95 }
96 96 }
97 };
97 }
98 98
99 99 const instance = new DomInject();
100 100 export default instance;
@@ -1,110 +1,110
1 import { AbstractConstructor, djbase, djclass } from "../declare";
1 import { djbase, djclass } from "../declare";
2 2 import _WidgetBase = require("dijit/_WidgetBase");
3 3 import _AttachMixin = require("dijit/_AttachMixin");
4 import { Rendition, isNode, startupWidgets } from "./traits";
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 24 }
25 25
26 26 type _super = {
27 27 startup(): void;
28 }
28 };
29 29
30 30 @djclass
31 31 export abstract class DjxWidgetBase<Attrs = {}, Events = {}> extends djbase<_super, _AttachMixin>(_WidgetBase, _AttachMixin) {
32 32
33 33 /** The list of pairs of event and method names. When the widget is created all methods from
34 34 * this list will be connected to corresponding events.
35 35 */
36 36 _eventHandlers: Array<{
37 37 eventName: string,
38 38 handlerMethod: keyof any;
39 39 }> = [];
40 40
41 41 buildRendering() {
42 42 this.domNode = this.render().getDomNode();
43 43 super.buildRendering();
44 44
45 45 // now we should get assigned data-dojo-attach-points
46 46 // place the contents of the original srcNode to the containerNode
47 47 const src = this.srcNodeRef;
48 48 const dest = this.containerNode;
49 49
50 50 // the donNode is constructed now we need to connect event handlers
51 51 this._connectEventHandlers();
52 52
53 53 if (src && dest) {
54 54 while (src.firstChild)
55 55 dest.appendChild(src.firstChild);
56 56 }
57 57 }
58 58
59 59 abstract render(): Rendition<HTMLElement>;
60 60
61 61 private _connectEventHandlers() {
62 62 this._eventHandlers.forEach(({eventName, handlerMethod}) => {
63 63 const handler = this[handlerMethod as keyof this];
64 64 if (typeof handler === "function")
65 65 this.on(eventName, handler.bind(this));
66 66 });
67 67 }
68 68
69 69 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
70 70 baseNode: T,
71 71 getAttrFunc: (baseNode: T, attr: string) => any,
72 72 // tslint:disable-next-line: ban-types
73 73 attachFunc: (node: T, type: string, func?: Function) => dojo.Handle
74 74 ): boolean {
75 75 if (isNode(baseNode)) {
76 76 const w = registry.byNode(baseNode);
77 77 if (w) {
78 78 // from dijit/_WidgetsInTemplateMixin
79 79 this._processTemplateNode(w,
80 80 (n, p) => n.get(p as any), // callback to get a property of a widget
81 81 (widget, type, callback) => {
82 82 if (!callback)
83 83 throw new Error("The callback must be specified");
84 84
85 85 // callback to do data-dojo-attach-event to a widget
86 86 if (type in widget) {
87 87 // back-compat, remove for 2.0
88 88 return widget.connect(widget, type, callback as EventListener);
89 89 } else {
90 90 // 1.x may never hit this branch, but it's the default for 2.0
91 91 return widget.on(type, callback);
92 92 }
93 93
94 94 });
95 95 // don't process widgets internals
96 96 return false;
97 97 }
98 98 }
99 99 return super._processTemplateNode(baseNode, getAttrFunc, attachFunc);
100 100 }
101 101
102 102 /** Starts current widget and all its supporting widgets (placed outside
103 103 * `containerNode`) and child widgets (placed inside `containerNode`)
104 104 */
105 105 startup() {
106 106 // startup supporting widgets
107 107 registry.findWidgets(this.domNode, this.containerNode).forEach(w => w.startup());
108 108 super.startup();
109 109 }
110 110 }
@@ -1,116 +1,116
1 1 import { isNull, mixin } from "@implab/core-amd/safe";
2 2 import { isPlainObject, isNode, isRendition, DojoNodePosition, Rendition, isInPage, isWidget, isDocumentFragmentNode, startupWidgets } from "./traits";
3 3
4 4 import dom = require("dojo/dom-construct");
5 5 import registry = require("dijit/registry");
6 6
7 7
8 8 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
9 9 private _attrs = {};
10 10
11 11 private _children = new Array();
12 12
13 13 private _created: boolean = false;
14 14
15 15 visitNext(v: any) {
16 16 if (this._created)
17 17 throw new Error("The Element is already created");
18 18
19 19 if (isNull(v) || typeof v === "boolean")
20 20 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
21 21 return;
22 22
23 23 if (isPlainObject(v)) {
24 24 mixin(this._attrs, v);
25 25 } else if (v instanceof Array) {
26 26 v.forEach(x => this.visitNext(x));
27 27 } else {
28 28 this._children.push(v);
29 29 }
30 30 }
31 31
32 32 protected getItemDom(v: any) {
33 33 const tv = typeof v;
34 34
35 35 if (tv === "string" || tv === "number" || v instanceof RegExp || v instanceof Date) {
36 36 // primitive types converted to the text nodes
37 37 return document.createTextNode(v.toString());
38 38 } else if (isNode(v)) {
39 39 // nodes are kept as is
40 40 return v;
41 41 } else if (isRendition(v)) {
42 42 // renditions as instantinated
43 43 return v.getDomNode();
44 44 } else if (isWidget(v)) {
45 45 // widgets are converted to it's markup
46 46 return v.domNode;
47 47 } else if (tv === "boolean" || v === null || v === undefined) {
48 48 // null | undefined | boolean are removed, converted to comments
49 49 return document.createComment(`[${tv} ${String(v)}]`);
50 50 } else {
51 51 // bug: explicit error otherwise
52 52 throw new Error("Invalid parameter: " + v);
53 53 }
54 54 }
55 55
56 56 ensureCreated() {
57 57 if (!this._created) {
58 58 this._create(this._attrs, this._children);
59 59 this._children = [];
60 60 this._attrs = {};
61 61 this._created = true;
62 62 }
63 63 }
64 64
65 65 /** @deprecated will be removed in 1.0.0, use getDomNode() */
66 66 getDomElement() {
67 67 return this.getDomNode();
68 68 }
69 69
70 70 /** Creates DOM node if not created. No additional actions are taken. */
71 71 getDomNode() {
72 72 this.ensureCreated();
73 73 return this._getDomNode();
74 74 }
75 75
76 76 /** Creates DOM node if not created, places it to the specified position
77 77 * and calls startup() method for all widgets contained by this node.
78 78 *
79 79 * @param {string | Node} refNode The reference node where the created
80 80 * DOM should be placed.
81 81 * @param {DojoNodePosition} position Optional parameter, specifies the
82 82 * position relative to refNode. Default is "last" (i.e. last child).
83 83 */
84 84 placeAt(refNode: string | Node, position?: DojoNodePosition) {
85 85 const domNode = this.getDomNode();
86 86
87 87 const collect = (collection: HTMLCollection) => {
88 88 const items = [];
89 89 for (let i = 0, n = collection.length; i < n; i++) {
90 90 items.push(collection[i]);
91 91 }
92 92 return items;
93 }
93 };
94 94
95 95 const startup = (node: Node) => {
96 96 if (node.parentNode) {
97 97 const parentWidget = registry.getEnclosingWidget(node.parentNode);
98 98 if (parentWidget && parentWidget._started)
99 99 return startupWidgets(node);
100 100 }
101 101 if (isInPage(node))
102 102 startupWidgets(node);
103 }
103 };
104 104
105 const startupPending = isDocumentFragmentNode(domNode) ? collect(domNode.children) : [domNode]
105 const startupPending = isDocumentFragmentNode(domNode) ? collect(domNode.children) : [domNode];
106 106
107 107 dom.place(domNode, refNode, position);
108 108
109 109 startupPending.forEach(startup);
110 110
111 111 }
112 112
113 113 protected abstract _create(attrs: object, children: any[]): void;
114 114
115 115 protected abstract _getDomNode(): TNode;
116 116 }
@@ -1,121 +1,121
1 1 import dom = require("dojo/dom-construct");
2 2 import { argumentNotNull } from "@implab/core-amd/safe";
3 3 import { RenditionBase } from "./RenditionBase";
4 4 import { DojoNodePosition, isInPage, isWidget } from "./traits";
5 5 import registry = require("dijit/registry");
6 6 import ContentPane = require("dijit/layout/ContentPane");
7 7
8 8 // tslint:disable-next-line: class-name
9 9 export interface _Widget {
10 10 domNode: Node;
11 11
12 12 containerNode?: Node;
13 13
14 14 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
15 15 startup?(): void;
16 16
17 17 addChild?(widget: any, index?: number): void;
18 18 }
19 19
20 20 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
21 21
22 22 export class WidgetRendition extends RenditionBase<Node> {
23 23 readonly widgetClass: _WidgetCtor;
24 24
25 25 _instance: _Widget | undefined;
26 26
27 27 constructor(widgetClass: _WidgetCtor) {
28 28 super();
29 29 argumentNotNull(widgetClass, "widgetClass");
30 30
31 31 this.widgetClass = widgetClass;
32 32 }
33 33
34 34 _addChild(child: any): void {
35 35 const instance = this._getInstance();
36 36
37 37 if (instance.addChild) {
38 38 if (child instanceof WidgetRendition) {
39 39 // layout containers add custom logic to addChild methods
40 40 instance.addChild(child.getWidgetInstance());
41 41 } else if (isWidget(child)) {
42 42 instance.addChild(child);
43 43 } else {
44 44 if (!instance.containerNode)
45 45 throw new Error("The widget doesn't have neither addChild nor containerNode");
46 46
47 47 // the current widget isn't started, it's children shouldn't start too
48 48 dom.place(this.getItemDom(child), instance.containerNode);
49 49 }
50 50 } else {
51 51 if (!instance.containerNode)
52 52 throw new Error("The widget doesn't have neither addChild nor containerNode");
53 53
54 54 // the current widget isn't started, it's children shouldn't start too
55 55 dom.place(this.getItemDom(child), instance.containerNode);
56 56 }
57 57 }
58 58
59 59 protected _create(attrs: any, children: any[]) {
60 60 if (this.widgetClass.prototype instanceof ContentPane) {
61 61 // a special case for the ContentPane this is for
62 62 // the compatibility with this heavy widget, all
63 63 // regular containers could be easily manipulated
64 64 // through `containerNode` property or `addChild` method.
65 65
66 66 // render children to the DocumentFragment
67 67 const content = document.createDocumentFragment();
68 68 children.forEach(child => content.appendChild(this.getItemDom(child)));
69 69
70 70 // set the content property to the parameters of the widget
71 71 const _attrs = { ...attrs, content };
72 72 this._instance = new this.widgetClass(_attrs);
73 73 } else {
74 74 this._instance = new this.widgetClass(attrs);
75 75 children.forEach(x => this._addChild(x));
76 76 }
77 77
78 78 }
79 79
80 80 private _getInstance() {
81 81 if (!this._instance)
82 82 throw new Error("The instance of the widget isn't created");
83 83 return this._instance;
84 84 }
85 85
86 86 protected _getDomNode() {
87 87 if (!this._instance)
88 88 throw new Error("The instance of the widget isn't created");
89 89 return this._instance.domNode;
90 90 }
91 91
92 92 /** Overrides default placeAt implementation. Calls placeAt of the
93 93 * widget and then starts it.
94 94 *
95 95 * @param refNode A node or id of the node where the widget should be placed.
96 96 * @param position A position relative to refNode.
97 97 */
98 98 placeAt(refNode: string | Node, position?: DojoNodePosition) {
99 99 this.ensureCreated();
100 100 const instance = this._getInstance();
101 101 if (typeof instance.placeAt === "function") {
102 102 instance.placeAt(refNode, position);
103 103
104 104 // fix the dojo startup behavior when the widget is placed
105 105 // directly to the document and doesn't have any enclosing widgets
106 106 const parentWidget = instance.domNode.parentNode ?
107 registry.getEnclosingWidget(instance.domNode.parentNode) : null
107 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
108 108 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
109 109 instance.startup();
110 110 } else {
111 111 // the widget doesn't have a placeAt method, strange but whatever
112 112 super.placeAt(refNode, position);
113 113 }
114 114 }
115 115
116 116 getWidgetInstance() {
117 117 this.ensureCreated();
118 118 return this._getInstance();
119 119 }
120 120
121 121 }
@@ -1,227 +1,227
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 const _own = cleanupOrOwner instanceof Function ? cleanupOrOwner : (x: IRemovable) => cleanupOrOwner.own(x)
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 <K extends keyof T,
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 descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
224 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
225 225 ): any => {
226 226 target._eventHandlers.push({ eventName, handlerMethod: key });
227 227 };
@@ -1,10 +1,12
1 1 import { Baz } from "./mock/Baz";
2 2
3 // tslint:disable-next-line: no-console
3 4 console.log("Declare tests");
4 5
5 6 const baz = new Baz();
6 7
7 8 const data: string[] = [];
8 9 baz.writeHello(data);
9 10
11 // tslint:disable-next-line: no-console
10 12 console.log(data.join("\n"));
@@ -1,17 +1,16
1 1 import { test } from "./TestTraits";
2 2 import { delay } from "@implab/core-amd/safe";
3 3 import { assert } from "chai";
4 import css = require("@implab/djx/css!my.css");
5 4
6 5 test("simple", (ok, fail, log) => {
7 6 setTimeout(() => {
8 7 // end should be called after the last assertion
9 8 ok("async assert");
10 9 }, 100);
11 10 });
12 11
13 12 test("simple", async (log, fail) => {
14 13 await delay(0);
15 14
16 15 assert.ok(true); // everything is fine
17 16 });
@@ -1,71 +1,71
1 1 import { djbase, djclass, bind, prototype, AbstractConstructor } from "../declare";
2 2
3 3 import { DjxWidgetBase } from "../tsx/DjxWidgetBase";
4 4 import { createElement } from "../tsx";
5 5 import { on } from "../tsx/traits";
6 6
7 7 interface MyWidgetAttrs {
8 8 title: string;
9 9
10 10 counter: number;
11 11 }
12 12
13 13 interface MyWidgetEvents {
14 14 "count-inc": Event & {
15 15 detail: number;
16 16 };
17 17
18 18 "count-dec": Event & {
19 19 detail: number;
20 20 };
21 21 }
22 22
23 23
24 24 @djclass
25 25 export class MyWidget extends djbase(DjxWidgetBase as AbstractConstructor<DjxWidgetBase<MyWidgetAttrs, MyWidgetEvents>>) {
26 26
27 27 @bind({ node: "titleNode", type: "innerHTML" })
28 28 title = "";
29 29
30 30 @prototype()
31 31 counter = 0;
32 32
33 33 render() {
34 34 const Frame = (props: any) => <div>{props.children}</div>;
35 35 return <div className="myWidget" onsubmit={e => this._onSubmit(e)} tabIndex={3} style={{ alignContent: "center", border: "1px solid" }} >
36 36 <h1 data-dojo-attach-point="titleNode"></h1>
37 37 <Frame>
38 38 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
39 39 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
40 40 </Frame>
41 41 </div>;
42 42 }
43 43
44 44 postCreate() {
45 45 super.postCreate();
46 46
47 47 this.on("click", () => {});
48 48 }
49 49
50 50 _onSubmit(e: Event) {
51 51 }
52 52
53 53 _onIncClick(e: MouseEvent) {
54 54 this.set("counter", this.counter + 1);
55 55
56 56 this.emit("count-inc", { bubbles: false });
57 57 }
58 58
59 59 _onDecClick() {
60 60 this.emit("count-dec", { bubbles: false, detail: this.counter });
61 61 }
62 62
63 63 @on("count-inc")
64 64 _onCounterInc(evt: Event & { detail: number; x?: number; }) {
65 65 }
66 66
67 67 @on("click")
68 _onClick() {
68 protected _onClick() {
69 69
70 70 }
71 71 } No newline at end of file
@@ -1,14 +1,15
1 1 {
2 2 "compilerOptions": {
3 3 "moduleResolution": "node",
4 4 "noEmitOnError": true,
5 5 "strict": true,
6 6 "types": [],
7 7 "experimentalDecorators": true,
8 8 "jsxFactory": "createElement",
9 9 "target": "ES5",
10 10 //"skipLibCheck": true,
11 11 "jsx": "react",
12 "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"]
12 "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"],
13 "noUnusedLocals": true
13 14 }
14 15 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now