import declare = require("dojo/_base/declare"); import { each } from "@implab/core-amd/safe"; import { Constructor } from "@implab/core-amd/interfaces"; export interface AbstractConstructor { prototype: T; } interface DjMockConstructor { new(...args: any[]): T; mock: boolean; base: AbstractConstructor; } export function djbase( b0: AbstractConstructor ): dojo._base.DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor ): dojo._base.DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor ): dojo._base.DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor ): dojo._base.DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor, b4: AbstractConstructor ): dojo._base.DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor, b4: AbstractConstructor, b5: AbstractConstructor ): dojo._base.DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor, b4: AbstractConstructor, b5: AbstractConstructor, b6: AbstractConstructor ): dojo._base.DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor, b4: AbstractConstructor, b5: AbstractConstructor, b6: AbstractConstructor, b7: AbstractConstructor ): dojo._base.DeclareConstructor; /** Создает конструктор-заглушку из списка базовых классов, используется * для объявления классов при помощи `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(v: AbstractConstructor): v is DjMockConstructor { return v && "mock" in v; } /** Создает класс при помощи `dojo/_base/declare`. Для этого исходный класс * должен быть унаследован от `djbase(...)`. * * @param target Класс, который нужно объявить при помощи `dojo/_base/declare` */ export function djclass(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 () { 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: string; } /** * Описание привязки свойства виджета к свойству внутреннего объекта. */ interface MemberBindSpec { /** * Имя свойства со ссылкой на объект, к которому . */ member: string; /** * Свойство объекта к которому нужно осуществить привязку. */ property: string; /** * Привязка осуществляется не только на запись но и на чтение свойства. */ getter?: boolean; } function isNodeBindSpec(v): v is NodeBindSpec { return "node" in v; } function isMemberBindSpec(v): v is MemberBindSpec { return "member" 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 if (isMemberBindSpec(params)) { return (target: any, name: string) => { target[name] = null; target[makeSetterName(name)] = function (v) { 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); }; }; } }