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