-import {EditorUiContext, EditorUiElement} from "./core";
-import {EditorContainerUiElement} from "./containers";
-import {el} from "../../helpers";
+import {
+ EditorUiContext,
+ EditorUiElement,
+ EditorContainerUiElement,
+ EditorUiBuilderDefinition,
+ isUiBuilderDefinition
+} from "./core";
+import {uniqueId} from "../../../services/util";
+import {el} from "../../utils/dom";
export interface EditorFormFieldDefinition {
label: string;
name: string;
- type: 'text' | 'select';
+ type: 'text' | 'select' | 'textarea' | 'checkbox' | 'hidden';
}
export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition {
valuesByLabel: Record<string, string>
}
+export type EditorFormFields = (EditorFormFieldDefinition|EditorUiBuilderDefinition)[];
+
+interface EditorFormTabDefinition {
+ label: string;
+ contents: EditorFormFields;
+}
+
export interface EditorFormDefinition {
submitText: string;
- cancelText: string;
- action: (formData: FormData, context: EditorUiContext) => boolean;
- cancel: () => void;
- fields: EditorFormFieldDefinition[];
+ action: (formData: FormData, context: EditorUiContext) => Promise<boolean>;
+ fields: EditorFormFields;
}
export class EditorFormField extends EditorUiElement {
this.definition = definition;
}
+ setValue(value: string) {
+ const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement;
+ if (this.definition.type === 'checkbox') {
+ input.checked = Boolean(value);
+ } else {
+ input.value = value;
+ }
+ input.dispatchEvent(new Event('change'));
+ }
+
+ getName(): string {
+ return this.definition.name;
+ }
+
protected buildDOM(): HTMLElement {
const id = `editor-form-field-${this.definition.name}-${Date.now()}`;
let input: HTMLElement;
if (this.definition.type === 'select') {
const options = (this.definition as EditorSelectFormFieldDefinition).valuesByLabel
const labels = Object.keys(options);
- const optionElems = labels.map(label => el('option', {value: options[label]}, [label]));
+ const optionElems = labels.map(label => el('option', {value: options[label]}, [this.trans(label)]));
input = el('select', {id, name: this.definition.name, class: 'editor-form-field-input'}, optionElems);
+ } else if (this.definition.type === 'textarea') {
+ input = el('textarea', {id, name: this.definition.name, class: 'editor-form-field-input'});
+ } else if (this.definition.type === 'checkbox') {
+ input = el('input', {id, name: this.definition.name, type: 'checkbox', class: 'editor-form-field-input-checkbox', value: 'true'});
+ } else if (this.definition.type === 'hidden') {
+ input = el('input', {id, name: this.definition.name, type: 'hidden'});
+ return el('div', {hidden: 'true'}, [input]);
} else {
input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'});
}
export class EditorForm extends EditorContainerUiElement {
protected definition: EditorFormDefinition;
+ protected onCancel: null|(() => void) = null;
+ protected onSuccessfulSubmit: null|(() => void) = null;
constructor(definition: EditorFormDefinition) {
- super(definition.fields.map(fieldDefinition => new EditorFormField(fieldDefinition)));
+ let children: (EditorFormField|EditorUiElement)[] = definition.fields.map(fieldDefinition => {
+ if (isUiBuilderDefinition(fieldDefinition)) {
+ return fieldDefinition.build();
+ }
+ return new EditorFormField(fieldDefinition)
+ });
+
+ super(children);
this.definition = definition;
}
+ setValues(values: Record<string, string>) {
+ for (const name of Object.keys(values)) {
+ const field = this.getFieldByName(name);
+ if (field) {
+ field.setValue(values[name]);
+ }
+ }
+ }
+
+ setOnCancel(callback: () => void) {
+ this.onCancel = callback;
+ }
+
+ setOnSuccessfulSubmit(callback: () => void) {
+ this.onSuccessfulSubmit = callback;
+ }
+
+ protected getFieldByName(name: string): EditorFormField|null {
+
+ const search = (children: EditorUiElement[]): EditorFormField|null => {
+ for (const child of children) {
+ if (child instanceof EditorFormField && child.getName() === name) {
+ return child;
+ } else if (child instanceof EditorContainerUiElement) {
+ const matchingChild = search(child.getChildren());
+ if (matchingChild) {
+ return matchingChild;
+ }
+ }
+ }
+
+ return null;
+ };
+
+ return search(this.getChildren());
+ }
+
protected buildDOM(): HTMLElement {
- const cancelButton = el('button', {type: 'button', class: 'editor-form-action-secondary'}, [this.trans(this.definition.cancelText)]);
+ const cancelButton = el('button', {type: 'button', class: 'editor-form-action-secondary'}, [this.trans('Cancel')]);
const form = el('form', {}, [
...this.children.map(child => child.getDOMElement()),
el('div', {class: 'editor-form-actions'}, [
])
]);
- form.addEventListener('submit', (event) => {
+ form.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(form as HTMLFormElement);
- this.definition.action(formData, this.getContext());
+ const result = await this.definition.action(formData, this.getContext());
+ if (result && this.onSuccessfulSubmit) {
+ this.onSuccessfulSubmit();
+ }
});
cancelButton.addEventListener('click', (event) => {
- this.definition.cancel();
+ if (this.onCancel) {
+ this.onCancel();
+ }
});
return form;
}
+}
+
+export class EditorFormTab extends EditorContainerUiElement {
+
+ protected definition: EditorFormTabDefinition;
+ protected fields: EditorUiElement[];
+ protected id: string;
+
+ constructor(definition: EditorFormTabDefinition) {
+ const fields = definition.contents.map(fieldDef => {
+ if (isUiBuilderDefinition(fieldDef)) {
+ return fieldDef.build();
+ }
+ return new EditorFormField(fieldDef)
+ });
+
+ super(fields);
+
+ this.definition = definition;
+ this.fields = fields;
+ this.id = uniqueId();
+ }
+
+ public getLabel(): string {
+ return this.getContext().translate(this.definition.label);
+ }
+
+ public getId(): string {
+ return this.id;
+ }
+
+ protected buildDOM(): HTMLElement {
+ return el(
+ 'div',
+ {
+ class: 'editor-form-tab-content',
+ role: 'tabpanel',
+ id: `editor-tabpanel-${this.id}`,
+ 'aria-labelledby': `editor-tab-${this.id}`,
+ },
+ this.fields.map(f => f.getDOMElement())
+ );
+ }
+}
+export class EditorFormTabs extends EditorContainerUiElement {
+
+ protected definitions: EditorFormTabDefinition[] = [];
+ protected tabs: EditorFormTab[] = [];
+
+ constructor(definitions: EditorFormTabDefinition[]) {
+ const tabs: EditorFormTab[] = definitions.map(d => new EditorFormTab(d));
+ super(tabs);
+
+ this.definitions = definitions;
+ this.tabs = tabs;
+ }
+
+ protected buildDOM(): HTMLElement {
+ const controls: HTMLElement[] = [];
+ const contents: HTMLElement[] = [];
+
+ const selectTab = (tabIndex: number) => {
+ for (let i = 0; i < controls.length; i++) {
+ controls[i].setAttribute('aria-selected', (i === tabIndex) ? 'true' : 'false');
+ }
+ for (let i = 0; i < contents.length; i++) {
+ contents[i].hidden = !(i === tabIndex);
+ }
+ };
+
+ for (const tab of this.tabs) {
+ const button = el('button', {
+ class: 'editor-form-tab-control',
+ type: 'button',
+ role: 'tab',
+ id: `editor-tab-${tab.getId()}`,
+ 'aria-controls': `editor-tabpanel-${tab.getId()}`
+ }, [tab.getLabel()]);
+ contents.push(tab.getDOMElement());
+ controls.push(button);
+
+ button.addEventListener('click', event => {
+ selectTab(controls.indexOf(button));
+ });
+ }
+
+ selectTab(0);
+
+ return el('div', {class: 'editor-form-tab-container'}, [
+ el('div', {class: 'editor-form-tab-controls'}, controls),
+ el('div', {class: 'editor-form-tab-contents'}, contents),
+ ]);
+ }
}
\ No newline at end of file