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 = dojo._base.DeclareConstructor; export interface AbstractConstructor { prototype: T; } interface DjMockConstructor { new(...args: any[]): T; mock: boolean; bases: AbstractConstructor[]; } export function djbase( b0?: AbstractConstructor ): DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor ): DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor ): DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor ): DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor, b4: AbstractConstructor ): DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor, b4: AbstractConstructor, b5: AbstractConstructor ): DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor, b4: AbstractConstructor, b5: AbstractConstructor, b6: AbstractConstructor ): DeclareConstructor; export function djbase( b0: AbstractConstructor, b1: AbstractConstructor, b2: AbstractConstructor, b3: AbstractConstructor, b4: AbstractConstructor, b5: AbstractConstructor, b6: AbstractConstructor, b7: AbstractConstructor ): DeclareConstructor; /** Создает конструктор-заглушку из списка базовых классов, используется * для объявления классов при помощи `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(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.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(value: T):

(p: P, name: K) => void; export function prototype(value?: T) { return

(p: P, name: K) => { p[name] = value as any; }; }