From 4ab68398032c7ea07933709bb3eaca15cfb1a8ec Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sat, 7 Nov 2020 12:53:50 +0100 Subject: [PATCH] Add button form field and is not clicked condition * Add button form field Close #3693 * Add public method to submit `Ui/Dialog` * Support additional submit buttons in `Form/Builder/Dialog` * Support `ButtonFormField` in AJAX forms * Fix identifier of data processor in `ButtonFormField` * Fix data processor for `ButtonFormField` * Add condition for form builder buttons not being clicked * Simplify button form field-related TypeScript code Co-authored-by: Alexander Ebert * Add missing semicolon * Unify condition to checked if form field button has been clicked Co-authored-by: Alexander Ebert --- .../templates/__buttonFormField.tpl | 6 ++ .../__isNotClickedFormFieldDependency.tpl | 7 ++ syncTemplates.json | 2 + .../files/acp/templates/__buttonFormField.tpl | 6 ++ .../__isNotClickedFormFieldDependency.tpl | 7 ++ .../WoltLabSuite/Core/Form/Builder/Dialog.js | 14 +++ .../Core/Form/Builder/Field/Button.js | 32 ++++++ .../Builder/Field/Dependency/IsNotClicked.js | 33 ++++++ .../files/js/WoltLabSuite/Core/Ui/Dialog.js | 8 +- .../builder/field/ButtonFormField.class.php | 100 ++++++++++++++++++ .../IsNotClickedFormFieldDependency.class.php | 50 +++++++++ .../WoltLabSuite/Core/Form/Builder/Dialog.js | 16 +++ .../Core/Form/Builder/Field/Button.js | 35 ++++++ .../Builder/Field/Dependency/IsNotClicked.js | 36 +++++++ .../files/ts/WoltLabSuite/Core/Ui/Dialog.ts | 9 +- 15 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 com.woltlab.wcf/templates/__buttonFormField.tpl create mode 100644 com.woltlab.wcf/templates/__isNotClickedFormFieldDependency.tpl create mode 100644 wcfsetup/install/files/acp/templates/__buttonFormField.tpl create mode 100644 wcfsetup/install/files/acp/templates/__isNotClickedFormFieldDependency.tpl create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Button.js create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js create mode 100644 wcfsetup/install/files/lib/system/form/builder/field/ButtonFormField.class.php create mode 100644 wcfsetup/install/files/lib/system/form/builder/field/dependency/IsNotClickedFormFieldDependency.class.php create mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Button.js create mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js diff --git a/com.woltlab.wcf/templates/__buttonFormField.tpl b/com.woltlab.wcf/templates/__buttonFormField.tpl new file mode 100644 index 0000000000..20d135eb35 --- /dev/null +++ b/com.woltlab.wcf/templates/__buttonFormField.tpl @@ -0,0 +1,6 @@ + diff --git a/com.woltlab.wcf/templates/__isNotClickedFormFieldDependency.tpl b/com.woltlab.wcf/templates/__isNotClickedFormFieldDependency.tpl new file mode 100644 index 0000000000..ea18b0bcd1 --- /dev/null +++ b/com.woltlab.wcf/templates/__isNotClickedFormFieldDependency.tpl @@ -0,0 +1,7 @@ +require(['WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked'], function(IsNotClickedFieldDependency) { + // dependency '{@$dependency->getId()}' + new IsNotClickedFieldDependency( + '{@$dependency->getDependentNode()->getPrefixedId()}Container', + '{@$dependency->getField()->getPrefixedId()}' + ); +}); diff --git a/syncTemplates.json b/syncTemplates.json index 267d8a9a65..f2232326c4 100644 --- a/syncTemplates.json +++ b/syncTemplates.json @@ -6,6 +6,7 @@ "templates": [ "__aclFormField", "__booleanFormField", + "__buttonFormField", "__contentLanguageFormField", "__dateFormField", "__emailFormField", @@ -20,6 +21,7 @@ "__formFieldError", "__formFieldErrors", "__iconFormField", + "__isNotClickedFormFieldDependency", "__itemListFormField", "__labelFormField", "__mediaSetCategoryDialog", diff --git a/wcfsetup/install/files/acp/templates/__buttonFormField.tpl b/wcfsetup/install/files/acp/templates/__buttonFormField.tpl new file mode 100644 index 0000000000..20d135eb35 --- /dev/null +++ b/wcfsetup/install/files/acp/templates/__buttonFormField.tpl @@ -0,0 +1,6 @@ + diff --git a/wcfsetup/install/files/acp/templates/__isNotClickedFormFieldDependency.tpl b/wcfsetup/install/files/acp/templates/__isNotClickedFormFieldDependency.tpl new file mode 100644 index 0000000000..ea18b0bcd1 --- /dev/null +++ b/wcfsetup/install/files/acp/templates/__isNotClickedFormFieldDependency.tpl @@ -0,0 +1,7 @@ +require(['WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked'], function(IsNotClickedFieldDependency) { + // dependency '{@$dependency->getId()}' + new IsNotClickedFieldDependency( + '{@$dependency->getDependentNode()->getPrefixedId()}Container', + '{@$dependency->getField()->getPrefixedId()}' + ); +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Dialog.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Dialog.js index e9f59e8e6d..3f6efe365f 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Dialog.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Dialog.js @@ -151,6 +151,19 @@ define(['Ajax', 'Core', './Manager', 'Ui/Dialog'], function (Ajax, Core, FormBui cancelButton.addEventListener('click', this._closeDialog.bind(this)); elData(cancelButton, 'has-event-listener', 1); } + this._additionalSubmitButtons = document.querySelectorAll(':not(.formSubmit) button[type="submit"]', dialogData.content); + this._additionalSubmitButtons.forEach((submit) => { + submit.addEventListener('click', (ev) => { + // 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. @@ -164,6 +177,7 @@ define(['Ajax', 'Core', './Manager', 'Ui/Dialog'], function (Ajax, Core, FormBui } 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: { diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Button.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Button.js new file mode 100644 index 0000000000..137aa55518 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Button.js @@ -0,0 +1,32 @@ +/** + * Data handler for a button 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/Value + * @since 5.4 + */ +define(['Core', './Field'], function (Core, FormBuilderField) { + "use strict"; + /** + * @constructor + */ + function FormBuilderFieldButton(fieldId) { + this.init(fieldId); + } + ; + Core.inherit(FormBuilderFieldButton, FormBuilderField, { + /** + * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData + */ + _getData: function () { + var data = {}; + if (this._field.dataset.isClicked === "1") { + data[this._fieldId] = this._field.value; + } + return data; + } + }); + return FormBuilderFieldButton; +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js new file mode 100644 index 0000000000..6d8c273fce --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js @@ -0,0 +1,33 @@ +/** + * Form field dependency implementation that requires that a button has not been clicked. + * + * @author Matthias Schmidt + * @copyright 2001-2020 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked + * @see module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract + * @since 5.4 + */ +define(['./Abstract', 'Core', './Manager'], function (Abstract, Core, DependencyManager) { + "use strict"; + /** + * @constructor + */ + function IsNotClicked(dependentElementId, fieldId) { + this.init(dependentElementId, fieldId); + this._field.addEventListener('click', () => { + this._field.dataset.isClicked = 1; + DependencyManager.checkDependencies(); + }); + } + ; + Core.inherit(IsNotClicked, Abstract, { + /** + * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract#checkDependency + */ + checkDependency: function () { + return this._field.dataset.isClicked !== "1"; + } + }); + return IsNotClicked; +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Dialog.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Dialog.js index 5d881c2d04..67deaeb8e7 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Dialog.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Dialog.js @@ -598,7 +598,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "./S } }, /** - * Submits the dialog. + * Submits the dialog with the given id. */ _submit(id) { const data = _dialogs.get(id); @@ -621,6 +621,12 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "./S } } }, + /** + * Submits the dialog with the given id. + */ + submit(id) { + this._submit(id); + }, /** * Handles clicks on the close button or the backdrop if enabled. */ diff --git a/wcfsetup/install/files/lib/system/form/builder/field/ButtonFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/ButtonFormField.class.php new file mode 100644 index 0000000000..7de61e8061 --- /dev/null +++ b/wcfsetup/install/files/lib/system/form/builder/field/ButtonFormField.class.php @@ -0,0 +1,100 @@ + + * @package WoltLabSuite\Core\System\Form\Builder\Field + * @since 5.4 + */ +class ButtonFormField extends AbstractFormField { + /** + * text shown on the button + * @var ?string + */ + protected $buttonLabel; + + /** + * @inheritDoc + */ + protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Button'; + + /** + * @inheritDoc + */ + protected $templateName = '__buttonFormField'; + + /** + * Sets the text shown on the button and returns this form field. + */ + public function buttonLabel(string $languageItem, array $variables = []): self { + $this->buttonLabel = WCF::getLanguage()->getDynamicVariable($languageItem, $variables); + + return $this; + } + + /** + * Returns the text shown on the button. + */ + public function getButtonLabel(): string { + if ($this->buttonLabel === null) { + throw new \BadMethodCallException("Button label has not been set."); + } + + return $this->buttonLabel; + } + + /** + * @inheritDoc + */ + public function getHtml() { + if ($this->buttonLabel === null) { + throw new \UnexpectedValueException("Form field '{$this->getPrefixedId()}' requires a button label."); + } + + return parent::getHtml(); + } + + /** + * @inheritDoc + */ + public function hasSaveValue() { + return false; + } + + /** + * @inheritDoc + */ + public function populate() { + parent::populate(); + + $this->getDocument()->getDataHandler()->addProcessor(new CustomFormDataProcessor('button', function(IFormDocument $document, array $parameters) { + if (!isset($parameters[$this->getObjectProperty()]) && $this->getValue() !== null) { + $parameters[$this->getObjectProperty()] = $this->getValue(); + } + + return $parameters; + })); + + return $this; + } + + /** + * @inheritDoc + */ + public function readValue() { + // The value of the button is set when setting up the form and has to be unset + // if the button was not clicked. + if (!$this->getDocument()->hasRequestData($this->getPrefixedId())) { + $this->value = null; + } + + return $this; + } +} diff --git a/wcfsetup/install/files/lib/system/form/builder/field/dependency/IsNotClickedFormFieldDependency.class.php b/wcfsetup/install/files/lib/system/form/builder/field/dependency/IsNotClickedFormFieldDependency.class.php new file mode 100644 index 0000000000..8c2607b502 --- /dev/null +++ b/wcfsetup/install/files/lib/system/form/builder/field/dependency/IsNotClickedFormFieldDependency.class.php @@ -0,0 +1,50 @@ + + * @package WoltLabSuite\Core\System\Form\Builder\Field\Dependency + * @since 5.4 + */ +class IsNotClickedFormFieldDependency extends AbstractFormFieldDependency { + /** + * @inheritDoc + */ + protected $templateName = '__isNotClickedFormFieldDependency'; + + /** + * @inheritDoc + */ + public function checkDependency() { + $form = $this->getField()->getDocument(); + + // If no request data is given, the button was not clicked. + if (!$form->hasRequestData($this->getField()->getPrefixedId())) { + return true; + } + + // Otherwise, the button is clicked if the relevant request data entry contains + // the button's value. + return $form->getRequestData($this->getField()->getPrefixedId()) !== $this->getField()->getValue(); + } + + /** + * @inheritDoc + */ + public function field(IFormField $field) { + if (!($field instanceof ButtonFormField)) { + throw new InvalidObjectArgument($field, ButtonFormField::class, 'Field'); + } + + return parent::field($field); + } +} diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Dialog.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Dialog.js index 7cebbfd69c..7aa7b5293e 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Dialog.js +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Dialog.js @@ -172,6 +172,21 @@ define(['Ajax', 'Core', './Manager', 'Ui/Dialog'], function(Ajax, Core, FormBuil cancelButton.addEventListener('click', this._closeDialog.bind(this)); elData(cancelButton, 'has-event-listener', 1); } + + this._additionalSubmitButtons = document.querySelectorAll(':not(.formSubmit) button[type="submit"]', dialogData.content); + this._additionalSubmitButtons.forEach((submit) => { + submit.addEventListener('click', (ev) => { + // 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); + }); + }); }, /** @@ -187,6 +202,7 @@ define(['Ajax', 'Core', './Manager', 'Ui/Dialog'], function(Ajax, Core, FormBuil } else if (typeof this._options.submitActionName === 'string') { submitButton.disabled = true; + this._additionalSubmitButtons.forEach((submit) => submit.disabled = true); Ajax.api(this, { actionName: this._options.submitActionName, diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Button.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Button.js new file mode 100644 index 0000000000..eef0575b71 --- /dev/null +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Button.js @@ -0,0 +1,35 @@ +/** + * Data handler for a button 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/Value + * @since 5.4 + */ +define(['Core', './Field'], function(Core, FormBuilderField) { + "use strict"; + + /** + * @constructor + */ + function FormBuilderFieldButton(fieldId) { + this.init(fieldId); + }; + Core.inherit(FormBuilderFieldButton, FormBuilderField, { + /** + * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData + */ + _getData: function() { + var data = {}; + + if (this._field.dataset.isClicked === "1") { + data[this._fieldId] = this._field.value; + } + + return data; + } + }); + + return FormBuilderFieldButton; +}); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js new file mode 100644 index 0000000000..f2b53f34a8 --- /dev/null +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js @@ -0,0 +1,36 @@ +/** + * Form field dependency implementation that requires that a button has not been clicked. + * + * @author Matthias Schmidt + * @copyright 2001-2020 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked + * @see module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract + * @since 5.4 + */ +define(['./Abstract', 'Core', './Manager'], function(Abstract, Core, DependencyManager) { + "use strict"; + + /** + * @constructor + */ + function IsNotClicked(dependentElementId, fieldId) { + this.init(dependentElementId, fieldId); + + this._field.addEventListener('click', () => { + this._field.dataset.isClicked = 1; + + DependencyManager.checkDependencies(); + }); + }; + Core.inherit(IsNotClicked, Abstract, { + /** + * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract#checkDependency + */ + checkDependency: function() { + return this._field.dataset.isClicked !== "1"; + } + }); + + return IsNotClicked; +}); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Dialog.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Dialog.ts index f7bbd73031..fae067a268 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Dialog.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Dialog.ts @@ -718,7 +718,7 @@ const UiDialog = { }, /** - * Submits the dialog. + * Submits the dialog with the given id. */ _submit(id: string): void { const data = _dialogs.get(id); @@ -744,6 +744,13 @@ const UiDialog = { } }, + /** + * Submits the dialog with the given id. + */ + submit(id: string): void { + this._submit(id); + }, + /** * Handles clicks on the close button or the backdrop if enabled. */ -- 2.20.1