… for easier Ajax form support.
See #2509
{@$field->getObjectType()->getProcessor()->getFormElement()}
+
+{include file='__formFieldDataHandler'}
<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>
--- /dev/null
+{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}
{include file='__formFieldDescription'}
{include file='__formFieldErrors'}
{include file='__formFieldDependencies'}
+ {include file='__formFieldDataHandler'}
</dd>
</dl>
{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}'
</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},
<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) {
{@$field->getObjectType()->getProcessor()->getFormElement()}
+
+{include file='__formFieldDataHandler'}
<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>
--- /dev/null
+{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}
{include file='__formFieldDescription'}
{include file='__formFieldErrors'}
{include file='__formFieldDependencies'}
+ {include file='__formFieldDataHandler'}
</dd>
</dl>
{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}'
</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},
--- /dev/null
+{include file='aclSimple'}
+
+{include file='__formFieldDataHandler'}
<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) {
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
}
_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);
+ }
+ }
+ });
}
};
});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
+++ /dev/null
-/**
- * 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;
-});
--- /dev/null
+/**
+ * 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;
+});
+++ /dev/null
-/**
- * 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;
-});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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;
+});
--- /dev/null
+/**
+ * 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);
+ }
+ };
+});
_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.
*/
protected function createDefaultButton() {
parent::createDefaultButton();
+ $this->getButtons()['submitButton']->attribute('data-type', 'submit');
+
if ($this->isCancelable()) {
$this->addButton(
FormButton::create('cancelButton')
* @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);
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
return WCF::getTPL()->fetch(
$this->templateName,
'wcf',
- array_merge($this->getHtmlVariables(), ['field' => $this]),
+ array_merge($this->getHtmlVariables(), [
+ 'field' => $this,
+ 'javaScriptDataHandlerModule' => $this->javaScriptDataHandlerModule
+ ]),
true
);
}
*/
protected $integerValues = false;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+
/**
* step value for the input element
* @var null|number
use TAutoFocusFormField;
use TImmutableFormField;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Checked';
+
/**
* @inheritDoc
*/
objectType as defaultObjectType;
}
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Captcha';
+
/**
* @inheritDoc
*/
use TImmutableFormField;
use TNullableFormField;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Date';
+
/**
* date time format of the save value
* @var string
class IconFormField extends AbstractFormField implements IImmutableFormField {
use TImmutableFormField;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+
/**
* @inheritDoc
*/
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
use TImmutableFormField;
use TNullableFormField;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Checkboxes';
+
/**
* @inheritDoc
*/
use TImmutableFormField;
use TSelectionFormField;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/RadioButton';
+
/**
* @inheritDoc
*/
*/
protected $defaultCssClasses = ['fa-star-o'];
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+
/**
* @inheritDoc
*/
*/
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
*/
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
*/
*/
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.
* @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;
*/
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());
use TMinimumLengthFormField;
use TPlaceholderFormField;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+
/**
* @inheritDoc
*/
}
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
/**
* @inheritDoc
*/
- protected $templateName = 'aclSimple';
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/SimpleAcl';
+
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = '__simpleAclFormField';
/**
* @inheritDoc
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
use TDefaultIdFormField;
use TObjectTypeFormNode;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Tag';
+
/**
* @inheritDoc
*/
use TMultipleFormField;
use TNullableFormField;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/User';
+
/**
* @inheritDoc
*/
use TNullableFormField;
use TPlaceholderFormField;
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/Value';
+
/**
* @inheritDoc
*/
* @return boolean
*/
public function supportsQuotes() {
- return $this->supportQuotes !== null;
+ return $this->supportQuotes;
}
/**
/**
* 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));
}