From: Tim Düsterhus Date: Fri, 15 Jan 2021 14:04:19 +0000 (+0100) Subject: Merge branch 'master' into typescript-tree X-Git-Tag: 5.4.0_Alpha_1~442^2 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=93d36794ece966333ecbc04e6eff3cc516550525;p=GitHub%2FWoltLab%2FWCF.git Merge branch 'master' into typescript-tree --- 93d36794ece966333ecbc04e6eff3cc516550525 diff --cc ts/WoltLabSuite/Core/Form/Builder/Dialog.ts index f37e78790d,0000000000..17b88324b4 mode 100644,000000..100644 --- a/ts/WoltLabSuite/Core/Form/Builder/Dialog.ts +++ b/ts/WoltLabSuite/Core/Form/Builder/Dialog.ts @@@ -1,240 -1,0 +1,245 @@@ +/** + * Provides API to create a dialog form created by form builder. + * + * @author Matthias Schmidt + * @copyright 2001-2020 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Form/Builder/Dialog + * @since 5.2 + */ + +import * as Core from "../../Core"; +import UiDialog from "../../Ui/Dialog"; +import { DialogCallbackObject, DialogCallbackSetup, DialogData } from "../../Ui/Dialog/Data"; +import * as Ajax from "../../Ajax"; +import { AjaxCallbackObject, AjaxCallbackSetup, DatabaseObjectActionResponse, RequestOptions } from "../../Ajax/Data"; +import * as FormBuilderManager from "./Manager"; +import { AjaxResponseReturnValues, FormBuilderData, FormBuilderDialogOptions } from "./Data"; + +interface AjaxResponse extends DatabaseObjectActionResponse { + returnValues: AjaxResponseReturnValues; +} + +class FormBuilderDialog implements AjaxCallbackObject, DialogCallbackObject { + protected _actionName: string; + protected _className: string; + protected _dialogContent: string; + protected _dialogId: string; + protected _formId: string; + protected _options: FormBuilderDialogOptions; + protected _additionalSubmitButtons: HTMLButtonElement[]; + - constructor(dialogId: string, className: string, actionName: string, options: FormBuilderDialogOptions) { ++ constructor(dialogId: string, className: string, actionName: string, options: Partial) { + this.init(dialogId, className, actionName, options); + } + - protected init(dialogId: string, className: string, actionName: string, options: FormBuilderDialogOptions): void { ++ protected init( ++ dialogId: string, ++ className: string, ++ actionName: string, ++ options: Partial, ++ ): void { + this._dialogId = dialogId; + this._className = className; + this._actionName = actionName; + this._options = Core.extend( + { + actionParameters: {}, + destroyOnClose: false, + usesDboAction: /\w+\\data\\/.test(this._className), + }, + options, + ) as FormBuilderDialogOptions; + this._options.dialog = Core.extend(this._options.dialog || {}, { + onClose: () => this._dialogOnClose(), + }); + + this._formId = ""; + this._dialogContent = ""; + } + + _ajaxSetup(): ReturnType { + const options = { + data: { + actionName: this._actionName, + className: this._className, + parameters: this._options.actionParameters, + }, + } as RequestOptions; + + // By default, `AJAXProxyAction` is used which relies on an `IDatabaseObjectAction` object; if + // no such object is used but an `IAJAXInvokeAction` object, `AJAXInvokeAction` has to be used. + if (!this._options.usesDboAction) { + options.url = "index.php?ajax-invoke/&t=" + window.SECURITY_TOKEN; + options.withCredentials = true; + } + + return options; + } + + _ajaxSuccess(data: AjaxResponse): void { + switch (data.actionName) { + case this._actionName: + if (data.returnValues === undefined) { + throw new Error("Missing return data."); + } else if (data.returnValues.dialog === undefined) { + throw new Error("Missing dialog template in return data."); + } else if (data.returnValues.formId === undefined) { + throw new Error("Missing form id in return data."); + } + + this._openDialogContent(data.returnValues.formId, data.returnValues.dialog); + + break; + + case this._options.submitActionName: + // If the validation failed, the dialog is shown again. + if (data.returnValues && data.returnValues.formId && data.returnValues.dialog) { + if (data.returnValues.formId !== this._formId) { + throw new Error( + "Mismatch between form ids: expected '" + this._formId + "' but got '" + data.returnValues.formId + "'.", + ); + } + + this._openDialogContent(data.returnValues.formId, data.returnValues.dialog); + } else { + this.destroy(); + + if (typeof this._options.successCallback === "function") { + this._options.successCallback(data.returnValues || {}); + } + } + + break; + + default: + throw new Error("Cannot handle action '" + data.actionName + "'."); + } + } + + protected _closeDialog(): void { + UiDialog.close(this); + + if (typeof this._options.closeCallback === "function") { + this._options.closeCallback(); + } + } + + protected _dialogOnClose(): void { + if (this._options.destroyOnClose) { + this.destroy(); + } + } + + _dialogSetup(): ReturnType { + return { + id: this._dialogId, + options: this._options.dialog, + source: this._dialogContent, + }; + } + + _dialogSubmit(): void { + void this.getData().then((formData: FormBuilderData) => this._submitForm(formData)); + } + + /** + * Opens the form dialog with the given form content. + */ + protected _openDialogContent(formId: string, dialogContent: string): void { + this.destroy(true); + + this._formId = formId; + this._dialogContent = dialogContent; + + const dialogData = UiDialog.open(this, this._dialogContent) as DialogData; + + const cancelButton = dialogData.content.querySelector("button[data-type=cancel]") as HTMLButtonElement; + if (cancelButton !== null && !Core.stringToBool(cancelButton.dataset.hasEventListener || "")) { + cancelButton.addEventListener("click", () => this._closeDialog()); + cancelButton.dataset.hasEventListener = "1"; + } + + this._additionalSubmitButtons = Array.from( + dialogData.content.querySelectorAll(':not(.formSubmit) button[type="submit"]'), + ); + this._additionalSubmitButtons.forEach((submit) => { + submit.addEventListener("click", () => { + // Mark the button that was clicked so that the button data handlers know + // which data needs to be submitted. + this._additionalSubmitButtons.forEach((button) => { + button.dataset.isClicked = button === submit ? "1" : "0"; + }); + + // Enable other `click` event listeners to be executed first before the form + // is submitted. + setTimeout(() => UiDialog.submit(this._dialogId), 0); + }); + }); + } + + /** + * Submits the form with the given form data. + */ + protected _submitForm(formData: FormBuilderData): void { + const dialogData = UiDialog.getDialog(this)!; + + const submitButton = dialogData.content.querySelector("button[data-type=submit]") as HTMLButtonElement; + + if (typeof this._options.onSubmit === "function") { + this._options.onSubmit(formData, submitButton); + } else if (typeof this._options.submitActionName === "string") { + submitButton.disabled = true; + this._additionalSubmitButtons.forEach((submit) => (submit.disabled = true)); + + Ajax.api(this, { + actionName: this._options.submitActionName, + parameters: { + data: formData, + formId: this._formId, + }, + }); + } + } + + /** + * Destroys the dialog form. + */ + public destroy(ignoreDialog = false): void { + if (this._formId !== "") { + if (FormBuilderManager.hasForm(this._formId)) { + FormBuilderManager.unregisterForm(this._formId); + } + + if (ignoreDialog !== true) { + UiDialog.destroy(this); + } + } + } + + /** + * Returns a promise that provides all of the dialog form's data. + */ + public getData(): Promise { + if (this._formId === "") { + throw new Error("Form has not been requested yet."); + } + + return FormBuilderManager.getData(this._formId); + } + + /** + * Opens the dialog form. + */ + public open(): void { + if (UiDialog.getDialog(this._dialogId)) { + UiDialog.open(this); + } else { + Ajax.api(this); + } + } +} + +Core.enableLegacyInheritance(FormBuilderDialog); + +export = FormBuilderDialog; diff --cc ts/WoltLabSuite/Core/Form/Builder/Field/User.ts index 70f419a978,0000000000..03cb752b19 mode 100644,000000..100644 --- a/ts/WoltLabSuite/Core/Form/Builder/Field/User.ts +++ b/ts/WoltLabSuite/Core/Form/Builder/Field/User.ts @@@ -1,36 -1,0 +1,36 @@@ +/** + * Data handler for a user form builder field in an Ajax form. + * + * @author Matthias Schmidt + * @copyright 2001-2020 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Form/Builder/Field/User + * @since 5.2 + */ + +import Field from "./Field"; +import { FormBuilderData } from "../Data"; +import * as Core from "../../../Core"; - import * as UiItemList from "../../../Ui/ItemList/Static"; ++import * as UiItemList from "../../../Ui/ItemList"; + +class User extends Field { + protected _getData(): FormBuilderData { + const usernames = UiItemList.getValues(this._fieldId) + .map((item) => { + if (item.objectId) { + return item.value; + } + + return null; + }) + .filter((v) => v !== null) as string[]; + + return { + [this._fieldId]: usernames.join(","), + }; + } +} + +Core.enableLegacyInheritance(User); + +export = User;