##// END OF EJS Templates
NodeBindSpec now conforms the dojo specification
cin -
r26:32b72f33756d v1.0.0-rc12 default
parent child
Show More
@@ -1,257 +1,260
1 import declare = require("dojo/_base/declare");
1 import declare = require("dojo/_base/declare");
2 import { each } from "@implab/core-amd/safe";
2 import { each } from "@implab/core-amd/safe";
3 import { Constructor } from "@implab/core-amd/interfaces";
3 import { Constructor } from "@implab/core-amd/interfaces";
4 import dojo = require("dojo/_base/kernel");
4 import dojo = require("dojo/_base/kernel");
5
5
6 // declare const declare: any;
6 // declare const declare: any;
7
7
8 type DeclareConstructor<T> = dojo._base.DeclareConstructor<T>;
8 type DeclareConstructor<T> = dojo._base.DeclareConstructor<T>;
9
9
10 export interface AbstractConstructor<T = {}> {
10 export interface AbstractConstructor<T = {}> {
11 prototype: T;
11 prototype: T;
12 }
12 }
13
13
14 interface DjMockConstructor<T = {}> {
14 interface DjMockConstructor<T = {}> {
15 new(...args: any[]): T;
15 new(...args: any[]): T;
16 mock: boolean;
16 mock: boolean;
17 base: AbstractConstructor;
17 base: AbstractConstructor;
18 }
18 }
19
19
20 export function djbase<T>(
20 export function djbase<T>(
21 b0: AbstractConstructor<T>
21 b0: AbstractConstructor<T>
22 ): DeclareConstructor<T>;
22 ): DeclareConstructor<T>;
23
23
24 export function djbase<T0, T1>(
24 export function djbase<T0, T1>(
25 b0: AbstractConstructor<T0>,
25 b0: AbstractConstructor<T0>,
26 b1: AbstractConstructor<T1>
26 b1: AbstractConstructor<T1>
27 ): DeclareConstructor<T0 & T1>;
27 ): DeclareConstructor<T0 & T1>;
28
28
29 export function djbase<T0, T1, T2>(
29 export function djbase<T0, T1, T2>(
30 b0: AbstractConstructor<T0>,
30 b0: AbstractConstructor<T0>,
31 b1: AbstractConstructor<T1>,
31 b1: AbstractConstructor<T1>,
32 b2: AbstractConstructor<T2>
32 b2: AbstractConstructor<T2>
33 ): DeclareConstructor<T0 & T1 & T2>;
33 ): DeclareConstructor<T0 & T1 & T2>;
34
34
35 export function djbase<T0, T1, T2, T3>(
35 export function djbase<T0, T1, T2, T3>(
36 b0: AbstractConstructor<T0>,
36 b0: AbstractConstructor<T0>,
37 b1: AbstractConstructor<T1>,
37 b1: AbstractConstructor<T1>,
38 b2: AbstractConstructor<T2>,
38 b2: AbstractConstructor<T2>,
39 b3: AbstractConstructor<T3>
39 b3: AbstractConstructor<T3>
40 ): DeclareConstructor<T0 & T1 & T2 & T3>;
40 ): DeclareConstructor<T0 & T1 & T2 & T3>;
41
41
42 export function djbase<T0, T1, T2, T3, T4>(
42 export function djbase<T0, T1, T2, T3, T4>(
43 b0: AbstractConstructor<T0>,
43 b0: AbstractConstructor<T0>,
44 b1: AbstractConstructor<T1>,
44 b1: AbstractConstructor<T1>,
45 b2: AbstractConstructor<T2>,
45 b2: AbstractConstructor<T2>,
46 b3: AbstractConstructor<T3>,
46 b3: AbstractConstructor<T3>,
47 b4: AbstractConstructor<T4>
47 b4: AbstractConstructor<T4>
48 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4>;
48 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4>;
49
49
50 export function djbase<T0, T1, T2, T3, T4, T5>(
50 export function djbase<T0, T1, T2, T3, T4, T5>(
51 b0: AbstractConstructor<T0>,
51 b0: AbstractConstructor<T0>,
52 b1: AbstractConstructor<T1>,
52 b1: AbstractConstructor<T1>,
53 b2: AbstractConstructor<T2>,
53 b2: AbstractConstructor<T2>,
54 b3: AbstractConstructor<T3>,
54 b3: AbstractConstructor<T3>,
55 b4: AbstractConstructor<T4>,
55 b4: AbstractConstructor<T4>,
56 b5: AbstractConstructor<T5>
56 b5: AbstractConstructor<T5>
57 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5>;
57 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5>;
58
58
59 export function djbase<T0, T1, T2, T3, T4, T5, T6>(
59 export function djbase<T0, T1, T2, T3, T4, T5, T6>(
60 b0: AbstractConstructor<T0>,
60 b0: AbstractConstructor<T0>,
61 b1: AbstractConstructor<T1>,
61 b1: AbstractConstructor<T1>,
62 b2: AbstractConstructor<T2>,
62 b2: AbstractConstructor<T2>,
63 b3: AbstractConstructor<T3>,
63 b3: AbstractConstructor<T3>,
64 b4: AbstractConstructor<T4>,
64 b4: AbstractConstructor<T4>,
65 b5: AbstractConstructor<T5>,
65 b5: AbstractConstructor<T5>,
66 b6: AbstractConstructor<T6>
66 b6: AbstractConstructor<T6>
67 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6>;
67 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6>;
68
68
69 export function djbase<T0, T1, T2, T3, T4, T5, T6, T7>(
69 export function djbase<T0, T1, T2, T3, T4, T5, T6, T7>(
70 b0: AbstractConstructor<T0>,
70 b0: AbstractConstructor<T0>,
71 b1: AbstractConstructor<T1>,
71 b1: AbstractConstructor<T1>,
72 b2: AbstractConstructor<T2>,
72 b2: AbstractConstructor<T2>,
73 b3: AbstractConstructor<T3>,
73 b3: AbstractConstructor<T3>,
74 b4: AbstractConstructor<T4>,
74 b4: AbstractConstructor<T4>,
75 b5: AbstractConstructor<T5>,
75 b5: AbstractConstructor<T5>,
76 b6: AbstractConstructor<T6>,
76 b6: AbstractConstructor<T6>,
77 b7: AbstractConstructor<T7>
77 b7: AbstractConstructor<T7>
78 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6 & T7>;
78 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6 & T7>;
79
79
80 /** Создает конструктор-заглушку из списка базовых классов, используется
80 /** Создает конструктор-заглушку из списка базовых классов, используется
81 * для объявления классов при помощи `dojo/_base/declare`.
81 * для объявления классов при помощи `dojo/_base/declare`.
82 *
82 *
83 * Создает пустой конструтор, с пустым стандартным прототипом, это нужно,
83 * Создает пустой конструтор, с пустым стандартным прототипом, это нужно,
84 * поскольку в унаследованном классе конструктор обязательно должен вызвать
84 * поскольку в унаследованном классе конструктор обязательно должен вызвать
85 * `super(...)`, таким образом он вызовет пустую функцию.
85 * `super(...)`, таким образом он вызовет пустую функцию.
86 *
86 *
87 * Созданный конструтор хранит в себе список базовых классов, который будет
87 * Созданный конструтор хранит в себе список базовых классов, который будет
88 * использован декоратором `djclass`, который вернет класс, объявленный при
88 * использован декоратором `djclass`, который вернет класс, объявленный при
89 * помощи `dojo/_base/declare`.
89 * помощи `dojo/_base/declare`.
90 *
90 *
91 * @param bases список базовых классов, от которых требуется унаследовать
91 * @param bases список базовых классов, от которых требуется унаследовать
92 * новый класс.
92 * новый класс.
93 *
93 *
94 */
94 */
95 export function djbase(...bases: any[]): Constructor {
95 export function djbase(...bases: any[]): Constructor {
96
96
97 const t = class {
97 const t = class {
98 static mock: boolean;
98 static mock: boolean;
99 static base: AbstractConstructor;
99 static base: AbstractConstructor;
100 };
100 };
101
101
102 t.mock = true;
102 t.mock = true;
103 t.base = declare(bases);
103 t.base = declare(bases);
104
104
105 return t as any;
105 return t as any;
106 }
106 }
107
107
108 function isMockConstructor<T extends {}>(v: AbstractConstructor<T>): v is DjMockConstructor<T> {
108 function isMockConstructor<T extends {}>(v: AbstractConstructor<T>): v is DjMockConstructor<T> {
109 return v && "mock" in v;
109 return v && "mock" in v;
110 }
110 }
111
111
112 /** Создает класс при помощи `dojo/_base/declare`. Для этого исходный класс
112 /** Создает класс при помощи `dojo/_base/declare`. Для этого исходный класс
113 * должен быть унаследован от `djbase(...)`.
113 * должен быть унаследован от `djbase(...)`.
114 *
114 *
115 * @param target Класс, который нужно объявить при помощи `dojo/_base/declare`
115 * @param target Класс, который нужно объявить при помощи `dojo/_base/declare`
116 */
116 */
117 export function djclass<T extends AbstractConstructor>(target: T): T {
117 export function djclass<T extends AbstractConstructor>(target: T): T {
118 // получаем базовый конструктор и его прототип
118 // получаем базовый конструктор и его прототип
119 let bp = target && target.prototype && Object.getPrototypeOf(target.prototype);
119 let bp = target && target.prototype && Object.getPrototypeOf(target.prototype);
120 const bc = bp && bp.constructor;
120 const bc = bp && bp.constructor;
121
121
122 // проверка того, что класс унаследован от специальной заглушки
122 // проверка того, что класс унаследован от специальной заглушки
123 if (isMockConstructor(bc)) {
123 if (isMockConstructor(bc)) {
124 // t - базовый класс, объявленный при помощи dojo/_base/declare
124 // t - базовый класс, объявленный при помощи dojo/_base/declare
125 const t = bc.base;
125 const t = bc.base;
126
126
127 // bc - базовый класс, bc.prototype используется как super
127 // bc - базовый класс, bc.prototype используется как super
128 // при вызове базовых методов. Нужно создать bc.prototype
128 // при вызове базовых методов. Нужно создать bc.prototype
129 // таким образом, чтобы он вызывал this.inherited().
129 // таким образом, чтобы он вызывал this.inherited().
130
130
131 // создаем новый порототип, он не в цепочке прототипов у текущего
131 // создаем новый порототип, он не в цепочке прототипов у текущего
132 // класса, но super.some_method будет использовать именно его.
132 // класса, но super.some_method будет использовать именно его.
133 // в этом объекте будут размещены прокси для переопределенных
133 // в этом объекте будут размещены прокси для переопределенных
134 // методов.
134 // методов.
135 bp = bc.prototype = Object.create(t.prototype);
135 bp = bc.prototype = Object.create(t.prototype);
136 bp.constructor = bc;
136 bp.constructor = bc;
137
137
138 // proxy - фабрика для создания прокси-методов, которые внутри
138 // proxy - фабрика для создания прокси-методов, которые внутри
139 // себя вызовут this.inherited с правильными параметрами.
139 // себя вызовут this.inherited с правильными параметрами.
140 const proxy = (m: (...args: any[]) => any) => function (this: any) {
140 const proxy = (m: (...args: any[]) => any) => function (this: any) {
141 const f = this.getInherited({ callee: m });
141 const f = this.getInherited({ callee: m });
142 return f && f.apply(this, arguments);
142 return f && f.apply(this, arguments);
143
143
144 // так сделать можно только dojo 1.15+
144 // так сделать можно только dojo 1.15+
145 // return this.inherited(m, arguments);
145 // return this.inherited(m, arguments);
146 };
146 };
147
147
148 // у текущего класса прототип содержит методы, объявленные в этом
148 // у текущего класса прототип содержит методы, объявленные в этом
149 // классе и его конструктор. Нужно пройти по всем методам и
149 // классе и его конструктор. Нужно пройти по всем методам и
150 // создать для них прокси.
150 // создать для них прокси.
151 // При этом только те, методы, которые есть в базовых классах
151 // При этом только те, методы, которые есть в базовых классах
152 // могут быть переопределены.
152 // могут быть переопределены.
153 each(target.prototype, (m: any, p: string | number | symbol) => {
153 each(target.prototype, (m: any, p: string | number | symbol) => {
154 if (typeof m === "function" &&
154 if (typeof m === "function" &&
155 p !== "constructor" &&
155 p !== "constructor" &&
156 target.prototype.hasOwnProperty(p) &&
156 target.prototype.hasOwnProperty(p) &&
157 p in t.prototype
157 p in t.prototype
158 ) {
158 ) {
159 bp[p] = proxy(m);
159 bp[p] = proxy(m);
160 }
160 }
161 });
161 });
162
162
163 const cls = declare(t, target.prototype);
163 const cls = declare(t, target.prototype);
164 // TODO mixin static members
164 // TODO mixin static members
165 return cls as any;
165 return cls as any;
166 } else {
166 } else {
167 return target as any;
167 return target as any;
168 }
168 }
169 }
169 }
170
170
171 function makeSetterName(prop: string) {
171 function makeSetterName(prop: string) {
172 return [
172 return [
173 "_set",
173 "_set",
174 prop.replace(/^./, x => x.toUpperCase()),
174 prop.replace(/^./, x => x.toUpperCase()),
175 "Attr"
175 "Attr"
176 ].join("");
176 ].join("");
177 }
177 }
178
178
179 function makeGetterName(prop: string) {
179 function makeGetterName(prop: string) {
180 return [
180 return [
181 "_get",
181 "_get",
182 prop.replace(/^./, x => x.toUpperCase()),
182 prop.replace(/^./, x => x.toUpperCase()),
183 "Attr"
183 "Attr"
184 ].join("");
184 ].join("");
185 }
185 }
186
186
187 interface NodeBindSpec {
187 interface NodeBindSpec {
188 node: string;
188 node: string;
189 type: string;
189 type: "attribute" | "innerText" | "textContent" | "innerHTML" | "class" | "toggleClass";
190 attribute?: string;
191
192 className?: string;
190 }
193 }
191
194
192 /**
195 /**
193 * Описание привязки свойства виджета к свойству внутреннего объекта.
196 * Описание привязки свойства виджета к свойству внутреннего объекта.
194 */
197 */
195 interface MemberBindSpec {
198 interface MemberBindSpec {
196 /**
199 /**
197 * Имя свойства со ссылкой на объект, к которому .
200 * Имя свойства со ссылкой на объект, к которому .
198 */
201 */
199 member: string;
202 member: string;
200 /**
203 /**
201 * Свойство объекта к которому нужно осуществить привязку.
204 * Свойство объекта к которому нужно осуществить привязку.
202 */
205 */
203 property: string;
206 property: string;
204
207
205 /**
208 /**
206 * Привязка осуществляется не только на запись но и на чтение свойства.
209 * Привязка осуществляется не только на запись но и на чтение свойства.
207 */
210 */
208 getter?: boolean;
211 getter?: boolean;
209 }
212 }
210
213
211 function isNodeBindSpec(v: any): v is NodeBindSpec {
214 function isNodeBindSpec(v: any): v is NodeBindSpec {
212 return "node" in v;
215 return "node" in v;
213 }
216 }
214
217
215 function isMemberBindSpec(v: any): v is MemberBindSpec {
218 function isMemberBindSpec(v: any): v is MemberBindSpec {
216 return "member" in v;
219 return "member" in v;
217 }
220 }
218
221
219 /** Декорирует свойства виджета для привязки их к внутренним членам, либо DOM
222 /** Декорирует свойства виджета для привязки их к внутренним членам, либо DOM
220 * элементам, либо свойству внутреннего объекта.
223 * элементам, либо свойству внутреннего объекта.
221 *
224 *
222 * @param {NodeBindSpec | MemberBindSpec} params Параметры связывания.
225 * @param {NodeBindSpec | MemberBindSpec} params Параметры связывания.
223 */
226 */
224 export function bind(params: NodeBindSpec | MemberBindSpec) {
227 export function bind(params: NodeBindSpec | MemberBindSpec) {
225 if (isNodeBindSpec(params))
228 if (isNodeBindSpec(params))
226 return (target: any, name: string) => {
229 return (target: any, name: string) => {
227 target[makeSetterName(name)] = params;
230 target[makeSetterName(name)] = params;
228 };
231 };
229 else if (isMemberBindSpec(params)) {
232 else if (isMemberBindSpec(params)) {
230 return (target: any, name: string) => {
233 return (target: any, name: string) => {
231 target[name] = null;
234 target[name] = null;
232 target[makeSetterName(name)] = function (v: any) {
235 target[makeSetterName(name)] = function (v: any) {
233 this._set(name, v);
236 this._set(name, v);
234 this[params.member].set(params.property, v);
237 this[params.member].set(params.property, v);
235 };
238 };
236 if (params.getter)
239 if (params.getter)
237 target[makeGetterName(name)] = function () {
240 target[makeGetterName(name)] = function () {
238 return this[params.member].get(params.property);
241 return this[params.member].get(params.property);
239 };
242 };
240 };
243 };
241 }
244 }
242 }
245 }
243
246
244 /** Создает в прототипе указанное свойство со значение `undefined`, данный
247 /** Создает в прототипе указанное свойство со значение `undefined`, данный
245 * декоратор следует использовать для свойств, у которых нет значения по-умолчанию
248 * декоратор следует использовать для свойств, у которых нет значения по-умолчанию
246 * и они не могут быть `null | undefined`
249 * и они не могут быть `null | undefined`
247 */
250 */
248 export function prototype(): (p: any, name: string) => void;
251 export function prototype(): (p: any, name: string) => void;
249 /** Создает в прототипе свойство с указанным значением.
252 /** Создает в прототипе свойство с указанным значением.
250 * @param value Значение, которое будет указано в прототипе
253 * @param value Значение, которое будет указано в прототипе
251 */
254 */
252 export function prototype<T>(value: T): <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => void;
255 export function prototype<T>(value: T): <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => void;
253 export function prototype<T>(value?: T) {
256 export function prototype<T>(value?: T) {
254 return <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => {
257 return <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => {
255 p[name] = value as any;
258 p[name] = value as any;
256 };
259 };
257 }
260 }
General Comments 0
You need to be logged in to leave comments. Login now