X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/c8f6b7e0d655562aa143ad7c3e82c560b376e74b..d13abc7e1d9c204640fe9a6624a162a7cc106de6:/resources/js/wysiwyg/ui/framework/forms.ts diff --git a/resources/js/wysiwyg/ui/framework/forms.ts b/resources/js/wysiwyg/ui/framework/forms.ts index b641f993b..b12d9f692 100644 --- a/resources/js/wysiwyg/ui/framework/forms.ts +++ b/resources/js/wysiwyg/ui/framework/forms.ts @@ -1,10 +1,17 @@ -import {EditorUiContext, EditorUiElement, EditorContainerUiElement} from "./core"; -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' | 'textarea'; + type: 'text' | 'select' | 'textarea' | 'checkbox' | 'hidden'; } export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition { @@ -12,10 +19,17 @@ export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefiniti valuesByLabel: Record } +export type EditorFormFields = (EditorFormFieldDefinition|EditorUiBuilderDefinition)[]; + +interface EditorFormTabDefinition { + label: string; + contents: EditorFormFields; +} + export interface EditorFormDefinition { submitText: string; action: (formData: FormData, context: EditorUiContext) => Promise; - fields: EditorFormFieldDefinition[]; + fields: EditorFormFields; } export class EditorFormField extends EditorUiElement { @@ -28,7 +42,12 @@ export class EditorFormField extends EditorUiElement { setValue(value: string) { const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement; - input.value = value; + if (this.definition.type === 'checkbox') { + input.checked = Boolean(value); + } else { + input.value = value; + } + input.dispatchEvent(new Event('change')); } getName(): string { @@ -42,10 +61,15 @@ export class EditorFormField extends EditorUiElement { 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'}); } @@ -60,9 +84,17 @@ export class EditorFormField extends EditorUiElement { 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; } @@ -79,14 +111,28 @@ export class EditorForm extends EditorContainerUiElement { this.onCancel = callback; } + setOnSuccessfulSubmit(callback: () => void) { + this.onSuccessfulSubmit = callback; + } + protected getFieldByName(name: string): EditorFormField|null { - for (const child of this.children as EditorFormField[]) { - if (child.getName() === name) { - return child; + + 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 null; + }; + + return search(this.getChildren()); } protected buildDOM(): HTMLElement { @@ -99,10 +145,13 @@ export class EditorForm extends EditorContainerUiElement { ]) ]); - 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) => { @@ -113,4 +162,97 @@ export class EditorForm extends EditorContainerUiElement { 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