4 EditorContainerUiElement,
5 EditorUiBuilderDefinition,
8 import {uniqueId} from "../../../services/util";
9 import {el} from "../../utils/dom";
11 export interface EditorFormFieldDefinition {
14 type: 'text' | 'select' | 'textarea';
17 export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition {
19 valuesByLabel: Record<string, string>
22 interface EditorFormTabDefinition {
24 contents: EditorFormFieldDefinition[];
27 export interface EditorFormDefinition {
29 action: (formData: FormData, context: EditorUiContext) => Promise<boolean>;
30 fields: (EditorFormFieldDefinition|EditorUiBuilderDefinition)[];
33 export class EditorFormField extends EditorUiElement {
34 protected definition: EditorFormFieldDefinition;
36 constructor(definition: EditorFormFieldDefinition) {
38 this.definition = definition;
41 setValue(value: string) {
42 const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement;
47 return this.definition.name;
50 protected buildDOM(): HTMLElement {
51 const id = `editor-form-field-${this.definition.name}-${Date.now()}`;
52 let input: HTMLElement;
54 if (this.definition.type === 'select') {
55 const options = (this.definition as EditorSelectFormFieldDefinition).valuesByLabel
56 const labels = Object.keys(options);
57 const optionElems = labels.map(label => el('option', {value: options[label]}, [this.trans(label)]));
58 input = el('select', {id, name: this.definition.name, class: 'editor-form-field-input'}, optionElems);
59 } else if (this.definition.type === 'textarea') {
60 input = el('textarea', {id, name: this.definition.name, class: 'editor-form-field-input'});
62 input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'});
65 return el('div', {class: 'editor-form-field-wrapper'}, [
66 el('label', {class: 'editor-form-field-label', for: id}, [this.trans(this.definition.label)]),
72 export class EditorForm extends EditorContainerUiElement {
73 protected definition: EditorFormDefinition;
74 protected onCancel: null|(() => void) = null;
76 constructor(definition: EditorFormDefinition) {
77 let children: (EditorFormField|EditorUiElement)[] = definition.fields.map(fieldDefinition => {
78 if (isUiBuilderDefinition(fieldDefinition)) {
79 return fieldDefinition.build();
81 return new EditorFormField(fieldDefinition)
85 this.definition = definition;
88 setValues(values: Record<string, string>) {
89 for (const name of Object.keys(values)) {
90 const field = this.getFieldByName(name);
92 field.setValue(values[name]);
97 setOnCancel(callback: () => void) {
98 this.onCancel = callback;
101 protected getFieldByName(name: string): EditorFormField|null {
103 const search = (children: EditorUiElement[]): EditorFormField|null => {
104 for (const child of children) {
105 if (child instanceof EditorFormField && child.getName() === name) {
107 } else if (child instanceof EditorContainerUiElement) {
108 const matchingChild = search(child.getChildren());
110 return matchingChild;
118 return search(this.getChildren());
121 protected buildDOM(): HTMLElement {
122 const cancelButton = el('button', {type: 'button', class: 'editor-form-action-secondary'}, [this.trans('Cancel')]);
123 const form = el('form', {}, [
124 ...this.children.map(child => child.getDOMElement()),
125 el('div', {class: 'editor-form-actions'}, [
127 el('button', {type: 'submit', class: 'editor-form-action-primary'}, [this.trans(this.definition.submitText)]),
131 form.addEventListener('submit', (event) => {
132 event.preventDefault();
133 const formData = new FormData(form as HTMLFormElement);
134 this.definition.action(formData, this.getContext());
137 cancelButton.addEventListener('click', (event) => {
147 export class EditorFormTab extends EditorContainerUiElement {
149 protected definition: EditorFormTabDefinition;
150 protected fields: EditorFormField[];
151 protected id: string;
153 constructor(definition: EditorFormTabDefinition) {
154 const fields = definition.contents.map(fieldDef => new EditorFormField(fieldDef));
157 this.definition = definition;
158 this.fields = fields;
159 this.id = uniqueId();
162 public getLabel(): string {
163 return this.getContext().translate(this.definition.label);
166 public getId(): string {
170 protected buildDOM(): HTMLElement {
174 class: 'editor-form-tab-content',
176 id: `editor-tabpanel-${this.id}`,
177 'aria-labelledby': `editor-tab-${this.id}`,
179 this.fields.map(f => f.getDOMElement())
183 export class EditorFormTabs extends EditorContainerUiElement {
185 protected definitions: EditorFormTabDefinition[] = [];
186 protected tabs: EditorFormTab[] = [];
188 constructor(definitions: EditorFormTabDefinition[]) {
189 const tabs: EditorFormTab[] = definitions.map(d => new EditorFormTab(d));
192 this.definitions = definitions;
196 protected buildDOM(): HTMLElement {
197 const controls: HTMLElement[] = [];
198 const contents: HTMLElement[] = [];
200 const selectTab = (tabIndex: number) => {
201 for (let i = 0; i < controls.length; i++) {
202 controls[i].setAttribute('aria-selected', (i === tabIndex) ? 'true' : 'false');
204 for (let i = 0; i < contents.length; i++) {
205 contents[i].hidden = !(i === tabIndex);
209 for (const tab of this.tabs) {
210 const button = el('button', {
211 class: 'editor-form-tab-control',
214 id: `editor-tab-${tab.getId()}`,
215 'aria-controls': `editor-tabpanel-${tab.getId()}`
216 }, [tab.getLabel()]);
217 contents.push(tab.getDOMElement());
218 controls.push(button);
220 button.addEventListener('click', event => {
221 selectTab(controls.indexOf(button));
227 return el('div', {class: 'editor-form-tab-container'}, [
228 el('div', {class: 'editor-form-tab-controls'}, controls),
229 el('div', {class: 'editor-form-tab-contents'}, contents),