|
|
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;
|
|
|
|
|
|
const cls = declare(bc.bases, target.prototype);
|
|
|
|
|
|
// 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;
|
|
|
};
|
|
|
}
|
|
|
|