4. Свойства, методы, события элемента управления
Вы создаете объект typescript
и указываете какой интерфейс он реализует. Например:
<IElement>{
type:'IElement',
name: 'elem'
}
Этот объект реализует интерфейс IElement
, т.е. описан минимальный обязательный набор свойств и методов.
Когда на базе вашего описания AppManager
создаст "runtime" объект, он тоже будет реализовывать интерфейс IElement
и свойство name
этого объекта тоже будет 'elem'
.
4.1. Свойства и методы
Далее идет описание свойств и методов так, как реализовано в самом javascript
. Библиотека ui-organizer здесь ничего нового не добавляет. Она только реализует возможность описать эти свойства и методы для элементов.
Рассмотрим что требует от нас интерфейс на примере базового интерфейса IElement
. Все остальные интерфейсы являются наследниками IElement
, т.е. во всех элементах управления доступны эти свойства и методы. Подробное описание смотрите в api.
export interface IElement {
type: string;
name: string;
caption?: string;
visibility?: boolean;
readonly?: boolean;
justifyContent?: JustifyContent;
textSelect?: boolean;
positionAbsolute?: boolean;
alignSelf?: AlignSelf;
onBeforeLoad?(form: IForm, elem: IElement, data: any): Promise<boolean>;
onAfterLoad?(form: IForm, elem: IElement, data: any): Promise<boolean>;
value?: string | number | string[]
parent?: IGroup;
form?: IForm;
dom?: HTMLElement;
addClass?(className: string): void;
removeClass?(className: string): void;
isType?(type: string | typeof UIElement): boolean;
}
Те свойства и методы, которые помечены словом /*runtime*/
не доступны в процессе описания формы. Если вы их опишите, AppManager
их проигнорирует. Эти свойства и методы доступны только у "runtime" объекта.
Например, вы можете задать свойство value
и добавить класс addClass()
в процессе выполнения:
export var baseElem: IElement = <IElement>{
type: 'IElement',
name: 'baseElem',
caption: 'Базовый элемент',
onAfterLoad: async function (form: IForm, elem: IElement, data: any) {
elem.value = elem.caption;
elem.addClass('someclass');
return true;
}
}
Все остальные свойства вы можете задавать по своему усмотрению для достижения нужного размещения на форме и поведения элемента управления. Причем, обязательно задать только два свойства: type
и name
.
Из предыдущего примера видно, что задано три свойства и один метод:
- обязательные свойства:
type
и name
,
- необязательные свойства:
caption
,
- необязательный метод:
onAfterLoad
, который вызывается "runtime" объектом после своей загрузки.
Все необязательные методы, доступные в процессе описания являются обработчиками событий и начинаются с префикса "on", например onAfterLoad
.
4.2. События
Далее идет описание событий так как реализовано в модуле "event" node js и браузера. Библиотека ui-organizer здесь ничего нового не добавляет. Она только реализует возможность описать эти события.
Каждый элемент управления генерирует события. Например, объект (элемент управления), который реализует интерфейс IElement
генерирует следующие события. Все остальные интерфейсы являются наследниками IElement
, поэтому все элементы управления также генерируют эти события.
export interface IElementEvents {
beforeLoad(form: IForm, elem: IElement, data: any): void,
afterLoad(form: IForm, elem: IElement, data: any): void,
show(form: IForm, element: IElement): void,
hide(form: IForm, element: IElement): void,
}
Обработчики этих событий создаются в runtime
, например:
export var baseElem: IElement = <IElement>{
type: 'IElement',
name: 'baseElem',
caption: 'Базовый элемент',
onAfterLoad: async function (form: IForm, elem: IElement, data: any) {
elem.on("show", (_form, _elem) => {
elem.addClass('visible');
});
elem.on("hide", (_form, _elem) => {
elem.removeClass('visible');
})
return true;
}
}
Почему в интерфейсе IElement указаны методы onBeforeLoad
, onAfterLoad
и события
beforeLoad
, afterLoad
?
Потому что обработчики события могут быть разными, например:
export var form: IForm = <IForm>{
type: 'IForm',
name: 'simpleForm',
flex: Flex.flexible,
grouping: Grouping.vertical,
elements: [
<IElement>{
type: 'IElement',
name: 'header',
onAfterLoad: async function (form: IForm, elem: IElement, data: any) {
elem.addClass('header');
return true;
}
},
],
onBeforeLoad: async function (form: IForm, elem: IElement, data: any) {
var header: IElement = form.getElement('header');
header.on("afterLoad", (_form, _elem) => {
_elem.value = 'Мое приложение';
})
return true;
}
}
В примере элемент IElement
имеет обработчик события onAfterLoad
со своей логикой. Форма добавляет к событию afterLoad
элемента дополнительную логику.
4.3. Расширение интерфейсов
Например, вы хотите, чтобы ваш элемент управления имел дополнительное обязательное свойство someProperty
, метод setSomeProperty
и генерировал событие someSetted
. Для этого задайте интерфейс. Конечно, вы можете не создавать интерфейс, это и так будет работать, но задание интерфейса убережет вас от ошибок и сэкономит кучу времени в будущем.
export interface ISomeElement<T=ISomeElementEvents> extends IElement<T> {
someProperty: string;
setSomeProperty(val: string): void;
}
export interface ISomeElementEvents {
someSetted(form: IForm, element: IElement): void,
}
Описание вашего элемента управления может быть следующим:
export var elem: ISomeElement = <ISomeElement>{
type: 'IElement',
name: 'elem',
someProperty: undefined,
setSomeProperty: function (val: string) {
someProperty = val;
this.emit("someSetted", this.form, this);
}
}
Обратите внимание свойство type
указывает на изначальный интерфейс IElement
.
5. Работа с классами
Когда вы работаете с объектами, вы можете использовать объект один раз. Если вы присвоите объект, например let elem = <IElement>{...}
, двум разным формам, то эти формы будут работать с одним и тем же объектом.
Но, вы можете работать с классами. Вы точно так же описываете класс, который реализует интерфейс нужного элемента управления и наследуете этот класс от специального класса этого элемента управления как показано на примере ниже. Потом, добавляя на форму элемент управления вы просто вызываете new MyElement()
:
class MyElement extends UIElement{
async onAfterLoad(){
this.value = 'Мой элемент.';
return true;
}
}
class MyExtraElement extends MyElement {
async onAfterLoad(){
this.value = 'Мой экстра элемент.';
return true;
}
}
export class MyForm extends UIForm {
constructor(name: string){
super();
this.name = name;
this.elements.push(new MyElement());
this.elements.push(new MyExtraElement());
}
}
let formName: string = 'simpleForm';
AppManager.add([new MyForm(formName)]);
AppManager.open(formName, undefined, undefined);
Для каждого интерфейса создан специальный класс, который реализует этот интерфейс и от которого вы можете создавать наследников для описания элементов управления:
- interface IElement - class UIElement;
- interface IDataElement - class UIDataElement;
- interface IGroup - class UIGroup;
- ... и так далее для всех интерфейсов.
Если вы не наследуете от класса UIElement, то вам нужно указать свойство type:
class MyElement implements IElement{
type = UIElement;
async onAfterLoad(){
this.value = 'Мой элемент.';
return true;
}
}