##// END OF EJS Templates
Added @prototype decorator
cin -
r24:9b77ac3bf8f2 v1.0.0-rc11 default
parent child
Show More
@@ -1,242 +1,257
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 189 type: string;
190 190 }
191 191
192 192 /**
193 193 * Описание привязки свойства виджета к свойству внутреннего объекта.
194 194 */
195 195 interface MemberBindSpec {
196 196 /**
197 197 * Имя свойства со ссылкой на объект, к которому .
198 198 */
199 199 member: string;
200 200 /**
201 201 * Свойство объекта к которому нужно осуществить привязку.
202 202 */
203 203 property: string;
204 204
205 205 /**
206 206 * Привязка осуществляется не только на запись но и на чтение свойства.
207 207 */
208 208 getter?: boolean;
209 209 }
210 210
211 211 function isNodeBindSpec(v: any): v is NodeBindSpec {
212 212 return "node" in v;
213 213 }
214 214
215 215 function isMemberBindSpec(v: any): v is MemberBindSpec {
216 216 return "member" in v;
217 217 }
218 218
219 219 /** Декорирует свойства виджета для привязки их к внутренним членам, либо DOM
220 220 * элементам, либо свойству внутреннего объекта.
221 221 *
222 222 * @param {NodeBindSpec | MemberBindSpec} params Параметры связывания.
223 223 */
224 224 export function bind(params: NodeBindSpec | MemberBindSpec) {
225 225 if (isNodeBindSpec(params))
226 226 return (target: any, name: string) => {
227 227 target[makeSetterName(name)] = params;
228 228 };
229 229 else if (isMemberBindSpec(params)) {
230 230 return (target: any, name: string) => {
231 231 target[name] = null;
232 232 target[makeSetterName(name)] = function (v: any) {
233 233 this._set(name, v);
234 234 this[params.member].set(params.property, v);
235 235 };
236 236 if (params.getter)
237 237 target[makeGetterName(name)] = function () {
238 238 return this[params.member].get(params.property);
239 239 };
240 240 };
241 241 }
242 242 }
243
244 /** Создает в прототипе указанное свойство со значение `undefined`, данный
245 * декоратор следует использовать для свойств, у которых нет значения по-умолчанию
246 * и они не могут быть `null | undefined`
247 */
248 export function prototype(): (p: any, name: string) => void;
249 /** Создает в прототипе свойство с указанным значением.
250 * @param value Значение, которое будет указано в прототипе
251 */
252 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) {
254 return <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => {
255 p[name] = value as any;
256 };
257 }
@@ -1,23 +1,25
1 1 import { _Widget } from "./WidgetContext";
2 2 import { MapOf } from "@implab/core-amd/interfaces";
3 import { prototype } from "../declare";
3 4
4 5 /** Special widget used to create a document fragment */
5 6 export class DjxFragment implements _Widget {
6 7
8 @prototype()
7 9 domNode: Node;
8 10
9 11 containerNode?: Node;
10 12
11 13 constructor() {
12 14 this.domNode = this.containerNode = document.createDocumentFragment();
13 15 }
14 16
15 17 get(attr: string) {
16 18 return undefined;
17 19 }
18 20 set(attr: string, value: any): void;
19 21 set(attrs: MapOf<any>): void;
20 22 set() {
21 23 /* do nothing */
22 24 }
23 25 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now