##// END OF EJS Templates
corrected tear down logic handling in observables. Added support for observable query results
corrected tear down logic handling in observables. Added support for observable query results

File last commit:

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