Add JavaScript data handlers for form builder forms
authorMatthias Schmidt <gravatronics@live.com>
Wed, 3 Apr 2019 17:20:33 +0000 (19:20 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Wed, 3 Apr 2019 17:20:33 +0000 (19:20 +0200)
… for easier Ajax form support.

See #2509

58 files changed:
com.woltlab.wcf/templates/__captchaFormField.tpl
com.woltlab.wcf/templates/__form.tpl
com.woltlab.wcf/templates/__formFieldDataHandler.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/__formFieldFooter.tpl
com.woltlab.wcf/templates/__labelFormField.tpl
com.woltlab.wcf/templates/__ratingFormField.tpl
com.woltlab.wcf/templates/uploadFieldComponent.tpl
wcfsetup/install/files/acp/templates/__captchaFormField.tpl
wcfsetup/install/files/acp/templates/__form.tpl
wcfsetup/install/files/acp/templates/__formFieldDataHandler.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/__formFieldFooter.tpl
wcfsetup/install/files/acp/templates/__labelFormField.tpl
wcfsetup/install/files/acp/templates/__ratingFormField.tpl
wcfsetup/install/files/acp/templates/__simpleAclFormField.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/uploadFieldComponent.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Dialog.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Captcha.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Checkboxes.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Checked.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Controller/Label.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Controller/Rating.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Date.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager.js
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Field.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/ItemList.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Label.js [deleted file]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/RadioButton.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Rating.js [deleted file]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/SimpleAcl.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Tag.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/User.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Value.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/ValueI18n.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Manager.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Language/Input.js
wcfsetup/install/files/lib/system/form/builder/DialogFormDocument.class.php
wcfsetup/install/files/lib/system/form/builder/FormDocument.class.php
wcfsetup/install/files/lib/system/form/builder/field/AbstractFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/AbstractNumericFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/BooleanFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/CaptchaFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/DateFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/IconFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/ItemListFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/MultipleSelectionFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/RadioButtonFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/RatingFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/SingleSelectionFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/TI18nFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/TextFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/UploadFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/acl/simple/SimpleAclFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/label/LabelFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/tag/TagFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/user/UserFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/user/UsernameFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/wysiwyg/WysiwygFormField.class.php
wcfsetup/install/files/lib/system/language/I18nHandler.class.php

index f4202f8f1c2579eee0adb327f2035083bf2af2df..360f272abc5b933d1ed1fea52574f45acd0d8795 100644 (file)
@@ -1 +1,3 @@
 {@$field->getObjectType()->getProcessor()->getFormElement()}
+
+{include file='__formFieldDataHandler'}
index 4a1fcadb94986954806bbdd00e867b8071ce01cf..8719c7e04b314e89bf4813d64be5fe80a9ef11bf 100644 (file)
@@ -1,7 +1,21 @@
 <script data-relocate="true">
        {* register form with dependency manager before any form field-related JavaScript code is executed below *}
-       require(['WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager'], function(FormBuilderFieldDependencyManager) {
+       require([
+               'WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager'
+               {if $form->isAjax()}
+                       , 'WoltLabSuite/Core/Form/Builder/Manager'
+               {/if}
+       ], function(
+               FormBuilderFieldDependencyManager
+               {if $form->isAjax()}
+                       , FormBuilderManager
+               {/if}
+       ) {
                FormBuilderFieldDependencyManager.register('{@$form->getId()}');
+               
+               {if $form->isAjax()}
+                       FormBuilderManager.registerForm('{@$form->getId()}');
+               {/if}
        });
 </script>
 
diff --git a/com.woltlab.wcf/templates/__formFieldDataHandler.tpl b/com.woltlab.wcf/templates/__formFieldDataHandler.tpl
new file mode 100644 (file)
index 0000000..83b9800
--- /dev/null
@@ -0,0 +1,16 @@
+{if $field->getDocument()->isAjax() && !$javaScriptDataHandlerModule|empty}
+       <script data-relocate="true">
+               require([
+                       '{$javaScriptDataHandlerModule}{if $field|is_subclass_of:'wcf\system\form\builder\field\II18nFormField' && $field->isI18n()}I18n{/if}',
+                       'WoltLabSuite/Core/Form/Builder/Manager'
+               ], function(
+                       FormBuilderField,
+                       FormBuilderManager
+               ) {
+                       FormBuilderManager.registerField(
+                               '{@$field->getDocument()->getId()}',
+                               new FormBuilderField('{@$field->getPrefixedId()}')
+                       );
+               });
+       </script>
+{/if}
index bb5c6873902cd15ecb13f9d9448adbdbc28e9bb1..f420d74d3d968317436714c79f76e75d96e4bffb 100644 (file)
@@ -1,5 +1,6 @@
                {include file='__formFieldDescription'}
                {include file='__formFieldErrors'}
                {include file='__formFieldDependencies'}
+               {include file='__formFieldDataHandler'}
        </dd>
 </dl>
index 071acec51693b69ae3b884e02f303bfec3281929..971066d4b1413b5282a7fa99d7d667f97bea5837 100644 (file)
@@ -27,7 +27,7 @@
 
 {js application='wcf' file='WCF.Label' bundle='WCF.Combined'}
 <script data-relocate="true">
-       require(['Dom/Util', 'Language', 'WoltLabSuite/Core/Form/Builder/Field/Label'], function(DomUtil, Language, FormBuilderFieldLabel) {
+       require(['Dom/Util', 'Language', 'WoltLabSuite/Core/Form/Builder/Field/Controller/Label'], function(DomUtil, Language, FormBuilderFieldLabel) {
                Language.addObject({
                        'wcf.label.none': '{lang}wcf.label.none{/lang}',
                        'wcf.label.withoutSelection': '{lang}wcf.label.withoutSelection{/lang}'
index 0dc9978b883e831d9ee7ec48b43524fc490d4333..d1d8ded8e7e6efb7120e24eb7ab0b422403fc8a5 100644 (file)
@@ -17,7 +17,7 @@
 </noscript>
 
 <script data-relocate="true">
-       require(['WoltLabSuite/Core/Form/Builder/Field/Rating'], function(FormBuilderFieldRating) {
+       require(['WoltLabSuite/Core/Form/Builder/Field/Controller/Rating'], function(FormBuilderFieldRating) {
                new FormBuilderFieldRating(
                        '{@$field->getPrefixedId()}',
                        {if $field->getValue() !== null}{@$field->getValue()}{else}''{/if},
index 90995ce7a93d07837c60332c9e8f271e54dfead4..927cf8249a176207e345a4fb50b1f4c16faa8e37 100644 (file)
@@ -30,7 +30,7 @@
 
 <div id="{$uploadFieldId}UploadButtonDiv" class="uploadButtonDiv"></div>
 
-<input type="hidden" name="{$uploadFieldId}" value="{$uploadField->getInternalId()}">
+<input type="hidden" id="{$uploadFieldId}" name="{$uploadFieldId}" value="{$uploadField->getInternalId()}">
 
 <script data-relocate="true">
        require(['WoltLabSuite/Core/Ui/File/Upload', 'Language'], function(Upload, Language) {
index f4202f8f1c2579eee0adb327f2035083bf2af2df..360f272abc5b933d1ed1fea52574f45acd0d8795 100644 (file)
@@ -1 +1,3 @@
 {@$field->getObjectType()->getProcessor()->getFormElement()}
+
+{include file='__formFieldDataHandler'}
index 4a1fcadb94986954806bbdd00e867b8071ce01cf..8719c7e04b314e89bf4813d64be5fe80a9ef11bf 100644 (file)
@@ -1,7 +1,21 @@
 <script data-relocate="true">
        {* register form with dependency manager before any form field-related JavaScript code is executed below *}
-       require(['WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager'], function(FormBuilderFieldDependencyManager) {
+       require([
+               'WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager'
+               {if $form->isAjax()}
+                       , 'WoltLabSuite/Core/Form/Builder/Manager'
+               {/if}
+       ], function(
+               FormBuilderFieldDependencyManager
+               {if $form->isAjax()}
+                       , FormBuilderManager
+               {/if}
+       ) {
                FormBuilderFieldDependencyManager.register('{@$form->getId()}');
+               
+               {if $form->isAjax()}
+                       FormBuilderManager.registerForm('{@$form->getId()}');
+               {/if}
        });
 </script>
 
diff --git a/wcfsetup/install/files/acp/templates/__formFieldDataHandler.tpl b/wcfsetup/install/files/acp/templates/__formFieldDataHandler.tpl
new file mode 100644 (file)
index 0000000..d4651bd
--- /dev/null
@@ -0,0 +1,16 @@
+{if $field->getDocument()->isAjax() && !$javaScriptDataHandlerModule|empty}
+       <script data-relocate="true">
+               require([
+                       '{$javaScriptDataHandlerModule}',
+                       'WoltLabSuite/Core/Form/Builder/Manager'
+               ], function(
+                       FormBuilderField,
+                       FormBuilderManager
+               ) {
+                       FormBuilderManager.registerField(
+                               '{@$field->getDocument()->getId()}',
+                               new FormBuilderField('{@$field->getPrefixedId()}')
+                       );
+               });
+       </script>
+{/if}
index bb5c6873902cd15ecb13f9d9448adbdbc28e9bb1..f420d74d3d968317436714c79f76e75d96e4bffb 100644 (file)
@@ -1,5 +1,6 @@
                {include file='__formFieldDescription'}
                {include file='__formFieldErrors'}
                {include file='__formFieldDependencies'}
+               {include file='__formFieldDataHandler'}
        </dd>
 </dl>
index 071acec51693b69ae3b884e02f303bfec3281929..971066d4b1413b5282a7fa99d7d667f97bea5837 100644 (file)
@@ -27,7 +27,7 @@
 
 {js application='wcf' file='WCF.Label' bundle='WCF.Combined'}
 <script data-relocate="true">
-       require(['Dom/Util', 'Language', 'WoltLabSuite/Core/Form/Builder/Field/Label'], function(DomUtil, Language, FormBuilderFieldLabel) {
+       require(['Dom/Util', 'Language', 'WoltLabSuite/Core/Form/Builder/Field/Controller/Label'], function(DomUtil, Language, FormBuilderFieldLabel) {
                Language.addObject({
                        'wcf.label.none': '{lang}wcf.label.none{/lang}',
                        'wcf.label.withoutSelection': '{lang}wcf.label.withoutSelection{/lang}'
index 0dc9978b883e831d9ee7ec48b43524fc490d4333..d1d8ded8e7e6efb7120e24eb7ab0b422403fc8a5 100644 (file)
@@ -17,7 +17,7 @@
 </noscript>
 
 <script data-relocate="true">
-       require(['WoltLabSuite/Core/Form/Builder/Field/Rating'], function(FormBuilderFieldRating) {
+       require(['WoltLabSuite/Core/Form/Builder/Field/Controller/Rating'], function(FormBuilderFieldRating) {
                new FormBuilderFieldRating(
                        '{@$field->getPrefixedId()}',
                        {if $field->getValue() !== null}{@$field->getValue()}{else}''{/if},
diff --git a/wcfsetup/install/files/acp/templates/__simpleAclFormField.tpl b/wcfsetup/install/files/acp/templates/__simpleAclFormField.tpl
new file mode 100644 (file)
index 0000000..f24fac9
--- /dev/null
@@ -0,0 +1,3 @@
+{include file='aclSimple'}
+
+{include file='__formFieldDataHandler'}
index 90995ce7a93d07837c60332c9e8f271e54dfead4..927cf8249a176207e345a4fb50b1f4c16faa8e37 100644 (file)
@@ -30,7 +30,7 @@
 
 <div id="{$uploadFieldId}UploadButtonDiv" class="uploadButtonDiv"></div>
 
-<input type="hidden" name="{$uploadFieldId}" value="{$uploadField->getInternalId()}">
+<input type="hidden" id="{$uploadFieldId}" name="{$uploadFieldId}" value="{$uploadField->getInternalId()}">
 
 <script data-relocate="true">
        require(['WoltLabSuite/Core/Ui/File/Upload', 'Language'], function(Upload, Language) {
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Dialog.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Dialog.js
new file mode 100644 (file)
index 0000000..b833273
--- /dev/null
@@ -0,0 +1,195 @@
+/**
+ * Provides API to easily create a dialog form created by form builder.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Manager
+ * @since      5.2
+ */
+define(['Ajax', 'Core', './Manager', 'Ui/Dialog'], function(Ajax, Core, FormBuilderManager, UiDialog) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderDialog(dialogId, className, actionName, options) {
+               this.init(dialogId, className, actionName, options);
+       };
+       FormBuilderDialog.prototype = {
+               /**
+                * Initializes the dialog.
+                * 
+                * @param       {string}        dialogId
+                * @param       {string}        className
+                * @param       {string}        actionName
+                * @param       {{actionParameters: object, destoryOnClose: boolean, dialog: object}}   options
+                */
+               init: function(dialogId, className, actionName, options) {
+                       this._dialogId = dialogId;
+                       this._className = className;
+                       this._actionName = actionName;
+                       this._options = Core.extend({
+                               actionParameters: {},
+                               destroyOnClose: false
+                       }, options);
+                       this._options.dialog = Core.extend(this._options.dialog || {}, {
+                               onClose: this._dialogOnClose.bind(this),
+                               onSetup: this._dialogOnSetup.bind(this)
+                       });
+                       
+                       this._formId = '';
+                       this._dialogContent = '';
+               },
+               
+               /**
+                * Returns the data for Ajax to setup the Ajax/Request object.
+                * 
+                * @return      {object}        setup data for Ajax/Request object
+                */
+               _ajaxSetup: function() {
+                       return {
+                               data: {
+                                       actionName: this._actionName,
+                                       className: this._className,
+                                       parameters: this._options.actionParameters
+                               }
+                       };
+               },
+               
+               /**
+                * Handles successful Ajax requests.
+                *
+                * @param       {object}        data    response data
+                */
+               _ajaxSuccess: function(data) {
+                       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.destroy(true);
+                       
+                       this._formId = data.returnValues.formId;
+                       this._dialogContent = data.returnValues.dialog;
+                       
+                       UiDialog.open(this, this._dialogContent);
+               },
+               
+               /**
+                * Is called when clicking on the dialog form's close button.
+                */
+               _closeDialog: function() {
+                       UiDialog.close(this);
+               },
+               
+               /**
+                * Is called when the dialog is set up.
+                * 
+                * @param       {HTMLElement}   content         dialog's content element
+                */
+               _dialogOnSetup: function(content) {
+                       var cancelButton = elById(this._formId + '_cancelButton', content);
+                       if (cancelButton !== null) {
+                               cancelButton.addEventListener('click', this._closeDialog.bind(this));
+                       }
+               },
+               
+               /**
+                * Is called by the dialog API when the dialog is closed.
+                */
+               _dialogOnClose: function() {
+                       if (this._options.destroyOnClose) {
+                               this.destroy();
+                       }
+               },
+               
+               /**
+                * Returns the data used to setup the dialog.
+                * 
+                * @return      {object}        setup data
+                */
+               _dialogSetup: function() {
+                       return {
+                               id: this._dialogId,
+                               options : this._options.dialog,
+                               source: this._dialogContent
+                       };
+               },
+               
+               /**
+                * Is called by the dialog API when the dialog form is submitted.
+                */
+               _dialogSubmit: function() {
+                       this.getData().then(this._submitForm.bind(this));
+               },
+               
+               /**
+                * Submits the form with the given form data.
+                * 
+                * @param       {object}        formData
+                */
+               _submitForm: function(formData) {
+                       if (typeof this._options.onSubmit === 'function') {
+                               this._options.onSubmit(formData);
+                       }
+                       else if (typeof this._options.submitActionName === 'string') {
+                               Ajax.api(this, {
+                                       actionName: this._options.submitActionName,
+                                       parameters: {
+                                               data: formData
+                                       }
+                               });
+                       }
+               },
+               
+               /**
+                * Destroys the dialog.
+                * 
+                * @param       {boolean}       ignoreDialog    if `true`, the actual dialog is not destroyed, only the form is
+                */
+               destroy: function(ignoreDialog) {
+                       if (this._formId !== '') {
+                               if (FormBuilderManager.hasForm(this._formId)) {
+                                       FormBuilderManager.unregisterForm(this._formId);
+                               }
+                               
+                               if (ignoreDialog !== true) {
+                                       UiDialog.destroy(this);
+                               }
+                       }
+               },
+               
+               /**
+                * Returns a promise that all of the dialog form's data.
+                * 
+                * @return      {Promise}
+                */
+               getData: function() {
+                       if (this._formId === '') {
+                               throw new Error("Form has not been requested yet.");
+                       }
+                       
+                       return FormBuilderManager.getData(this._formId);
+               },
+               
+               /**
+                * Opens the dialog form.
+                */
+               open: function() {
+                       if (UiDialog.getDialog(this._dialogId)) {
+                               UiDialog.openStatic(this._dialogId);
+                       }
+                       else {
+                               Ajax.api(this);
+                       }
+               }
+       };
+       
+       return FormBuilderDialog;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Captcha.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Captcha.js
new file mode 100644 (file)
index 0000000..4419c64
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Data handler for a captcha form builder field in an Ajax form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Captcha
+ * @since      5.2
+ */
+define(['Core', './Field', 'WoltLabSuite/Core/Controller/Captcha'], function(Core, FormBuilderField, Captcha) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldCaptcha(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldCaptcha, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#getData
+                */
+               _getData: function() {
+                       if (Captcha.has(this._fieldId)) {
+                               return Captcha.getData(this._fieldId);
+                       }
+                       
+                       return {};
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
+                */
+               _readField: function() {
+                       // does nothing
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#destroy
+                */
+               destroy: function() {
+                       if (Captcha.has(this._fieldId)) {
+                               Captcha.delete(this._fieldId);
+                       }
+               }
+       });
+       
+       return FormBuilderFieldCaptcha;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Checkboxes.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Checkboxes.js
new file mode 100644 (file)
index 0000000..ecca075
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Data handler for a form builder field in an Ajax form represented by checkboxes.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Checkboxes
+ * @since      5.2
+ */
+define(['Core', './Field'], function(Core, FormBuilderField) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldCheckboxes(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldCheckboxes, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var data = {};
+                       
+                       data[this._fieldId] = [];
+                       
+                       for (var i = 0, length = this._fields.length; i < length; i++) {
+                               if (this._fields[i].checked) {
+                                       data[this._fieldId].push(this._fields[i].value);
+                               }
+                       }
+                       
+                       return data;
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
+                */
+               _readField: function() {
+                       this._fields = elBySelAll('input[name="' + this._fieldId + '[]"]');
+               }
+       });
+       
+       return FormBuilderFieldCheckboxes;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Checked.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Checked.js
new file mode 100644 (file)
index 0000000..7673985
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * Data handler for a form builder field in an Ajax form that stores its value via a checkbox being
+ * checked or not.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Checked
+ * @since      5.2
+ */
+define(['Core', './Field'], function(Core, FormBuilderField) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldInput(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldInput, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var data = {};
+                       
+                       data[this._fieldId] = ~~this._field.checked;
+                       
+                       return data;
+               }
+       });
+       
+       return FormBuilderFieldInput;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Controller/Label.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Controller/Label.js
new file mode 100644 (file)
index 0000000..e0150da
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ * Handles the JavaScript part of the label form field.
+ * 
+ * @author     Alexander Ebert, Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Controller/Label
+ * @since      5.2
+ */
+define(['Core', 'Dom/Util', 'Language', 'Ui/SimpleDropdown'], function(Core, DomUtil, Language, UiSimpleDropdown) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldLabel(fielId, labelId, options) {
+               this.init(fielId, labelId, options);
+       };
+       FormBuilderFieldLabel.prototype = {
+               /**
+                * Initializes the label form field.
+                * 
+                * @param       {string}        fieldId         id of the relevant form builder field
+                * @param       {integer}       labelId         id of the currently selected label
+                * @param       {object}        options         additional label options
+                */
+               init: function(fieldId, labelId, options) {
+                       this._formFieldContainer = elById(fieldId + 'Container');
+                       this._labelChooser = elByClass('labelChooser', this._formFieldContainer)[0];
+                       this._options = Core.extend({
+                               forceSelection: false,
+                               showWithoutSelection: false
+                       }, options);
+                       
+                       this._input = elCreate('input');
+                       this._input.type = 'hidden';
+                       this._input.id = fieldId;
+                       this._input.name = fieldId;
+                       this._input.value = ~~labelId;
+                       this._formFieldContainer.appendChild(this._input);
+                       
+                       var labelChooserId = DomUtil.identify(this._labelChooser);
+                       
+                       // init dropdown
+                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
+                       if (dropdownMenu === null) {
+                               UiSimpleDropdown.init(elByClass('dropdownToggle', this._labelChooser)[0]);
+                               dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
+                       }
+                       
+                       var additionalOptionList = null;
+                       if (this._options.showWithoutSelection || !this._options.forceSelection) {
+                               additionalOptionList = elCreate('ul');
+                               dropdownMenu.appendChild(additionalOptionList);
+                               
+                               var dropdownDivider = elCreate('li');
+                               dropdownDivider.className = 'dropdownDivider';
+                               additionalOptionList.appendChild(dropdownDivider);
+                       }
+                       
+                       if (this._options.showWithoutSelection) {
+                               var listItem = elCreate('li');
+                               elData(listItem, 'label-id', -1);
+                               this._blockScroll(listItem);
+                               additionalOptionList.appendChild(listItem);
+                               
+                               var span = elCreate('span');
+                               listItem.appendChild(span);
+                               
+                               var label = elCreate('span');
+                               label.className = 'badge label';
+                               label.innerHTML = Language.get('wcf.label.withoutSelection');
+                               span.appendChild(label);
+                       }
+                       
+                       if (!this._options.forceSelection) {
+                               var listItem = elCreate('li');
+                               elData(listItem, 'label-id', 0);
+                               this._blockScroll(listItem);
+                               additionalOptionList.appendChild(listItem);
+                               
+                               var span = elCreate('span');
+                               listItem.appendChild(span);
+                               
+                               var label = elCreate('span');
+                               label.className = 'badge label';
+                               label.innerHTML = Language.get('wcf.label.none');
+                               span.appendChild(label);
+                       }
+                       
+                       elBySelAll('li:not(.dropdownDivider)', dropdownMenu, function(listItem) {
+                               listItem.addEventListener('click', this._click.bind(this));
+                               
+                               if (labelId) {
+                                       if (~~elData(listItem, 'label-id') === labelId) {
+                                               this._selectLabel(listItem);
+                                       }
+                               }
+                       }.bind(this));
+               },
+               
+               /**
+                * Blocks page scrolling for the given element.
+                * 
+                * @param       {HTMLElement}           element
+                */
+               _blockScroll: function(element) {
+                       element.addEventListener(
+                               'wheel',
+                               function(event) {
+                                       event.preventDefault();
+                               },
+                               {
+                                       passive: false
+                               }
+                       );
+               },
+               
+               /**
+                * Select a label after clicking on it.
+                * 
+                * @param       {Event}         event   click event in label selection dropdown
+                */
+               _click: function(event) {
+                       event.preventDefault();
+                       
+                       this._selectLabel(event.currentTarget, false);
+               },
+               
+               /**
+                * Selects the given label.
+                * 
+                * @param       {HTMLElement}   label
+                */
+               _selectLabel: function(label) {
+                       // save label
+                       var labelId = elData(label, 'label-id');
+                       if (!labelId) {
+                               labelId = 0;
+                       }
+                       
+                       // replace button with currently selected label
+                       var displayLabel = elBySel('span > span', label);
+                       var button = elBySel('.dropdownToggle > span', this._labelChooser);
+                       button.className = displayLabel.className;
+                       button.textContent = displayLabel.textContent;
+                       
+                       this._input.value = labelId;
+               }
+       };
+       
+       return FormBuilderFieldLabel;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Controller/Rating.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Controller/Rating.js
new file mode 100644 (file)
index 0000000..b9c6692
--- /dev/null
@@ -0,0 +1,159 @@
+/**
+ * Handles the JavaScript part of the rating form field.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Controller/Rating
+ * @since      5.2
+ */
+define(['Dictionary'], function(Dictionary) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldRating(fieldId, value, activeCssClasses, defaultCssClasses) {
+               this.init(fieldId, value, activeCssClasses, defaultCssClasses);
+       };
+       FormBuilderFieldRating.prototype = {
+               /**
+                * Initializes the rating form field.
+                * 
+                * @param       {string}        fieldId                 id of the relevant form builder field
+                * @param       {integer}       value                   current value of the field
+                * @param       {string[]}      activeCssClasses        CSS classes for the active state of rating elements
+                * @param       {string[]}      defaultCssClasses       CSS classes for the default state of rating elements
+                */
+               init: function(fieldId, value, activeCssClasses, defaultCssClasses) {
+                       this._field = elBySel('#' + fieldId + 'Container');
+                       if (this._field === null) {
+                               throw new Error("Unknown field with id '" + fieldId + "'");
+                       }
+                       
+                       this._input = elCreate('input');
+                       this._input.id = fieldId;
+                       this._input.name = fieldId;
+                       this._input.type = 'hidden';
+                       this._input.value = value;
+                       this._field.appendChild(this._input);
+                       
+                       this._activeCssClasses = activeCssClasses;
+                       this._defaultCssClasses = defaultCssClasses;
+                       
+                       this._ratingElements = new Dictionary();
+                       
+                       var ratingList = elBySel('.ratingList', this._field);
+                       ratingList.addEventListener('mouseleave', this._restoreRating.bind(this));
+                       
+                       elBySelAll('li', ratingList, function(listItem) {
+                               if (listItem.classList.contains('ratingMetaButton')) {
+                                       listItem.addEventListener('click', this._metaButtonClick.bind(this));
+                                       listItem.addEventListener('mouseenter', this._restoreRating.bind(this));
+                               }
+                               else {
+                                       this._ratingElements.set(~~elData(listItem, 'rating'), listItem);
+                                       
+                                       listItem.addEventListener('click', this._listItemClick.bind(this));
+                                       listItem.addEventListener('mouseenter', this._listItemMouseEnter.bind(this));
+                                       listItem.addEventListener('mouseleave', this._listItemMouseLeave.bind(this));
+                               }
+                       }.bind(this));
+               },
+               
+               /**
+                * Saves the rating associated with the clicked rating element.
+                * 
+                * @param       {Event}         event   rating element `click` event
+                */
+               _listItemClick: function(event) {
+                       this._input.value = ~~elData(event.currentTarget, 'rating');
+                       
+                       if (Environment.platform() !== 'desktop') {
+                               this._restoreRating();
+                       }
+               },
+               
+               /**
+                * Updates the rating UI when hovering over a rating element.
+                * 
+                * @param       {Event}         event   rating element `mouseenter` event
+                */
+               _listItemMouseEnter: function(event) {
+                       var currentRating = elData(event.currentTarget, 'rating');
+                       
+                       this._ratingElements.forEach(function(ratingElement, rating) {
+                               var icon = elByClass('icon', ratingElement)[0];
+                               
+                               this._toggleIcon(icon, rating <= currentRating);
+                       }.bind(this));
+               },
+               
+               /**
+                * Updates the rating UI when leaving a rating element by changing all rating elements
+                * to their default state.
+                */
+               _listItemMouseLeave: function() {
+                       this._ratingElements.forEach(function(ratingElement) {
+                               var icon = elByClass('icon', ratingElement)[0];
+                               
+                               this._toggleIcon(icon, false);
+                       }.bind(this));
+               },
+               
+               /**
+                * Handles clicks on meta buttons.
+                * 
+                * @param       {Event}         event   meta button `click` event
+                */
+               _metaButtonClick: function(event) {
+                       if (elData(event.currentTarget, 'action') === 'removeRating') {
+                               this._input.value = '';
+                               
+                               this._listItemMouseLeave();
+                       }
+               },
+               
+               /**
+                * Updates the rating UI by changing the rating elements to the stored rating state.
+                */
+               _restoreRating: function() {
+                       this._ratingElements.forEach(function(ratingElement, rating) {
+                               var icon = elByClass('icon', ratingElement)[0];
+                               
+                               this._toggleIcon(icon, rating <= this._input.value);
+                       }.bind(this));
+               },
+               
+               /**
+                * Toggles the state of the given icon based on the given state parameter.
+                * 
+                * @param       {HTMLElement}   icon            toggled icon
+                * @param       {boolean}       active          is `true` if icon will be changed to `active` state, otherwise changed to `default` state
+                */
+               _toggleIcon: function(icon, active) {
+                       active = active || false;
+                       
+                       if (active) {
+                               for (var i = 0; i < this._defaultCssClasses.length; i++) {
+                                       icon.classList.remove(this._defaultCssClasses[i]);
+                               }
+                               
+                               for (var i = 0; i < this._activeCssClasses.length; i++) {
+                                       icon.classList.add(this._activeCssClasses[i]);
+                               }
+                       }
+                       else {
+                               for (var i = 0; i < this._activeCssClasses.length; i++) {
+                                       icon.classList.remove(this._activeCssClasses[i]);
+                               }
+                               
+                               for (var i = 0; i < this._defaultCssClasses.length; i++) {
+                                       icon.classList.add(this._defaultCssClasses[i]);
+                               }
+                       }
+               }
+       };
+       
+       return FormBuilderFieldRating;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Date.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Date.js
new file mode 100644 (file)
index 0000000..770d742
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Data handler for a date form builder field in an Ajax form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Date
+ * @since      5.2
+ */
+define(['Core', 'WoltLabSuite/Core/Date/Picker', './Field'], function(Core, DatePicker, FormBuilderField) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldDate(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldDate, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var data = {};
+                       
+                       data[this._fieldId] = DatePicker.getValue(this._field);
+                       
+                       return data;
+               }
+       });
+       
+       return FormBuilderFieldDate;
+});
index 84e461dcab2571676eb5c384582979fbf57fb103..3c26dbca6f551ed7d3352471d606ce6cc3310078 100644 (file)
@@ -306,6 +306,47 @@ define(['Dictionary', 'Dom/ChangeListener', 'EventHandler', 'List', 'Dom/Travers
                        }
                        
                        _forms.add(form);
+               },
+               
+               /**
+                * Unregisters the form with the given id and all of its dependencies.
+                * 
+                * @param       {string}        formId          id of unregistered form
+                */
+               unregister: function(formId) {
+                       var form = elById(formId);
+                       
+                       if (form === null) {
+                               throw new Error("Unknown element with id '" + formId + "'");
+                       }
+                       
+                       if (!_forms.has(form)) {
+                               throw new Error("Form with id '" + formId + "' has not been registered.");
+                       }
+                       
+                       _forms.delete(form);
+                       
+                       _dependencyHiddenNodes.forEach(function(hiddenNode) {
+                               if (form.contains(hiddenNode)) {
+                                       _dependencyHiddenNodes.delete(hiddenNode);
+                               }
+                       });
+                       _nodeDependencies.forEach(function(dependencies, nodeId) {
+                               if (form.contains(elById(nodeId))) {
+                                       _nodeDependencies.delete(nodeId);
+                               }
+                               
+                               for (var i = 0, length = dependencies.length; i < length; i++) {
+                                       var fields = dependencies[i].getFields();
+                                       for (var j = 0, length = fields.length; j < length; j++) {
+                                               var field = fields[j];
+                                               
+                                               _fields.delete(field.id);
+                                               
+                                               _validatedFieldProperties.delete(field);
+                                       }
+                               }
+                       });
                }
        };
 });
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Field.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Field.js
new file mode 100644 (file)
index 0000000..59edc17
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * Data handler for a form builder field in an Ajax form.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Field
+ * @since      5.2
+ */
+define([], function() {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderField(fieldId) {
+               this.init(fieldId);
+       };
+       FormBuilderField.prototype = {
+               /**
+                * Initializes the form field.
+                * 
+                * @param       {string}        fieldId         id of the relevant form builder field
+                */
+               init: function(fieldId) {
+                       this._fieldId = fieldId;
+                       
+                       this._readField();
+               },
+               
+               /**
+                * Returns the current data of the field or a promise returning the current data
+                * of the field.
+                * 
+                * @return      {Promise|data}
+                */
+               _getData: function() {
+                       throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Field._getData!");
+               },
+               
+               /**
+                * Reads the field HTML element.
+                */
+               _readField: function() {
+                       this._field = elById(this._fieldId);
+                       
+                       if (this._field === null) {
+                               throw new Error("Unknown field with id '" + this._fieldId + "'.");
+                       }
+               },
+               
+               /**
+                * Destroys the field.
+                * 
+                * This function is useful for remove registered elements from other APIs like dialogs.
+                */
+               destroy: function() {
+                       // does nothing
+               },
+               
+               /**
+                * Returns a promise returning the current data of the field.
+                * 
+                * @return      {Promise}
+                */
+               getData: function() {
+                       var data = this._getData();
+                       
+                       if (!(data instanceof Promise)) {
+                               return Promise.resolve(data);
+                       }
+                       
+                       return data;
+               },
+               
+               /**
+                * Returns the id of the field.
+                * 
+                * @return      {string}
+                */
+               getId: function() {
+                       return this._fieldId;
+               }
+       };
+       
+       return FormBuilderField;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/ItemList.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/ItemList.js
new file mode 100644 (file)
index 0000000..0cc5513
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Data handler for an item list form builder field in an Ajax form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/ItemList
+ * @since      5.2
+ */
+define(['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList/Static'], function(Core, FormBuilderField, UiItemListStatic) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldItemList(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldItemList, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var data = {};
+                       data[this._fieldId] = [];
+                       
+                       var values = UiItemListStatic.getValues(this._fieldId);
+                       for (var i = 0, length = values.length; i < length; i++) {
+                               // TODO: data[this._fieldId] is an array but if code assumes object
+                               if (values[i].objectId) {
+                                       data[this._fieldId][values[i].objectId] = values[i].value;
+                               }
+                               else {
+                                       data[this._fieldId].push(values[i].value);
+                               }
+                       }
+                       
+                       return data;
+               }
+       });
+       
+       return FormBuilderFieldItemList;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Label.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Label.js
deleted file mode 100644 (file)
index ef136f7..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * Handles the JavaScript part of the label form field.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Label
- * @since      5.2
- */
-define(['Core', 'Dom/Util', 'Language', 'Ui/SimpleDropdown'], function(Core, DomUtil, Language, UiSimpleDropdown) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldLabel(fielId, labelId, options) {
-               this.init(fielId, labelId, options);
-       };
-       FormBuilderFieldLabel.prototype = {
-               /**
-                * Initializes the label form field.
-                * 
-                * @param       {string}        fieldId         id of the relevant form builder field
-                * @param       {integer}       labelId         id of the currently selected label
-                * @param       {object}        options         additional label options
-                */
-               init: function(fieldId, labelId, options) {
-                       this._formFieldContainer = elById(fieldId + 'Container');
-                       this._labelChooser = elByClass('labelChooser', this._formFieldContainer)[0];
-                       this._options = Core.extend({
-                               forceSelection: false,
-                               showWithoutSelection: false
-                       }, options);
-                       
-                       this._input = elCreate('input');
-                       this._input.type = 'hidden';
-                       this._input.name = fieldId;
-                       this._input.value = ~~labelId;
-                       this._formFieldContainer.appendChild(this._input);
-                       
-                       var labelChooserId = DomUtil.identify(this._labelChooser);
-                       
-                       // init dropdown
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
-                       if (dropdownMenu === null) {
-                               UiSimpleDropdown.init(elByClass('dropdownToggle', this._labelChooser)[0]);
-                               dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
-                       }
-                       
-                       var additionalOptionList = null;
-                       if (this._options.showWithoutSelection || !this._options.forceSelection) {
-                               additionalOptionList = elCreate('ul');
-                               dropdownMenu.appendChild(additionalOptionList);
-                               
-                               var dropdownDivider = elCreate('li');
-                               dropdownDivider.className = 'dropdownDivider';
-                               additionalOptionList.appendChild(dropdownDivider);
-                       }
-                       
-                       if (this._options.showWithoutSelection) {
-                               var listItem = elCreate('li');
-                               elData(listItem, 'label-id', -1);
-                               this._blockScroll(listItem);
-                               additionalOptionList.appendChild(listItem);
-                               
-                               var span = elCreate('span');
-                               listItem.appendChild(span);
-                               
-                               var label = elCreate('span');
-                               label.className = 'badge label';
-                               label.innerHTML = Language.get('wcf.label.withoutSelection');
-                               span.appendChild(label);
-                       }
-                       
-                       if (!this._options.forceSelection) {
-                               var listItem = elCreate('li');
-                               elData(listItem, 'label-id', 0);
-                               this._blockScroll(listItem);
-                               additionalOptionList.appendChild(listItem);
-                               
-                               var span = elCreate('span');
-                               listItem.appendChild(span);
-                               
-                               var label = elCreate('span');
-                               label.className = 'badge label';
-                               label.innerHTML = Language.get('wcf.label.none');
-                               span.appendChild(label);
-                       }
-                       
-                       elBySelAll('li:not(.dropdownDivider)', dropdownMenu, function(listItem) {
-                               listItem.addEventListener('click', this._click.bind(this));
-                               
-                               if (labelId) {
-                                       if (~~elData(listItem, 'label-id') === labelId) {
-                                               this._selectLabel(listItem);
-                                       }
-                               }
-                       }.bind(this));
-               },
-               
-               /**
-                * Blocks page scrolling for the given element.
-                * 
-                * @param       {HTMLElement}           element
-                */
-               _blockScroll: function(element) {
-                       element.addEventListener(
-                               'wheel',
-                               function(event) {
-                                       event.preventDefault();
-                               },
-                               {
-                                       passive: false
-                               }
-                       );
-               },
-               
-               /**
-                * Select a label after clicking on it.
-                * 
-                * @param       {Event}         event   click event in label selection dropdown
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       this._selectLabel(event.currentTarget, false);
-               },
-               
-               /**
-                * Selects the given label.
-                * 
-                * @param       {HTMLElement}   label
-                */
-               _selectLabel: function(label) {
-                       // save label
-                       var labelId = elData(label, 'label-id');
-                       if (!labelId) {
-                               labelId = 0;
-                       }
-                       
-                       // replace button with currently selected label
-                       var displayLabel = elBySel('span > span', label);
-                       var button = elBySel('.dropdownToggle > span', this._labelChooser);
-                       button.className = displayLabel.className;
-                       button.textContent = displayLabel.textContent;
-                       
-                       this._input.value = labelId;
-               }
-       };
-       
-       return FormBuilderFieldLabel;
-});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/RadioButton.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/RadioButton.js
new file mode 100644 (file)
index 0000000..b08f6b6
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * Data handler for a radio button form builder field in an Ajax form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/RadioButton
+ * @since      5.2
+ */
+define(['Core', './Field'], function(Core, FormBuilderField) {
+       "use strict";
+
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldRadioButton(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldRadioButton, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#getData
+                */
+               _getData: function() {
+                       var data = {};
+                       
+                       for (var i = 0, length = this._fields.length; i < length; i++) {
+                               if (this._fields[i].checked) {
+                                       data[this._fieldId] = this._fields[i].value;
+                                       break;
+                               }
+                       }
+                       
+                       return data;
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
+                */
+               _readField: function() {
+                       this._fields = elBySelAll('input[name=' + this._fieldId + ']');
+               },
+       });
+       
+       return FormBuilderFieldRadioButton;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Rating.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Rating.js
deleted file mode 100644 (file)
index 441a036..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-/**
- * Handles the JavaScript part of the rating form field.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Rating
- * @since      5.2
- */
-define(['Dictionary'], function(Dictionary) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldRating(fieldId, value, activeCssClasses, defaultCssClasses) {
-               this.init(fieldId, value, activeCssClasses, defaultCssClasses);
-       };
-       FormBuilderFieldRating.prototype = {
-               /**
-                * Initializes the rating form field.
-                * 
-                * @param       {string}        fieldId                 id of the relevant form builder field
-                * @param       {integer}       value                   current value of the field
-                * @param       {string[]}      activeCssClasses        CSS classes for the active state of rating elements
-                * @param       {string[]}      defaultCssClasses       CSS classes for the default state of rating elements
-                */
-               init: function(fieldId, value, activeCssClasses, defaultCssClasses) {
-                       this._field = elBySel('#' + fieldId + 'Container');
-                       if (this._field === null) {
-                               throw new Error("Unknown field with id '" + fieldId + "'");
-                       }
-                       
-                       this._input = elCreate('input');
-                       this._input.name = fieldId;
-                       this._input.type = 'hidden';
-                       this._input.value = value;
-                       this._field.appendChild(this._input);
-                       
-                       this._activeCssClasses = activeCssClasses;
-                       this._defaultCssClasses = defaultCssClasses;
-                       
-                       this._ratingElements = new Dictionary();
-                       
-                       var ratingList = elBySel('.ratingList', this._field);
-                       ratingList.addEventListener('mouseleave', this._restoreRating.bind(this));
-                       
-                       elBySelAll('li', ratingList, function(listItem) {
-                               if (listItem.classList.contains('ratingMetaButton')) {
-                                       listItem.addEventListener('click', this._metaButtonClick.bind(this));
-                                       listItem.addEventListener('mouseenter', this._restoreRating.bind(this));
-                               }
-                               else {
-                                       this._ratingElements.set(~~elData(listItem, 'rating'), listItem);
-                                       
-                                       listItem.addEventListener('click', this._listItemClick.bind(this));
-                                       listItem.addEventListener('mouseenter', this._listItemMouseEnter.bind(this));
-                                       listItem.addEventListener('mouseleave', this._listItemMouseLeave.bind(this));
-                               }
-                       }.bind(this));
-               },
-               
-               /**
-                * Saves the rating associated with the clicked rating element.
-                * 
-                * @param       {Event}         event   rating element `click` event
-                */
-               _listItemClick: function(event) {
-                       this._input.value = ~~elData(event.currentTarget, 'rating');
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               this._restoreRating();
-                       }
-               },
-               
-               /**
-                * Updates the rating UI when hovering over a rating element.
-                * 
-                * @param       {Event}         event   rating element `mouseenter` event
-                */
-               _listItemMouseEnter: function(event) {
-                       var currentRating = elData(event.currentTarget, 'rating');
-                       
-                       this._ratingElements.forEach(function(ratingElement, rating) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, rating <= currentRating);
-                       }.bind(this));
-               },
-               
-               /**
-                * Updates the rating UI when leaving a rating element by changing all rating elements
-                * to their default state.
-                */
-               _listItemMouseLeave: function() {
-                       this._ratingElements.forEach(function(ratingElement) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, false);
-                       }.bind(this));
-               },
-               
-               /**
-                * Handles clicks on meta buttons.
-                * 
-                * @param       {Event}         event   meta button `click` event
-                */
-               _metaButtonClick: function(event) {
-                       if (elData(event.currentTarget, 'action') === 'removeRating') {
-                               this._input.value = '';
-                               
-                               this._listItemMouseLeave();
-                       }
-               },
-               
-               /**
-                * Updates the rating UI by changing the rating elements to the stored rating state.
-                */
-               _restoreRating: function() {
-                       this._ratingElements.forEach(function(ratingElement, rating) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, rating <= this._input.value);
-                       }.bind(this));
-               },
-               
-               /**
-                * Toggles the state of the given icon based on the given state parameter.
-                * 
-                * @param       {HTMLElement}   icon            toggled icon
-                * @param       {boolean}       active          is `true` if icon will be changed to `active` state, otherwise changed to `default` state
-                */
-               _toggleIcon: function(icon, active) {
-                       active = active || false;
-                       
-                       if (active) {
-                               for (var i = 0; i < this._defaultCssClasses.length; i++) {
-                                       icon.classList.remove(this._defaultCssClasses[i]);
-                               }
-                               
-                               for (var i = 0; i < this._activeCssClasses.length; i++) {
-                                       icon.classList.add(this._activeCssClasses[i]);
-                               }
-                       }
-                       else {
-                               for (var i = 0; i < this._activeCssClasses.length; i++) {
-                                       icon.classList.remove(this._activeCssClasses[i]);
-                               }
-                               
-                               for (var i = 0; i < this._defaultCssClasses.length; i++) {
-                                       icon.classList.add(this._defaultCssClasses[i]);
-                               }
-                       }
-               }
-       };
-       
-       return FormBuilderFieldRating;
-});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/SimpleAcl.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/SimpleAcl.js
new file mode 100644 (file)
index 0000000..2da8625
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Data handler for a simple acl form builder field in an Ajax form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/SimpleAcl
+ * @since      5.2
+ */
+define(['Core', './Field'], function(Core, FormBuilderField) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldSimpleAcl(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldSimpleAcl, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var groupIds = [];
+                       elBySelAll('input[name="' + this._fieldId + '[group][]"]', undefined, function(input) {
+                               groupIds.push(~~input.value);
+                       });
+                       
+                       var usersIds = [];
+                       elBySelAll('input[name="' + this._fieldId + '[user][]"]', undefined, function(input) {
+                               usersIds.push(~~input.value);
+                       });
+                       
+                       var data = {};
+                       
+                       data[this._fieldId] = {
+                               group: groupIds,
+                               user: usersIds
+                       };
+                       
+                       return data;
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
+                */
+               _readField: function() {
+                       // does nothing
+               }
+       });
+       
+       return FormBuilderFieldSimpleAcl;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Tag.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Tag.js
new file mode 100644 (file)
index 0000000..a56bed9
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Data handler for a tag form builder field in an Ajax form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Tag
+ * @since      5.2
+ */
+define(['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList'], function(Core, FormBuilderField, UiItemList) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldTag(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldTag, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var data = {};
+                       data[this._fieldId] = [];
+                       
+                       var values = UiItemList.getValues(this._fieldId);
+                       for (var i = 0, length = values.length; i < length; i++) {
+                               data[this._fieldId].push(values[i].value);
+                       }
+                       
+                       return data;
+               }
+       });
+       
+       return FormBuilderFieldTag;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/User.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/User.js
new file mode 100644 (file)
index 0000000..2cb81e1
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * Data handler for a user form builder field in an Ajax form.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/User
+ * @since      5.2
+ */
+define(['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList'], function(Core, FormBuilderField, UiItemList) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldUser(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldUser, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var values = UiItemList.getValues(this._fieldId);
+                       var usernames = [];
+                       for (var i = 0, length = values.length; i < length; i++) {
+                               if (values[i].objectId) {
+                                       usernames.push(values[i].value);
+                               }
+                       }
+                       
+                       var data = {};
+                       data[this._fieldId] = usernames.join(',');
+                       
+                       return data;
+               }
+       });
+       
+       return FormBuilderFieldUser;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Value.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Value.js
new file mode 100644 (file)
index 0000000..a404ee4
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * Data handler for a form builder field in an Ajax form that stores its value in an input's value
+ * attribute.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Value
+ * @since      5.2
+ */
+define(['Core', './Field'], function(Core, FormBuilderField) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldValue(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldValue, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var data = {};
+                       
+                       data[this._fieldId] = this._field.value;
+                       
+                       return data;
+               }
+       });
+       
+       return FormBuilderFieldValue;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/ValueI18n.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/ValueI18n.js
new file mode 100644 (file)
index 0000000..7aceb81
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * Data handler for an i18n form builder field in an Ajax form that stores its value in an input's
+ * value attribute.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/ValueI18n
+ * @since      5.2
+ */
+define(['Core', './Field', 'WoltLabSuite/Core/Language/Input'], function(Core, FormBuilderField, LanguageInput) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldValueI18n(fieldId) {
+               this.init(fieldId);
+       };
+       Core.inherit(FormBuilderFieldValueI18n, FormBuilderField, {
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
+                */
+               _getData: function() {
+                       var data = {};
+                       
+                       var values = LanguageInput.getValues(this._fieldId);
+                       if (values.size > 1) {
+                               data[this._fieldId + '_i18n'] = values.toObject();
+                       }
+                       else {
+                               data[this._fieldId] = values.get(0);
+                       }
+                       
+                       return data;
+               },
+               
+               /**
+                * @see WoltLabSuite/Core/Form/Builder/Field/Field#destroy
+                */
+               destroy: function() {
+                       LanguageInput.unregister(this._fieldId);
+               }
+       });
+       
+       return FormBuilderFieldValueI18n;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Manager.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Manager.js
new file mode 100644 (file)
index 0000000..550a32e
--- /dev/null
@@ -0,0 +1,169 @@
+/**
+ * Manager for registered Ajax forms and its fields that can be used to retrieve the current data
+ * of the registered forms.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Manager
+ * @since      5.2
+ */
+define([
+       'Core',
+       'Dictionary',
+       './Field/Dependency/Manager',
+       './Field/Field'
+], function(
+       Core,
+       Dictionary,
+       FormBuilderFieldDependencyManager,
+       FormBuilderField
+) {
+       "use strict";
+       
+       var _fields = new Dictionary();
+       var _forms = new Dictionary();
+       
+       return {
+               /**
+                * Returns a promise returning the data of the form with the given id.
+                * 
+                * @param       {string}        formId
+                * @return      {Promise}
+                */
+               getData: function(formId) {
+                       if (!this.hasForm(formId)) {
+                               throw new Error("Unknown form with id '" + formId + "'.");
+                       }
+                       
+                       var promises = [];
+                       
+                       _fields.get(formId).forEach(function(field) {
+                               var fieldData = field.getData();
+                               
+                               if (!(fieldData instanceof Promise)) {
+                                       throw new TypeError("Data for field with id '" + field.getId() + "' is no promise.");
+                               }
+                               
+                               promises.push(fieldData);
+                       });
+                       
+                       return new Promise(function(resolve, reject) {
+                               Promise.all(promises).then(function(promiseData) {
+                                       var data = {};
+                                       
+                                       for (var i = 0, length = promiseData.length; i < length; i++) {
+                                               data = Core.extend(data, promiseData[i]);
+                                       }
+                                       
+                                       resolve(data);
+                               });
+                       });
+               },
+               
+               /**
+                * Returns the registered form with given id.
+                * 
+                * @param       {string}        formId
+                * @return      {HTMLElement}
+                */
+               getForm: function(formId) {
+                       if (!this.hasForm(formId)) {
+                               throw new Error("Unknown form with id '" + formId + "'.");
+                       }
+                       
+                       return _forms.get(formId);
+               },
+               
+               /**
+                * Returns `true` if a field with the given id has been registered for the form with
+                * the given id and `false` otherwise.
+                * 
+                * @param       {string}        formId
+                * @param       {string}        fieldId
+                * @return      {boolean}
+                */
+               hasField: function(formId, fieldId) {
+                       if (!this.hasForm(formId)) {
+                               throw new Error("Unknown form with id '" + formId + "'.");
+                       }
+                       
+                       return _fields.get(formId).has(fieldId);
+               },
+               
+               /**
+                * Returns `true` if a form with the given id has been registered and `false`
+                * otherwise.
+                * 
+                * @param       {string}        formId
+                * @return      {boolean}
+                */
+               hasForm: function(formId) {
+                       return _forms.has(formId);
+               },
+               
+               /**
+                * Registers the given field for the form with the given id.
+                * 
+                * @param       {string}                                        formId
+                * @param       {WoltLabSuite/Core/Form/Builder/Field/Field}    field
+                */
+               registerField: function(formId, field) {
+                       if (!this.hasForm(formId)) {
+                               throw new Error("Unknown form with id '" + formId + "'.");
+                       }
+                       
+                       if (!(field instanceof FormBuilderField)) {
+                               throw new Error("Add field is no instance of 'WoltLabSuite/Core/Form/Builder/Field/Field'.");
+                       }
+                       
+                       var fieldId = field.getId();
+                       
+                       if (this.hasField(formId, fieldId)) {
+                               throw new Error("Form field with id '" + fieldId + "' has already been registered for form with id '" + fieldId + "'.");
+                       }
+                       
+                       _fields.get(formId).set(fieldId, field);
+               },
+               
+               /**
+                * Registers the form with the given id.
+                * 
+                * @param       {string}        formId
+                */
+               registerForm: function(formId) {
+                       if (this.hasForm(formId)) {
+                               throw new Error("Form with id '" + formId + "' has already been registered.");
+                       }
+                       
+                       var form = elById(formId);
+                       if (form === null) {
+                               throw new Error("Unknown form with id '" + formId + "'.");
+                       }
+                       
+                       _forms.set(formId, form);
+                       _fields.set(formId, new Dictionary());
+               },
+               
+               /**
+                * Unregisters the form with the given id.
+                * 
+                * @param       {string}        formId
+                */
+               unregisterForm: function(formId) {
+                       if (!this.hasForm(formId)) {
+                               throw new Error("Unknown form with id '" + formId + "'.");
+                       }
+                       
+                       _forms.delete(formId);
+                       
+                       _fields.get(formId).forEach(function(field) {
+                               field.destroy();
+                       });
+                       
+                       _fields.delete(formId);
+                       
+                       FormBuilderFieldDependencyManager.unregister(formId);
+               }
+       };
+});
index 05595a938976b0222e86d9f68dd8c749796c6461..d0a0856dd421a2b1ee715ff4bdec14fc293855cf 100644 (file)
@@ -69,6 +69,21 @@ define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traver
                        _elements.get(elementId).callbacks.set(eventName, callback);
                },
                
+               /**
+                * Unregisters the element with the given id.
+                * 
+                * @param       {string}        elementId
+                * @since       5.2
+                */
+               unregister: function(elementId) {
+                       if (!_values.has(elementId)) {
+                               throw new Error("Unknown element id '" + elementId + "'.");
+                       }
+                       
+                       _values.delete(elementId);
+                       _elements.delete(elementId);
+               },
+               
                /**
                 * Caches common event listener callbacks.
                 */
index 781a0876f509cbc683d7ab677b5e81c452e25159..f9937a92f987ae0fba2136e9ffb022760580d199 100644 (file)
@@ -51,6 +51,8 @@ class DialogFormDocument extends FormDocument {
        protected function createDefaultButton() {
                parent::createDefaultButton();
                
+               $this->getButtons()['submitButton']->attribute('data-type', 'submit');
+               
                if ($this->isCancelable()) {
                        $this->addButton(
                                FormButton::create('cancelButton')
index 34bca7cd0c3b5ad25fb34c251b401eb6c7bf1e42..b6416492d731b556ab184812ccf809efa5a10949 100644 (file)
@@ -151,7 +151,11 @@ class FormDocument implements IFormDocument {
         * @inheritDoc
         */
        public function addButton(IFormButton $button) {
-               $this->buttons[] = $button;
+               if (isset($this->buttons[$button->getId()])) {
+                       throw new \InvalidArgumentException("There is already button with id '{$button->getId()}'.");
+               }
+               
+               $this->buttons[$button->getId()] = $button;
                
                $button->parent($this);
                
index 3255efde5669e24ec677e4107d4123b1821e6cf4..738cb2960b9f1b552476d61b3dadf78fd3e70e7d 100644 (file)
@@ -21,6 +21,12 @@ abstract class AbstractFormField implements IFormField {
        use TFormChildNode;
        use TFormElement;
        
+       /**
+        * name of the JavaScript data handler module used for Ajax dialogs
+        * @var null|string
+        */
+       protected $javaScriptDataHandlerModule;
+       
        /**
         * name of the object property this field represents
         * @var null|string
@@ -98,7 +104,10 @@ abstract class AbstractFormField implements IFormField {
                return WCF::getTPL()->fetch(
                        $this->templateName,
                        'wcf',
-                       array_merge($this->getHtmlVariables(), ['field' => $this]),
+                       array_merge($this->getHtmlVariables(), [
+                               'field' => $this,
+                               'javaScriptDataHandlerModule' => $this->javaScriptDataHandlerModule
+                       ]),
                        true
                );
        }
index b6031142b0755873221a3407dfb7e4978e8b44ed..85faf4972d5a23f2910bec8530a96570e517192a 100644 (file)
@@ -26,6 +26,11 @@ abstract class AbstractNumericFormField extends AbstractFormField implements IAu
         */
        protected $integerValues = false;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+       
        /**
         * step value for the input element
         * @var null|number
index 0c5d6f280f153fff51c1820984edfe2ba471a28d..665acba3f6180e7710abf47ec407c31c13dbfafb 100644 (file)
@@ -15,6 +15,11 @@ class BooleanFormField extends AbstractFormField implements IAutoFocusFormField,
        use TAutoFocusFormField;
        use TImmutableFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Checked';
+       
        /**
         * @inheritDoc
         */
index 153ffac4d8871caeb9aa47e9d2c3f7e05079a43a..9b163d4c111ac0525272d3f0b1a9f1e4e62195e5 100644 (file)
@@ -21,6 +21,11 @@ class CaptchaFormField extends AbstractFormField implements IObjectTypeFormNode
                objectType as defaultObjectType;
        }
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Captcha';
+       
        /**
         * @inheritDoc
         */
index 13c13e6244d6c7cdd8cfde14e56084f06e72dda1..84dcbd2363b009bec0fedbf61bb99f8d21ee63b7 100644 (file)
@@ -17,6 +17,11 @@ class DateFormField extends AbstractFormField implements IAutoFocusFormField, II
        use TImmutableFormField;
        use TNullableFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Date';
+       
        /**
         * date time format of the save value
         * @var string
index 11b76e03b58c3186aaaab24d721b8f9294bccf7c..3890b70d7ac77c7a66483384dc6fb215d59580a9 100644 (file)
@@ -15,6 +15,11 @@ use wcf\system\style\StyleHandler;
 class IconFormField extends AbstractFormField implements IImmutableFormField {
        use TImmutableFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+       
        /**
         * @inheritDoc
         */
index f0d7e5e03e33fea972255277c10d9fa9df6fe887..a98eede6c7d164bb1272105ff9edbaf4d6a61913 100644 (file)
@@ -18,6 +18,11 @@ class ItemListFormField extends AbstractFormField implements IAutoFocusFormField
        use TAutoFocusFormField;
        use TImmutableFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/ItemList';
+       
        /**
         * type of the returned save value (see `SAVE_VALUE_TYPE_*` constants)
         * @var string
index 59fc1e7f32aa463cabe5e1589111424ec569cc01..484fe87d79ac32fdb6bcfad7b54980d47058b5a1 100644 (file)
@@ -16,6 +16,11 @@ class MultipleSelectionFormField extends AbstractFormField implements IFilterabl
        use TImmutableFormField;
        use TNullableFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Checkboxes';
+       
        /**
         * @inheritDoc
         */
index 77ce5eca0afb17b700d198ca0084eb53a0f7649a..00b022cc648897b0c3f7e9f609f4001d8506e1c0 100644 (file)
@@ -15,6 +15,11 @@ class RadioButtonFormField extends AbstractFormField implements IImmutableFormFi
        use TImmutableFormField;
        use TSelectionFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/RadioButton';
+       
        /**
         * @inheritDoc
         */
index f254f8cca5e72d78d62172f85f11dce9db5e2ce1..999c43b6a641134f3a9baa07ef20916c8328ae18 100644 (file)
@@ -46,6 +46,11 @@ class RatingFormField extends AbstractFormField implements IImmutableFormField,
         */
        protected $defaultCssClasses = ['fa-star-o'];
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+       
        /**
         * @inheritDoc
         */
index 04543c2a495bca71292c6c54f2fec18001e32ae2..eb8540a37d8f778cecaea3197653aabe07b5e959 100644 (file)
@@ -13,9 +13,16 @@ use wcf\system\form\builder\field\validation\FormFieldValidationError;
  */
 class SingleSelectionFormField extends AbstractFormField implements IImmutableFormField, IFilterableSelectionFormField, INullableFormField {
        use TImmutableFormField;
-       use TFilterableSelectionFormField;
+       use TFilterableSelectionFormField {
+               filterable as protected traitFilterable;
+       }
        use TNullableFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+       
        /**
         * @inheritDoc
         */
@@ -32,6 +39,20 @@ class SingleSelectionFormField extends AbstractFormField implements IImmutableFo
                return parent::getSaveValue();
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function filterable($filterable = true) {
+               if ($filterable) {
+                       $this->javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/RadioButton';
+               }
+               else {
+                       $this->javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+               }
+               
+               return $this->traitFilterable($filterable);
+       }
+       
        /**
         * @inheritDoc
         */
index 7c4d6a2a36aaa62a3c6c3c2e6b7dfc32a0467cce..6d26b67d4449f09c6e192265e5d9cd92ecbbd76b 100644 (file)
@@ -43,6 +43,12 @@ trait TI18nFormField {
         */
        protected $languageItemPattern;
        
+       /**
+        * name of the nin-i18n JavaScript data handler module used for Ajax dialogs
+        * @var null|string
+        */
+       protected $nonI18nJavaScriptDataHandlerModule;
+       
        /**
         * Returns additional template variables used to generate the html representation
         * of this node.
@@ -165,6 +171,16 @@ trait TI18nFormField {
         * @return      II18nFormField                  this field
         */
        public function i18n($i18n = true) {
+               if ($this->javaScriptDataHandlerModule) {
+                       if ($this->isI18n() && !$i18n) {
+                               $this->javaScriptDataHandlerModule = $this->nonI18nJavaScriptDataHandlerModule;
+                       }
+                       else if (!$this->isI18n() && $i18n) {
+                               $this->nonI18nJavaScriptDataHandlerModule = $this->javaScriptDataHandlerModule;
+                               $this->javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/ValueI18n';
+                       }
+               }
+               
                $this->i18n = $i18n;
                
                return $this;
@@ -289,7 +305,7 @@ trait TI18nFormField {
         */
        public function readValue() {
                if ($this->isI18n()) {
-                       I18nHandler::getInstance()->readValues();
+                       I18nHandler::getInstance()->readValues($this->getDocument()->getRequestData());
                }
                else if ($this->getDocument()->hasRequestData($this->getPrefixedId())) {
                        $value = $this->getDocument()->getRequestData($this->getPrefixedId());
index 39d4321909d31893b0012fc31ae2f2b018ce8cfe..ff2f58caaecd7d1f67efdbb94478abb52eb790c8 100644 (file)
@@ -23,6 +23,11 @@ class TextFormField extends AbstractFormField implements IAutoFocusFormField, II
        use TMinimumLengthFormField;
        use TPlaceholderFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+       
        /**
         * @inheritDoc
         */
index 72b43c3800237011c4817595e6b3e115e56634e9..c078d8aa4ce60c6bbace338bd3f6d648ad01f403 100644 (file)
@@ -23,6 +23,11 @@ class UploadFormField extends AbstractFormField {
        }
        use TMinimumFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+       
        /**
         * This flag indicates whether only images can uploaded via this field.
         * <strong>Heads up:</strong> SVG images can contain bad code, therefore do not
index 3d8d6c8ccd34b848c31e6e67ea3d83ad8b79545a..f97835938510905372b3c37742e59953e531d8a1 100644 (file)
@@ -21,7 +21,12 @@ class SimpleAclFormField extends AbstractFormField {
        /**
         * @inheritDoc
         */
-       protected $templateName = 'aclSimple';
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/SimpleAcl';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $templateName = '__simpleAclFormField';
        
        /**
         * @inheritDoc
index 370715683e84bb2a4b0aa82aac1380c677f4ad40..c9392315f238443dfe9545b1621cda59882e0689 100644 (file)
@@ -23,6 +23,11 @@ use wcf\system\label\LabelHandler;
 class LabelFormField extends AbstractFormField implements IObjectTypeFormNode {
        use TObjectTypeFormNode;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+       
        /**
         * label group whose labels can be selected via this form field
         * @var ViewableLabelGroup
index 25be285577537206349f0e18a8a0fd9cd17cbaec..7cb5323b0b8a72aec89842f1b8a1d1767450bf3d 100644 (file)
@@ -28,6 +28,11 @@ class TagFormField extends AbstractFormField implements IObjectTypeFormNode {
        use TDefaultIdFormField;
        use TObjectTypeFormNode;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Tag';
+       
        /**
         * @inheritDoc
         */
index 7950deb00244701924dd634b9e5157f547d33fcd..fe086e60beca00ea0ce82166eb25a62ffa51a754 100644 (file)
@@ -29,6 +29,11 @@ class UserFormField extends AbstractFormField implements IAutoFocusFormField, II
        use TMultipleFormField;
        use TNullableFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/User';
+       
        /**
         * @inheritDoc
         */
index a45ecc40237cec1355e0a70242ad9eb5528d2247..90e73faa8b033258aacf727afa06f9e4b3eaa21b 100644 (file)
@@ -39,6 +39,11 @@ class UsernameFormField extends AbstractFormField implements IAutoFocusFormField
        use TNullableFormField;
        use TPlaceholderFormField;
        
+       /**
+        * @inheritDoc
+        */
+       protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+       
        /**
         * @inheritDoc
         */
index 5c2e880e0c159df4d9658e6b2b00d4f07ef23209..6f7c729acbb0b74c92a40b27d816330dc416fa00 100644 (file)
@@ -337,7 +337,7 @@ class WysiwygFormField extends AbstractFormField implements IMaximumLengthFormFi
         * @return      boolean
         */
        public function supportsQuotes() {
-               return $this->supportQuotes !== null;
+               return $this->supportQuotes;
        }
        
        /**
index eef3c37a39e12c98b7f9d85a2da50b9795d30817..4bd4dd3a80fe448266901f417f2ae33652b37da0 100644 (file)
@@ -95,19 +95,23 @@ class I18nHandler extends SingletonFactory {
        /**
         * Reads plain and i18n values from request data.
         */
-       public function readValues() {
+       public function readValues(array $requestData = null) {
+               if ($requestData === null) {
+                       $requestData = $_POST;
+               }
+               
                foreach ($this->elementIDs as $elementID) {
-                       if (isset($_POST[$elementID])) {
+                       if (isset($requestData[$elementID])) {
                                // you should trim the string before using it; prevents unwanted newlines
-                               $this->plainValues[$elementID] = StringUtil::unifyNewlines(StringUtil::trim($_POST[$elementID]));
+                               $this->plainValues[$elementID] = StringUtil::unifyNewlines(StringUtil::trim($requestData[$elementID]));
                                continue;
                        }
                        
                        $i18nElementID = $elementID . '_i18n';
-                       if (isset($_POST[$i18nElementID]) && is_array($_POST[$i18nElementID])) {
+                       if (isset($requestData[$i18nElementID]) && is_array($requestData[$i18nElementID])) {
                                $this->i18nValues[$elementID] = [];
                                
-                               foreach ($_POST[$i18nElementID] as $languageID => $value) {
+                               foreach ($requestData[$i18nElementID] as $languageID => $value) {
                                        $this->i18nValues[$elementID][$languageID] = StringUtil::unifyNewlines(StringUtil::trim($value));
                                }