##// END OF EJS Templates
Added 'Attrs', 'Events' type parameters to DjxWidgetBase, typed 'on' and 'emit' methods
Added 'Attrs', 'Events' type parameters to DjxWidgetBase, typed 'on' and 'emit' methods

File last commit:

r30:a46488b209e8 v1.0.0-rc14 default
r30:a46488b209e8 v1.0.0-rc14 default
Show More
declare.ts
256 lines | 9.7 KiB | video/mp2t | TypeScriptLexer
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;
base: 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 base: AbstractConstructor;
};
t.mock = true;
t.base = declare(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.base;
// bc - базовый класс, bc.prototype используется как super
// при вызове базовых методов. Нужно создать bc.prototype
// таким образом, чтобы он вызывал this.inherited().
// создаем новый порототип, он не в цепочке прототипов у текущего
// класса, но super.some_method будет использовать именно его.
// в этом объекте будут размещены прокси для переопределенных
// методов.
bp = bc.prototype = Object.create(t.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) &&
p in t.prototype
) {
bp[p] = proxy(m);
}
});
const cls = declare(t, target.prototype);
// 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;
};
}