declare.ts
255 lines
| 10.0 KiB
| video/mp2t
|
TypeScriptLexer
|
|
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>; | ||||
|
|
r107 | export interface AbstractConstructor<T = object> { | ||
|
|
r65 | prototype: T; | ||
| } | ||||
|
|
r107 | interface DjMockConstructor<T = object> { | ||
| new(...args: unknown[]): T; | ||||
|
|
r65 | mock: boolean; | ||
| bases: AbstractConstructor[]; | ||||
| } | ||||
|
|
r107 | export function djbase<T>(): DeclareConstructor<T>; | ||
|
|
r65 | export function djbase<T>( | ||
|
|
r107 | b0: AbstractConstructor<T> | ||
|
|
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 список базовых классов, от которых требуется унаследовать | ||||
| * новый класс. | ||||
| * | ||||
| */ | ||||
|
|
r107 | export function djbase(...bases: AbstractConstructor[]): Constructor { | ||
|
|
r65 | |||
| const t = class { | ||||
| static mock: boolean; | ||||
| static bases: AbstractConstructor[]; | ||||
| }; | ||||
| t.mock = true; | ||||
| t.bases = bases; | ||||
|
|
r107 | return t as Constructor; | ||
|
|
r65 | } | ||
|
|
r107 | function isMockConstructor<T extends object>(v: AbstractConstructor<T>): v is DjMockConstructor<T> { | ||
|
|
r65 | return v && "mock" in v; | ||
| } | ||||
| /** Создает класс при помощи `dojo/_base/declare`. Для этого исходный класс | ||||
| * должен быть унаследован от `djbase(...)`. | ||||
| * | ||||
| * @param target Класс, который нужно объявить при помощи `dojo/_base/declare` | ||||
| */ | ||||
| export function djclass<T extends AbstractConstructor>(target: T): T { | ||||
| // получаем базовый конструктор и его прототип | ||||
|
|
r107 | const bp = target && !!target.prototype && Object.getPrototypeOf(target.prototype) as object; | ||
|
|
r65 | const bc = bp && bp.constructor; | ||
| // проверка того, что класс унаследован от специальной заглушки | ||||
| if (isMockConstructor(bc)) { | ||||
|
|
r79 | // bc.bases - базовый класс, объявленный при помощи dojo/_base/declare | ||
|
|
r107 | const cls = declare(bc.bases, target.prototype) as unknown as T; | ||
|
|
r65 | |||
| // bc - базовый класс, bc.prototype используется как super | ||||
| // при вызове базовых методов. Нужно создать bc.prototype | ||||
| // таким образом, чтобы он вызывал this.inherited(). | ||||
|
|
r107 | // создаем новый прототип, он не в цепочке прототипов у текущего | ||
|
|
r65 | // класса, но super.some_method будет использовать именно его. | ||
| // в этом объекте будут размещены прокси для переопределенных | ||||
| // методов. | ||||
|
|
r107 | const nbp = bc.prototype = Object.create(cls.prototype) as Record<string, unknown>; | ||
| nbp.constructor = bc; | ||||
|
|
r65 | |||
| // proxy - фабрика для создания прокси-методов, которые внутри | ||||
| // себя вызовут this.inherited с правильными параметрами. | ||||
|
|
r109 | const proxy = (m: (...args: unknown[]) => unknown) => function (this: dojo._base.DeclareCreatedObject) { | ||
| const f = this.getInherited({ callee: m } as unknown as IArguments); | ||||
| // eslint-disable-next-line prefer-rest-params | ||||
| return f ? f.apply(this, arguments) as unknown : undefined; | ||||
|
|
r65 | |||
| // так сделать можно только dojo 1.15+ | ||||
| // return this.inherited(m, arguments); | ||||
| }; | ||||
| // у текущего класса прототип содержит методы, объявленные в этом | ||||
| // классе и его конструктор. Нужно пройти по всем методам и | ||||
| // создать для них прокси. | ||||
| // При этом только те, методы, которые есть в базовых классах | ||||
| // могут быть переопределены. | ||||
|
|
r107 | each(target.prototype, (m: unknown, p: string) => { | ||
|
|
r65 | if (typeof m === "function" && | ||
| p !== "constructor" && | ||||
|
|
r109 | Object.prototype.hasOwnProperty.call(target.prototype, p) | ||
|
|
r65 | ) { | ||
|
|
r107 | nbp[p] = proxy(m as (...args: unknown[]) => unknown); | ||
|
|
r65 | } | ||
| }); | ||||
| // TODO mixin static members | ||||
|
|
r107 | return cls; | ||
|
|
r65 | } else { | ||
|
|
r107 | return target; | ||
|
|
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; | ||||
| } | ||||
|
|
r107 | function isNodeBindSpec(v: object): v is NodeBindSpec { | ||
|
|
r65 | return "node" in v; | ||
| } | ||||
| /** Декорирует свойства виджета для привязки их к внутренним членам, либо DOM | ||||
| * элементам, либо свойству внутреннего объекта. | ||||
| * | ||||
| * @param {NodeBindSpec | MemberBindSpec} params Параметры связывания. | ||||
| */ | ||||
| export function bind(params: NodeBindSpec | MemberBindSpec) { | ||||
| if (isNodeBindSpec(params)) { | ||||
|
|
r108 | return <K extends string>(target: Record<K, unknown>, name: K) => { | ||
| target[makeSetterName(name) as K /** hack to not go insane) */] = params; | ||||
|
|
r65 | }; | ||
| } else { | ||||
|
|
r109 | return <K extends string>(target: Record<K | "_set", unknown>, name: K) => { | ||
| target[name] = undefined; | ||||
| target[makeSetterName(name) as K] = function (this: typeof target, v: unknown) { | ||||
| (this._set as (n: K, v: unknown) => void)(name, v); | ||||
| const inner = this[params.member as K] as Record<string, unknown>; | ||||
|
|
r108 | if (typeof inner.set === "function") | ||
|
|
r107 | inner.set(params.property, v); | ||
|
|
r109 | }; | ||
|
|
r65 | if (params.getter) | ||
|
|
r109 | target[makeGetterName(name) as K] = function (this: typeof target) { | ||
| const inner = this[params.member as K] as Record<string, unknown>; | ||||
|
|
r108 | if (typeof inner.get === "function") | ||
|
|
r107 | return inner.get(params.property) as unknown; | ||
|
|
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) { | ||||
|
|
r108 | return (p: { [m in string]: T | undefined }, name: string) => { | ||
| p[name] = value; | ||||
|
|
r65 | }; | ||
| } | ||||
