declare.ts
256 lines
| 9.7 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"; | ||||
import dojo = require("dojo/_base/kernel"); | ||||
// declare const declare: any; | ||||
type DeclareConstructor<T> = dojo._base.DeclareConstructor<T>; | ||||
export interface AbstractConstructor<T = {}> { | ||||
prototype: T; | ||||
} | ||||
interface DjMockConstructor<T = {}> { | ||||
new(...args: any[]): T; | ||||
mock: boolean; | ||||
bases: AbstractConstructor[]; | ||||
} | ||||
export function djbase<T>( | ||||
b0?: AbstractConstructor<T> | ||||
): 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 список базовых классов, от которых требуется унаследовать | ||||
* новый класс. | ||||
* | ||||
*/ | ||||
export function djbase(...bases: any[]): Constructor { | ||||
const t = class { | ||||
static mock: boolean; | ||||
static bases: AbstractConstructor[]; | ||||
}; | ||||
t.mock = true; | ||||
t.bases = bases; | ||||
return t as any; | ||||
} | ||||
function isMockConstructor<T extends {}>(v: AbstractConstructor<T>): v is DjMockConstructor<T> { | ||||
return v && "mock" in v; | ||||
} | ||||
/** Создает класс при помощи `dojo/_base/declare`. Для этого исходный класс | ||||
* должен быть унаследован от `djbase(...)`. | ||||
* | ||||
* @param target Класс, который нужно объявить при помощи `dojo/_base/declare` | ||||
*/ | ||||
export function djclass<T extends AbstractConstructor>(target: T): T { | ||||
// получаем базовый конструктор и его прототип | ||||
let bp = target && target.prototype && Object.getPrototypeOf(target.prototype); | ||||
const bc = bp && bp.constructor; | ||||
// проверка того, что класс унаследован от специальной заглушки | ||||
if (isMockConstructor(bc)) { | ||||
// t - базовый класс, объявленный при помощи dojo/_base/declare | ||||
const t = bc.bases; | ||||
cin
|
r71 | const cls = declare<any>(bc.bases, target.prototype); | ||
cin
|
r65 | |||
// bc - базовый класс, bc.prototype используется как super | ||||
// при вызове базовых методов. Нужно создать bc.prototype | ||||
// таким образом, чтобы он вызывал this.inherited(). | ||||
// создаем новый порототип, он не в цепочке прототипов у текущего | ||||
// класса, но super.some_method будет использовать именно его. | ||||
// в этом объекте будут размещены прокси для переопределенных | ||||
// методов. | ||||
bp = bc.prototype = Object.create(cls.prototype); | ||||
bp.constructor = bc; | ||||
// proxy - фабрика для создания прокси-методов, которые внутри | ||||
// себя вызовут this.inherited с правильными параметрами. | ||||
const proxy = (m: (...args: any[]) => any) => function (this: any) { | ||||
const f = this.getInherited({ callee: m }); | ||||
return f && f.apply(this, arguments); | ||||
// так сделать можно только dojo 1.15+ | ||||
// return this.inherited(m, arguments); | ||||
}; | ||||
// у текущего класса прототип содержит методы, объявленные в этом | ||||
// классе и его конструктор. Нужно пройти по всем методам и | ||||
// создать для них прокси. | ||||
// При этом только те, методы, которые есть в базовых классах | ||||
// могут быть переопределены. | ||||
each(target.prototype, (m: any, p: string | number | symbol) => { | ||||
if (typeof m === "function" && | ||||
p !== "constructor" && | ||||
target.prototype.hasOwnProperty(p) | ||||
) { | ||||
bp[p] = proxy(m); | ||||
} | ||||
}); | ||||
// TODO mixin static members | ||||
return cls as any; | ||||
} else { | ||||
return target as any; | ||||
} | ||||
} | ||||
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; | ||||
} | ||||
function isNodeBindSpec(v: any): v is NodeBindSpec { | ||||
return "node" in v; | ||||
} | ||||
/** Декорирует свойства виджета для привязки их к внутренним членам, либо DOM | ||||
* элементам, либо свойству внутреннего объекта. | ||||
* | ||||
* @param {NodeBindSpec | MemberBindSpec} params Параметры связывания. | ||||
*/ | ||||
export function bind(params: NodeBindSpec | MemberBindSpec) { | ||||
if (isNodeBindSpec(params)) { | ||||
return (target: any, name: string) => { | ||||
target[makeSetterName(name)] = params; | ||||
}; | ||||
} else { | ||||
return (target: any, name: string) => { | ||||
target[name] = null; | ||||
target[makeSetterName(name)] = function (v: any) { | ||||
this._set(name, v); | ||||
this[params.member].set(params.property, v); | ||||
}; | ||||
if (params.getter) | ||||
target[makeGetterName(name)] = function () { | ||||
return this[params.member].get(params.property); | ||||
}; | ||||
}; | ||||
} | ||||
} | ||||
/** Создает в прототипе указанное свойство со значение `undefined`, данный | ||||
* декоратор следует использовать для свойств, у которых нет значения по-умолчанию | ||||
* и они не могут быть `null | undefined` | ||||
*/ | ||||
export function prototype(): (p: any, name: string) => void; | ||||
/** Создает в прототипе свойство с указанным значением. | ||||
* @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) { | ||||
return <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => { | ||||
p[name] = value as any; | ||||
}; | ||||
} | ||||