pandorafms/visual_console_client/src/Form.ts

255 lines
6.8 KiB
TypeScript

import TypedEvent, { Listener, Disposable } from "./lib/TypedEvent";
import { AnyObject, UnknownObject } from "./lib/types";
import { t } from "./lib";
interface InputGroupDataRequestedEvent {
identifier: string;
params: UnknownObject;
done: (error: Error | null, data?: unknown) => void;
}
// TODO: Document
export abstract class InputGroup<Data extends {} = {}> {
private _name: string = "";
private _element?: HTMLElement;
public readonly initialData: Data;
protected currentData: Partial<Data> = {};
// Event manager for data requests.
private readonly dataRequestedEventManager = new TypedEvent<
InputGroupDataRequestedEvent
>();
public constructor(name: string, initialData: Data) {
this.name = name;
this.initialData = initialData;
}
public set name(name: string) {
if (name.length === 0) throw new RangeError("empty name");
this._name = name;
}
public get name(): string {
return this._name;
}
public get data(): Partial<Data> {
return { ...this.currentData };
}
public get element(): HTMLElement {
if (this._element == null) {
const element = document.createElement("div");
element.className = `input-group input-group-${this.name}`;
const content = this.createContent();
if (content instanceof Array) {
content.forEach(element.appendChild);
} else {
element.appendChild(content);
}
this._element = element;
}
return this._element;
}
public reset(): void {
this.currentData = {};
}
protected updateData(data: Partial<Data>): void {
this.currentData = {
...this.currentData,
...data
};
// TODO: Update item.
}
protected requestData(
identifier: string,
params: UnknownObject,
done: (error: Error | null, data?: unknown) => void
): void {
this.dataRequestedEventManager.emit({ identifier, params, done });
}
public onDataRequested(
listener: Listener<InputGroupDataRequestedEvent>
): Disposable {
return this.dataRequestedEventManager.on(listener);
}
protected abstract createContent(): HTMLElement | HTMLElement[];
// public abstract get isValid(): boolean;
}
export interface SubmitFormEvent {
nativeEvent: Event;
data: AnyObject;
}
// TODO: Document
export class FormContainer {
public readonly title: string;
private inputGroupsByName: { [name: string]: InputGroup } = {};
private enabledInputGroupNames: string[] = [];
// Event manager for submit events.
private readonly submitEventManager = new TypedEvent<SubmitFormEvent>();
// Event manager for item data requests.
private readonly itemDataRequestedEventManager = new TypedEvent<
InputGroupDataRequestedEvent
>();
private handleItemDataRequested = this.itemDataRequestedEventManager.emit;
public constructor(
title: string,
inputGroups: InputGroup[] = [],
enabledInputGroups: string[] = []
) {
this.title = title;
if (inputGroups.length > 0) {
this.inputGroupsByName = inputGroups.reduce((prevVal, inputGroup) => {
// Add event handlers.
inputGroup.onDataRequested(this.handleItemDataRequested);
prevVal[inputGroup.name] = inputGroup;
return prevVal;
}, this.inputGroupsByName);
}
if (enabledInputGroups.length > 0) {
this.enabledInputGroupNames = [
...this.enabledInputGroupNames,
...enabledInputGroups.filter(
name => this.inputGroupsByName[name] != null
)
];
}
}
public getInputGroup(inputGroupName: string): InputGroup | null {
return this.inputGroupsByName[inputGroupName] || null;
}
public addInputGroup(
inputGroup: InputGroup,
index: number | null = null
): FormContainer {
// Add event handlers.
inputGroup.onDataRequested(this.handleItemDataRequested);
this.inputGroupsByName[inputGroup.name] = inputGroup;
// Remove the current stored name if exist.
this.enabledInputGroupNames = this.enabledInputGroupNames.filter(
name => name !== inputGroup.name
);
if (index !== null) {
if (index <= 0) {
this.enabledInputGroupNames = [
inputGroup.name,
...this.enabledInputGroupNames
];
} else if (index >= this.enabledInputGroupNames.length) {
this.enabledInputGroupNames = [
...this.enabledInputGroupNames,
inputGroup.name
];
} else {
this.enabledInputGroupNames = [
// part of the array before the specified index
...this.enabledInputGroupNames.slice(0, index),
// inserted item
inputGroup.name,
// part of the array after the specified index
...this.enabledInputGroupNames.slice(index)
];
}
} else {
this.enabledInputGroupNames = [
...this.enabledInputGroupNames,
inputGroup.name
];
}
return this;
}
public removeInputGroup(inputGroupName: string): FormContainer {
delete this.inputGroupsByName[inputGroupName];
// Remove the current stored name.
this.enabledInputGroupNames = this.enabledInputGroupNames.filter(
name => name !== inputGroupName
);
return this;
}
public getFormElement(
type: "creation" | "update" = "update"
): HTMLFormElement {
const form = document.createElement("form");
form.id = "visual-console-item-edition";
form.className = "visual-console-item-edition";
form.addEventListener("submit", e => {
e.preventDefault();
this.submitEventManager.emit({
nativeEvent: e,
data: this.enabledInputGroupNames.reduce((data, name) => {
if (this.inputGroupsByName[name]) {
data = {
...data,
...this.inputGroupsByName[name].data
};
}
return data;
}, {})
});
});
const formContent = document.createElement("div");
formContent.className = "input-groups";
this.enabledInputGroupNames.forEach(name => {
if (this.inputGroupsByName[name]) {
formContent.appendChild(this.inputGroupsByName[name].element);
}
});
form.appendChild(formContent);
return form;
}
public reset(): void {
this.enabledInputGroupNames.forEach(name => {
if (this.inputGroupsByName[name]) {
this.inputGroupsByName[name].reset();
}
});
}
// public get isValid(): boolean {
// for (let i = 0; i < this.enabledInputGroupNames.length; i++) {
// const inputGroup = this.inputGroupsByName[this.enabledInputGroupNames[i]];
// if (inputGroup && !inputGroup.isValid) return false;
// }
// return true;
// }
public onSubmit(listener: Listener<SubmitFormEvent>): Disposable {
return this.submitEventManager.on(listener);
}
public onInputGroupDataRequested(
listener: Listener<InputGroupDataRequestedEvent>
): Disposable {
return this.itemDataRequestedEventManager.on(listener);
}
}