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' | 'checkbox' | 'hidden';
17 export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition {
19 valuesByLabel: Record<string, string>
22 export type EditorFormFields = (EditorFormFieldDefinition|EditorUiBuilderDefinition)[];
24 interface EditorFormTabDefinition {
26 contents: EditorFormFields;
29 export interface EditorFormDefinition {
31 action: (formData: FormData, context: EditorUiContext) => Promise<boolean>;
32 fields: EditorFormFields;
35 export class EditorFormField extends EditorUiElement {
36 protected definition: EditorFormFieldDefinition;
38 constructor(definition: EditorFormFieldDefinition) {
40 this.definition = definition;
43 setValue(value: string) {
44 const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement;
45 if (this.definition.type === 'checkbox') {
46 input.checked = Boolean(value);
50 input.dispatchEvent(new Event('change'));
54 return this.definition.name;
57 protected buildDOM(): HTMLElement {
58 const id = `editor-form-field-${this.definition.name}-${Date.now()}`;
59 let input: HTMLElement;
61 if (this.definition.type === 'select') {
62 const options = (this.definition as EditorSelectFormFieldDefinition).valuesByLabel
63 const labels = Object.keys(options);
64 const optionElems = labels.map(label => el('option', {value: options[label]}, [this.trans(label)]));
65 input = el('select', {id, name: this.definition.name, class: 'editor-form-field-input'}, optionElems);
66 } else if (this.definition.type === 'textarea') {
67 input = el('textarea', {id, name: this.definition.name, class: 'editor-form-field-input'});
68 } else if (this.definition.type === 'checkbox') {
69 input = el('input', {id, name: this.definition.name, type: 'checkbox', class: 'editor-form-field-input-checkbox', value: 'true'});
70 } else if (this.definition.type === 'hidden') {
71 input = el('input', {id, name: this.definition.name, type: 'hidden'});
72 return el('div', {hidden: 'true'}, [input]);
74 input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'});
77 return el('div', {class: 'editor-form-field-wrapper'}, [
78 el('label', {class: 'editor-form-field-label', for: id}, [this.trans(this.definition.label)]),
84 export class EditorForm extends EditorContainerUiElement {
85 protected definition: EditorFormDefinition;
86 protected onCancel: null|(() => void) = null;
87 protected onSuccessfulSubmit: null|(() => void) = null;
89 constructor(definition: EditorFormDefinition) {
90 let children: (EditorFormField|EditorUiElement)[] = definition.fields.map(fieldDefinition => {
91 if (isUiBuilderDefinition(fieldDefinition)) {
92 return fieldDefinition.build();
94 return new EditorFormField(fieldDefinition)
98 this.definition = definition;
101 setValues(values: Record<string, string>) {
102 for (const name of Object.keys(values)) {
103 const field = this.getFieldByName(name);
105 field.setValue(values[name]);
110 setOnCancel(callback: () => void) {
111 this.onCancel = callback;
114 setOnSuccessfulSubmit(callback: () => void) {
115 this.onSuccessfulSubmit = callback;
118 protected getFieldByName(name: string): EditorFormField|null {
120 const search = (children: EditorUiElement[]): EditorFormField|null => {
121 for (const child of children) {
122 if (child instanceof EditorFormField && child.getName() === name) {
124 } else if (child instanceof EditorContainerUiElement) {
125 const matchingChild = search(child.getChildren());
127 return matchingChild;
135 return search(this.getChildren());
138 protected buildDOM(): HTMLElement {
139 const cancelButton = el('button', {type: 'button', class: 'editor-form-action-secondary'}, [this.trans('Cancel')]);
140 const form = el('form', {}, [
141 ...this.children.map(child => child.getDOMElement()),
142 el('div', {class: 'editor-form-actions'}, [
144 el('button', {type: 'submit', class: 'editor-form-action-primary'}, [this.trans(this.definition.submitText)]),
148 form.addEventListener('submit', async (event) => {
149 event.preventDefault();
150 const formData = new FormData(form as HTMLFormElement);
151 const result = await this.definition.action(formData, this.getContext());
152 if (result && this.onSuccessfulSubmit) {
153 this.onSuccessfulSubmit();
157 cancelButton.addEventListener('click', (event) => {
167 export class EditorFormTab extends EditorContainerUiElement {
169 protected definition: EditorFormTabDefinition;
170 protected fields: EditorUiElement[];
171 protected id: string;
173 constructor(definition: EditorFormTabDefinition) {
174 const fields = definition.contents.map(fieldDef => {
175 if (isUiBuilderDefinition(fieldDef)) {
176 return fieldDef.build();
178 return new EditorFormField(fieldDef)
183 this.definition = definition;
184 this.fields = fields;
185 this.id = uniqueId();
188 public getLabel(): string {
189 return this.getContext().translate(this.definition.label);
192 public getId(): string {
196 protected buildDOM(): HTMLElement {
200 class: 'editor-form-tab-content',
202 id: `editor-tabpanel-${this.id}`,
203 'aria-labelledby': `editor-tab-${this.id}`,
205 this.fields.map(f => f.getDOMElement())
209 export class EditorFormTabs extends EditorContainerUiElement {
211 protected definitions: EditorFormTabDefinition[] = [];
212 protected tabs: EditorFormTab[] = [];
214 constructor(definitions: EditorFormTabDefinition[]) {
215 const tabs: EditorFormTab[] = definitions.map(d => new EditorFormTab(d));
218 this.definitions = definitions;
222 protected buildDOM(): HTMLElement {
223 const controls: HTMLElement[] = [];
224 const contents: HTMLElement[] = [];
226 const selectTab = (tabIndex: number) => {
227 for (let i = 0; i < controls.length; i++) {
228 controls[i].setAttribute('aria-selected', (i === tabIndex) ? 'true' : 'false');
230 for (let i = 0; i < contents.length; i++) {
231 contents[i].hidden = !(i === tabIndex);
235 for (const tab of this.tabs) {
236 const button = el('button', {
237 class: 'editor-form-tab-control',
240 id: `editor-tab-${tab.getId()}`,
241 'aria-controls': `editor-tabpanel-${tab.getId()}`
242 }, [tab.getLabel()]);
243 contents.push(tab.getDOMElement());
244 controls.push(button);
246 button.addEventListener('click', event => {
247 selectTab(controls.indexOf(button));
253 return el('div', {class: 'editor-form-tab-container'}, [
254 el('div', {class: 'editor-form-tab-controls'}, controls),
255 el('div', {class: 'editor-form-tab-contents'}, contents),