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