Add button form field and is not clicked condition
authorMatthias Schmidt <gravatronics@live.com>
Sat, 7 Nov 2020 11:53:50 +0000 (12:53 +0100)
committerGitHub <noreply@github.com>
Sat, 7 Nov 2020 11:53:50 +0000 (12:53 +0100)
* 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 <ebert@woltlab.com>
* Add missing semicolon

* Unify condition to checked if form field button has been clicked

Co-authored-by: Alexander Ebert <ebert@woltlab.com>
15 files changed:
com.woltlab.wcf/templates/__buttonFormField.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/__isNotClickedFormFieldDependency.tpl [new file with mode: 0644]
syncTemplates.json
wcfsetup/install/files/acp/templates/__buttonFormField.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/__isNotClickedFormFieldDependency.tpl [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Dialog.js
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Button.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Dialog.js
wcfsetup/install/files/lib/system/form/builder/field/ButtonFormField.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/form/builder/field/dependency/IsNotClickedFormFieldDependency.class.php [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Dialog.js
wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Button.js [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/IsNotClicked.js [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Dialog.ts

diff --git a/com.woltlab.wcf/templates/__buttonFormField.tpl b/com.woltlab.wcf/templates/__buttonFormField.tpl
new file mode 100644 (file)
index 0000000..20d135e
--- /dev/null
@@ -0,0 +1,6 @@
+<button {*
+       *}type="submit" {*
+       *}id="{@$field->getPrefixedId()}" {*
+       *}name="{@$field->getPrefixedId()}" {*
+       *}value="{$field->getValue()}"{*
+*}>{$field->getButtonLabel()}</button>
diff --git a/com.woltlab.wcf/templates/__isNotClickedFormFieldDependency.tpl b/com.woltlab.wcf/templates/__isNotClickedFormFieldDependency.tpl
new file mode 100644 (file)
index 0000000..ea18b0b
--- /dev/null
@@ -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()}'
+       );
+});
index 267d8a9a6555b5280c73f5787f7e71f8debd9347..f2232326c43b23ebd62a78fdc676257c4116fdf7 100644 (file)
@@ -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 (file)
index 0000000..20d135e
--- /dev/null
@@ -0,0 +1,6 @@
+<button {*
+       *}type="submit" {*
+       *}id="{@$field->getPrefixedId()}" {*
+       *}name="{@$field->getPrefixedId()}" {*
+       *}value="{$field->getValue()}"{*
+*}>{$field->getButtonLabel()}</button>
diff --git a/wcfsetup/install/files/acp/templates/__isNotClickedFormFieldDependency.tpl b/wcfsetup/install/files/acp/templates/__isNotClickedFormFieldDependency.tpl
new file mode 100644 (file)
index 0000000..ea18b0b
--- /dev/null
@@ -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()}'
+       );
+});
index e9f59e8e6d72782beca8dc61b3d9a23614b8dda4..3f6efe365f62524908a13ef2ea8bb33dc53ccde2 100644 (file)
@@ -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 (file)
index 0000000..137aa55
--- /dev/null
@@ -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 <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..6d8c273
--- /dev/null
@@ -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 <http://opensource.org/licenses/lgpl-license.php>
+ * @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;
+});
index 5d881c2d04db7b7563b56884f109744871106a1a..67deaeb8e7f061f8280153edd25392a808b579ae 100644 (file)
@@ -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 (file)
index 0000000..7de61e8
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+namespace wcf\system\form\builder\field;
+use wcf\system\form\builder\data\processor\CustomFormDataProcessor;
+use wcf\system\form\builder\IFormDocument;
+use wcf\system\WCF;
+
+/**
+ * Implementation of a form field for submit buttons.
+ * 
+ * @author      Matthias Schmidt
+ * @copyright   2001-2020 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..8c2607b
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace wcf\system\form\builder\field\dependency;
+use wcf\system\exception\InvalidObjectArgument;
+use wcf\system\form\builder\field\ButtonFormField;
+use wcf\system\form\builder\field\IFormField;
+
+/**
+ * Represents a dependency that requires that a button has not been clicked.
+ * 
+ * This dependency only works for `ButtonFormField` fields.
+ * 
+ * @author      Matthias Schmidt
+ * @copyright   2001-2020 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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);
+       }
+}
index 7cebbfd69c1e3fa1002c0e7aed0e69cd34bfd77f..7aa7b5293eb07a6518d78d6a3e192e1e162b0459 100644 (file)
@@ -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 (file)
index 0000000..eef0575
--- /dev/null
@@ -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 <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..f2b53f3
--- /dev/null
@@ -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 <http://opensource.org/licenses/lgpl-license.php>
+ * @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;
+});
index f7bbd7303156021cf697007482154cdda03e5134..fae067a26885a56728c2fd5f283d4514782d4e81 100644 (file)
@@ -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.
    */