declare.ts
254 lines
| 10.0 KiB
| video/mp2t
|
TypeScriptLexer
cin
|
r65 | import declare = require("dojo/_base/declare"); | ||
import { each } from "@implab/core-amd/safe"; | ||||
import { Constructor } from "@implab/core-amd/interfaces"; | ||||
// declare const declare: any; | ||||
type DeclareConstructor<T> = dojo._base.DeclareConstructor<T>; | ||||
cin
|
r107 | export interface AbstractConstructor<T = object> { | ||
cin
|
r65 | prototype: T; | ||
} | ||||
cin
|
r107 | interface DjMockConstructor<T = object> { | ||
new(...args: unknown[]): T; | ||||
cin
|
r65 | mock: boolean; | ||
bases: AbstractConstructor[]; | ||||
} | ||||
cin
|
r107 | export function djbase<T>(): DeclareConstructor<T>; | ||
cin
|
r65 | export function djbase<T>( | ||
cin
|
r107 | b0: AbstractConstructor<T> | ||
cin
|
r65 | ): DeclareConstructor<T>; | ||
export function djbase<T0, T1>( | ||||
b0: AbstractConstructor<T0>, | ||||
b1: AbstractConstructor<T1> | ||||
): DeclareConstructor<T0 & T1>; | ||||
export function djbase<T0, T1, T2>( | ||||
b0: AbstractConstructor<T0>, | ||||
b1: AbstractConstructor<T1>, | ||||
b2: AbstractConstructor<T2> | ||||
): DeclareConstructor<T0 & T1 & T2>; | ||||
export function djbase<T0, T1, T2, T3>( | ||||
b0: AbstractConstructor<T0>, | ||||
b1: AbstractConstructor<T1>, | ||||
b2: AbstractConstructor<T2>, | ||||
b3: AbstractConstructor<T3> | ||||
): DeclareConstructor<T0 & T1 & T2 & T3>; | ||||
export function djbase<T0, T1, T2, T3, T4>( | ||||
b0: AbstractConstructor<T0>, | ||||
b1: AbstractConstructor<T1>, | ||||
b2: AbstractConstructor<T2>, | ||||
b3: AbstractConstructor<T3>, | ||||
b4: AbstractConstructor<T4> | ||||
): DeclareConstructor<T0 & T1 & T2 & T3 & T4>; | ||||
export function djbase<T0, T1, T2, T3, T4, T5>( | ||||
b0: AbstractConstructor<T0>, | ||||
b1: AbstractConstructor<T1>, | ||||
b2: AbstractConstructor<T2>, | ||||
b3: AbstractConstructor<T3>, | ||||
b4: AbstractConstructor<T4>, | ||||
b5: AbstractConstructor<T5> | ||||
): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5>; | ||||
export function djbase<T0, T1, T2, T3, T4, T5, T6>( | ||||
b0: AbstractConstructor<T0>, | ||||
b1: AbstractConstructor<T1>, | ||||
b2: AbstractConstructor<T2>, | ||||
b3: AbstractConstructor<T3>, | ||||
b4: AbstractConstructor<T4>, | ||||
b5: AbstractConstructor<T5>, | ||||
b6: AbstractConstructor<T6> | ||||
): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6>; | ||||
export function djbase<T0, T1, T2, T3, T4, T5, T6, T7>( | ||||
b0: AbstractConstructor<T0>, | ||||
b1: AbstractConstructor<T1>, | ||||
b2: AbstractConstructor<T2>, | ||||
b3: AbstractConstructor<T3>, | ||||
b4: AbstractConstructor<T4>, | ||||
b5: AbstractConstructor<T5>, | ||||
b6: AbstractConstructor<T6>, | ||||
b7: AbstractConstructor<T7> | ||||
): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6 & T7>; | ||||
/** Создает конструктор-заглушку из списка базовых классов, используется | ||||
* для объявления классов при помощи `dojo/_base/declare`. | ||||
* | ||||
* Создает пустой конструктор, с пустым стандартным прототипом, это нужно, | ||||
* поскольку в унаследованном классе конструктор обязательно должен вызвать | ||||
* `super(...)`, таким образом он вызовет пустую функцию. | ||||
* | ||||
* Созданный конструктор хранит в себе список базовых классов, который будет | ||||
* использован декоратором `djclass`, который вернет класс, объявленный при | ||||
* помощи `dojo/_base/declare`. | ||||
* | ||||
* @param bases список базовых классов, от которых требуется унаследовать | ||||
* новый класс. | ||||
* | ||||
*/ | ||||
cin
|
r107 | export function djbase(...bases: AbstractConstructor[]): Constructor { | ||
cin
|
r65 | |||
const t = class { | ||||
static mock: boolean; | ||||
static bases: AbstractConstructor[]; | ||||
}; | ||||
t.mock = true; | ||||
t.bases = bases; | ||||
cin
|
r107 | return t as Constructor; | ||
cin
|
r65 | } | ||
cin
|
r107 | function isMockConstructor<T extends object>(v: AbstractConstructor<T>): v is DjMockConstructor<T> { | ||
cin
|
r65 | return v && "mock" in v; | ||
} | ||||
/** Создает класс при помощи `dojo/_base/declare`. Для этого исходный класс | ||||
* должен быть унаследован от `djbase(...)`. | ||||
* | ||||
* @param target Класс, который нужно объявить при помощи `dojo/_base/declare` | ||||
*/ | ||||
export function djclass<T extends AbstractConstructor>(target: T): T { | ||||
// получаем базовый конструктор и его прототип | ||||
cin
|
r107 | const bp = target && !!target.prototype && Object.getPrototypeOf(target.prototype) as object; | ||
cin
|
r65 | const bc = bp && bp.constructor; | ||
// проверка того, что класс унаследован от специальной заглушки | ||||
if (isMockConstructor(bc)) { | ||||
cin
|
r79 | // bc.bases - базовый класс, объявленный при помощи dojo/_base/declare | ||
cin
|
r107 | const cls = declare(bc.bases, target.prototype) as unknown as T; | ||
cin
|
r65 | |||
// bc - базовый класс, bc.prototype используется как super | ||||
// при вызове базовых методов. Нужно создать bc.prototype | ||||
// таким образом, чтобы он вызывал this.inherited(). | ||||
cin
|
r107 | // создаем новый прототип, он не в цепочке прототипов у текущего | ||
cin
|
r65 | // класса, но super.some_method будет использовать именно его. | ||
// в этом объекте будут размещены прокси для переопределенных | ||||
// методов. | ||||
cin
|
r107 | const nbp = bc.prototype = Object.create(cls.prototype) as Record<string, unknown>; | ||
nbp.constructor = bc; | ||||
cin
|
r65 | |||
// proxy - фабрика для создания прокси-методов, которые внутри | ||||
// себя вызовут this.inherited с правильными параметрами. | ||||
cin
|
r107 | const proxy = (m: (...args: unknown[]) => unknown) => function (this: dojo._base.DeclareCreatedObject, ...args: unknown[]) { | ||
cin
|
r108 | const f = this.getInherited({ callee: m, ...args, length: args.length }); | ||
cin
|
r107 | return f ? f.apply(this, args) as unknown : undefined; | ||
cin
|
r65 | |||
// так сделать можно только dojo 1.15+ | ||||
// return this.inherited(m, arguments); | ||||
}; | ||||
// у текущего класса прототип содержит методы, объявленные в этом | ||||
// классе и его конструктор. Нужно пройти по всем методам и | ||||
// создать для них прокси. | ||||
// При этом только те, методы, которые есть в базовых классах | ||||
// могут быть переопределены. | ||||
cin
|
r107 | each(target.prototype, (m: unknown, p: string) => { | ||
cin
|
r65 | if (typeof m === "function" && | ||
p !== "constructor" && | ||||
cin
|
r107 | Object.prototype.hasOwnProperty.call(target, p) | ||
cin
|
r65 | ) { | ||
cin
|
r107 | nbp[p] = proxy(m as (...args: unknown[]) => unknown); | ||
cin
|
r65 | } | ||
}); | ||||
// TODO mixin static members | ||||
cin
|
r107 | return cls; | ||
cin
|
r65 | } else { | ||
cin
|
r107 | return target; | ||
cin
|
r65 | } | ||
} | ||||
function makeSetterName(prop: string) { | ||||
return [ | ||||
"_set", | ||||
prop.replace(/^./, x => x.toUpperCase()), | ||||
"Attr" | ||||
].join(""); | ||||
} | ||||
function makeGetterName(prop: string) { | ||||
return [ | ||||
"_get", | ||||
prop.replace(/^./, x => x.toUpperCase()), | ||||
"Attr" | ||||
].join(""); | ||||
} | ||||
interface NodeBindSpec { | ||||
node: string; | ||||
type: "attribute" | "innerText" | "textContent" | "innerHTML" | "class" | "toggleClass"; | ||||
attribute?: string; | ||||
className?: string; | ||||
} | ||||
/** | ||||
* Описание привязки свойства виджета к свойству внутреннего объекта. | ||||
*/ | ||||
interface MemberBindSpec { | ||||
/** | ||||
* Имя свойства со ссылкой на объект, к которому . | ||||
*/ | ||||
member: string; | ||||
/** | ||||
* Свойство объекта к которому нужно осуществить привязку. | ||||
*/ | ||||
property: string; | ||||
/** | ||||
* Привязка осуществляется не только на запись но и на чтение свойства. | ||||
*/ | ||||
getter?: boolean; | ||||
} | ||||
cin
|
r107 | function isNodeBindSpec(v: object): v is NodeBindSpec { | ||
cin
|
r65 | return "node" in v; | ||
} | ||||
/** Декорирует свойства виджета для привязки их к внутренним членам, либо DOM | ||||
* элементам, либо свойству внутреннего объекта. | ||||
* | ||||
* @param {NodeBindSpec | MemberBindSpec} params Параметры связывания. | ||||
*/ | ||||
export function bind(params: NodeBindSpec | MemberBindSpec) { | ||||
if (isNodeBindSpec(params)) { | ||||
cin
|
r108 | return <K extends string>(target: Record<K, unknown>, name: K) => { | ||
target[makeSetterName(name) as K /** hack to not go insane) */] = params; | ||||
cin
|
r65 | }; | ||
} else { | ||||
cin
|
r108 | return <K extends string, | ||
T extends { [p in K]: p extends "_set" ? (name: p, v: unknown) => void : unknown; }> (target: T, name: K) => { | ||||
target[name] = undefined as T[K]; | ||||
target[makeSetterName(name) as K] = function (this: T, v: unknown) { | ||||
cin
|
r65 | this._set(name, v); | ||
cin
|
r107 | const inner = this[params.member] as Record<string, unknown>; | ||
cin
|
r108 | if (typeof inner.set === "function") | ||
cin
|
r107 | inner.set(params.property, v); | ||
cin
|
r108 | } as T[K]; | ||
cin
|
r65 | if (params.getter) | ||
target[makeGetterName(name)] = function () { | ||||
cin
|
r107 | const inner = this[params.member] as Record<string, unknown>; | ||
cin
|
r108 | if (typeof inner.get === "function") | ||
cin
|
r107 | return inner.get(params.property) as unknown; | ||
cin
|
r65 | }; | ||
}; | ||||
} | ||||
} | ||||
/** Создает в прототипе свойство с указанным значением. | ||||
* @param value Значение, которое будет указано в прототипе | ||||
*/ | ||||
export function prototype<T>(value: T): <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => void; | ||||
export function prototype<T>(value?: T) { | ||||
cin
|
r108 | return (p: { [m in string]: T | undefined }, name: string) => { | ||
p[name] = value; | ||||
cin
|
r65 | }; | ||
} | ||||