Devtools?: typeof Devtools;
ENABLE_DEBUG_MODE: boolean;
ENABLE_DEVELOPER_TOOLS: boolean;
+ LANGUAGE_ID: number;
REACTION_TYPES: {
[key: string]: Reaction;
};
* I18n interface for input and textarea fields.
*
* @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @module WoltLabSuite/Core/Language/Input
*/
-define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traverse', 'Dom/Util', 'Ui/SimpleDropdown'], function (Core, Dictionary, Language, ObjectMap, StringUtil, DomTraverse, DomUtil, UiSimpleDropdown) {
+define(["require", "exports", "tslib", "../Dom/Util", "../Language", "../Ui/Dropdown/Simple", "../StringUtil"], function (require, exports, tslib_1, Util_1, Language, Simple_1, StringUtil) {
"use strict";
- var _elements = new Dictionary();
- var _didInit = false;
- var _forms = new ObjectMap();
- var _values = new Dictionary();
- var _callbackDropdownToggle = null;
- var _callbackSubmit = null;
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.validate = exports.isEnabled = exports.enable = exports.disable = exports.setValues = exports.getValues = exports.unregister = exports.registerCallback = exports.init = void 0;
+ Util_1 = tslib_1.__importDefault(Util_1);
+ Language = tslib_1.__importStar(Language);
+ Simple_1 = tslib_1.__importDefault(Simple_1);
+ StringUtil = tslib_1.__importStar(StringUtil);
+ const _elements = new Map();
+ const _forms = new WeakMap();
+ const _values = new Map();
/**
- * @exports WoltLabSuite/Core/Language/Input
+ * Sets up DOM and event listeners for an input field.
*/
- return {
- /**
- * Initializes an input field.
- *
- * @param {string} elementId input element id
- * @param {Object} values preset values per language id
- * @param {Object} availableLanguages language names per language id
- * @param {boolean} forceSelection require i18n input
- */
- init: function (elementId, values, availableLanguages, forceSelection) {
- if (_values.has(elementId)) {
- return;
- }
- var element = elById(elementId);
- if (element === null) {
- throw new Error("Expected a valid element id, cannot find '" + elementId + "'.");
- }
- this._setup();
- // unescape values
- var unescapedValues = new Dictionary();
- for (var key in values) {
- if (values.hasOwnProperty(key)) {
- unescapedValues.set(~~key, StringUtil.unescapeHTML(values[key]));
- }
- }
- _values.set(elementId, unescapedValues);
- this._initElement(elementId, element, unescapedValues, availableLanguages, forceSelection);
- },
- /**
- * Registers a callback for an element.
- *
- * @param {string} elementId
- * @param {string} eventName
- * @param {function} callback
- */
- registerCallback: function (elementId, eventName, callback) {
- if (!_values.has(elementId)) {
- throw new Error("Unknown element id '" + elementId + "'.");
+ function initElement(elementId, element, values, availableLanguages, forceSelection) {
+ let container = element.parentElement;
+ if (!container.classList.contains("inputAddon")) {
+ container = document.createElement("div");
+ container.className = "inputAddon";
+ if (element.nodeName === "TEXTAREA") {
+ container.classList.add("inputAddonTextarea");
}
- _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 + "'.");
+ container.dataset.inputId = elementId;
+ const hasFocus = document.activeElement === element;
+ // DOM manipulation causes focused element to lose focus
+ element.insertAdjacentElement("beforebegin", container);
+ container.appendChild(element);
+ if (hasFocus) {
+ element.focus();
}
- _values.delete(elementId);
- _elements.delete(elementId);
- },
- /**
- * Caches common event listener callbacks.
- */
- _setup: function () {
- if (_didInit)
- return;
- _didInit = true;
- _callbackDropdownToggle = this._dropdownToggle.bind(this);
- _callbackSubmit = this._submit.bind(this);
- },
- /**
- * Sets up DOM and event listeners for an input field.
- *
- * @param {string} elementId input element id
- * @param {Element} element input or textarea element
- * @param {Dictionary} values preset values per language id
- * @param {Object} availableLanguages language names per language id
- * @param {boolean} forceSelection require i18n input
- */
- _initElement: function (elementId, element, values, availableLanguages, forceSelection) {
- var container = element.parentNode;
- if (!container.classList.contains('inputAddon')) {
- container = elCreate('div');
- container.className = 'inputAddon' + (element.nodeName === 'TEXTAREA' ? ' inputAddonTextarea' : '');
- //noinspection JSCheckFunctionSignatures
- elData(container, 'input-id', elementId);
- var hasFocus = document.activeElement === element;
- // DOM manipulation causes focused element to lose focus
- element.parentNode.insertBefore(container, element);
- container.appendChild(element);
- if (hasFocus) {
- element.focus();
- }
+ }
+ container.classList.add("dropdown");
+ const button = document.createElement("span");
+ button.className = "button dropdownToggle inputPrefix";
+ const buttonLabel = document.createElement("span");
+ buttonLabel.textContent = Language.get("wcf.global.button.disabledI18n");
+ button.appendChild(buttonLabel);
+ container.insertBefore(button, element);
+ const dropdownMenu = document.createElement("ul");
+ dropdownMenu.className = "dropdownMenu";
+ button.insertAdjacentElement("afterend", dropdownMenu);
+ const callbackClick = (event) => {
+ let target;
+ if (event instanceof HTMLElement) {
+ target = event;
}
- container.classList.add('dropdown');
- var button = elCreate('span');
- button.className = 'button dropdownToggle inputPrefix';
- var span = elCreate('span');
- span.textContent = Language.get('wcf.global.button.disabledI18n');
- button.appendChild(span);
- container.insertBefore(button, element);
- var dropdownMenu = elCreate('ul');
- dropdownMenu.className = 'dropdownMenu';
- DomUtil.insertAfter(dropdownMenu, button);
- var callbackClick = (function (event, isInit) {
- var languageId = ~~elData(event.currentTarget, 'language-id');
- var activeItem = DomTraverse.childByClass(dropdownMenu, 'active');
- if (activeItem !== null)
- activeItem.classList.remove('active');
- if (languageId)
- event.currentTarget.classList.add('active');
- this._select(elementId, languageId, isInit || false);
- }).bind(this);
- // build language dropdown
- var listItem;
- for (var languageId in availableLanguages) {
- if (availableLanguages.hasOwnProperty(languageId)) {
- listItem = elCreate('li');
- elData(listItem, 'language-id', languageId);
- span = elCreate('span');
- span.textContent = availableLanguages[languageId];
- listItem.appendChild(span);
- listItem.addEventListener('click', callbackClick);
- dropdownMenu.appendChild(listItem);
- }
+ else {
+ target = event.currentTarget;
}
- if (forceSelection !== true) {
- listItem = elCreate('li');
- listItem.className = 'dropdownDivider';
- dropdownMenu.appendChild(listItem);
- listItem = elCreate('li');
- elData(listItem, 'language-id', 0);
- span = elCreate('span');
- span.textContent = Language.get('wcf.global.button.disabledI18n');
- listItem.appendChild(span);
- listItem.addEventListener('click', callbackClick);
- dropdownMenu.appendChild(listItem);
+ const languageId = ~~target.dataset.languageId;
+ const activeItem = dropdownMenu.querySelector(".active");
+ if (activeItem !== null) {
+ activeItem.classList.remove("active");
}
- var activeItem = null;
- if (forceSelection === true || values.size) {
- for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
- //noinspection JSUnresolvedVariable
- if (~~elData(dropdownMenu.children[i], 'language-id') === LANGUAGE_ID) {
- activeItem = dropdownMenu.children[i];
- break;
- }
- }
+ if (languageId) {
+ target.classList.add("active");
}
- UiSimpleDropdown.init(button);
- UiSimpleDropdown.registerCallback(container.id, _callbackDropdownToggle);
- _elements.set(elementId, {
- buttonLabel: button.children[0],
- callbacks: new Dictionary(),
- element: element,
- languageId: 0,
- isEnabled: true,
- forceSelection: forceSelection
+ const isInit = event instanceof HTMLElement;
+ select(elementId, languageId, isInit);
+ };
+ // build language dropdown
+ Object.entries(availableLanguages).forEach(([languageId, languageName]) => {
+ const listItem = document.createElement("li");
+ listItem.dataset.languageId = languageId;
+ const span = document.createElement("span");
+ span.textContent = languageName;
+ listItem.appendChild(span);
+ listItem.addEventListener("click", callbackClick);
+ dropdownMenu.appendChild(listItem);
+ });
+ if (!forceSelection) {
+ const divider = document.createElement("li");
+ divider.className = "dropdownDivider";
+ dropdownMenu.appendChild(divider);
+ const listItem = document.createElement("li");
+ listItem.dataset.languageId = "0";
+ listItem.addEventListener("click", callbackClick);
+ const span = document.createElement("span");
+ span.textContent = Language.get("wcf.global.button.disabledI18n");
+ listItem.appendChild(span);
+ dropdownMenu.appendChild(listItem);
+ }
+ let activeItem = undefined;
+ if (forceSelection || values.size) {
+ activeItem = Array.from(dropdownMenu.children).find((element) => {
+ return +element.dataset.languageId === window.LANGUAGE_ID;
});
- // bind to submit event
- var submit = DomTraverse.parentByTag(element, 'FORM');
- if (submit !== null) {
- submit.addEventListener('submit', _callbackSubmit);
- var elementIds = _forms.get(submit);
- if (elementIds === undefined) {
- elementIds = [];
- _forms.set(submit, elementIds);
- }
- elementIds.push(elementId);
+ }
+ Simple_1.default.init(button);
+ Simple_1.default.registerCallback(container.id, dropdownToggle);
+ _elements.set(elementId, {
+ buttonLabel,
+ callbacks: new Map(),
+ element,
+ languageId: 0,
+ isEnabled: true,
+ forceSelection,
+ });
+ // bind to submit event
+ const form = element.closest("form");
+ if (form !== null) {
+ form.addEventListener("submit", submit);
+ let elementIds = _forms.get(form);
+ if (elementIds === undefined) {
+ elementIds = [];
+ _forms.set(form, elementIds);
}
- if (activeItem !== null) {
- callbackClick({ currentTarget: activeItem }, true);
+ elementIds.push(elementId);
+ }
+ if (activeItem) {
+ callbackClick(activeItem);
+ }
+ }
+ /**
+ * Selects a language or non-i18n from the dropdown list.
+ */
+ function select(elementId, languageId, isInit) {
+ const data = _elements.get(elementId);
+ const dropdownMenu = Simple_1.default.getDropdownMenu(data.element.closest(".inputAddon").id);
+ const item = dropdownMenu.querySelector(`[data-language-id="${languageId}"]`);
+ const label = item ? item.textContent : "";
+ // save current value
+ if (data.languageId !== languageId) {
+ const values = _values.get(elementId);
+ if (data.languageId) {
+ values.set(data.languageId, data.element.value);
}
- },
- /**
- * Selects a language or non-i18n from the dropdown list.
- *
- * @param {string} elementId input element id
- * @param {int} languageId language id or `0` to disable i18n
- * @param {boolean} isInit triggers pre-selection on init
- */
- _select: function (elementId, languageId, isInit) {
- var data = _elements.get(elementId);
- var dropdownMenu = UiSimpleDropdown.getDropdownMenu(data.element.closest('.inputAddon').id);
- var item, label = '';
- for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
- item = dropdownMenu.children[i];
- var itemLanguageId = elData(item, 'language-id');
- if (itemLanguageId.length && languageId === ~~itemLanguageId) {
- label = item.children[0].textContent;
- }
+ if (languageId === 0) {
+ _values.set(elementId, new Map());
}
- // save current value
- if (data.languageId !== languageId) {
- var values = _values.get(elementId);
+ else if (data.buttonLabel.classList.contains("active") || isInit) {
+ data.element.value = values.get(languageId) || "";
+ }
+ // update label
+ data.buttonLabel.textContent = label;
+ data.buttonLabel.classList[languageId ? "add" : "remove"]("active");
+ data.languageId = languageId;
+ }
+ if (!isInit) {
+ data.element.blur();
+ data.element.focus();
+ }
+ if (data.callbacks.has("select")) {
+ data.callbacks.get("select")(data.element);
+ }
+ }
+ /**
+ * Callback for dropdowns being opened, flags items with a missing value for one or more languages.
+ */
+ function dropdownToggle(containerId, action) {
+ if (action !== "open") {
+ return;
+ }
+ const dropdownMenu = Simple_1.default.getDropdownMenu(containerId);
+ const container = document.getElementById(containerId);
+ const elementId = container.dataset.inputId;
+ const data = _elements.get(elementId);
+ const values = _values.get(elementId);
+ Array.from(dropdownMenu.children).forEach((item) => {
+ const languageId = ~~(item.dataset.languageId || "");
+ if (languageId) {
+ let hasMissingValue = false;
if (data.languageId) {
- values.set(data.languageId, data.element.value);
+ if (languageId === data.languageId) {
+ hasMissingValue = data.element.value.trim() === "";
+ }
+ else {
+ hasMissingValue = !values.get(languageId);
+ }
}
- if (languageId === 0) {
- _values.set(elementId, new Dictionary());
+ if (hasMissingValue) {
+ item.classList.add("missingValue");
}
- else if (data.buttonLabel.classList.contains('active') || isInit === true) {
- data.element.value = (values.has(languageId)) ? values.get(languageId) : '';
+ else {
+ item.classList.remove("missingValue");
}
- // update label
- data.buttonLabel.textContent = label;
- data.buttonLabel.classList[(languageId ? 'add' : 'remove')]('active');
- data.languageId = languageId;
- }
- if (!isInit) {
- data.element.blur();
- data.element.focus();
}
- if (data.callbacks.has('select')) {
- data.callbacks.get('select')(data.element);
- }
- },
- /**
- * Callback for dropdowns being opened, flags items with a missing value for one or more languages.
- *
- * @param {string} containerId dropdown container id
- * @param {string} action toggle action, can be `open` or `close`
- */
- _dropdownToggle: function (containerId, action) {
- if (action !== 'open') {
+ });
+ }
+ /**
+ * Inserts hidden fields for i18n input on submit.
+ */
+ function submit(event) {
+ const form = event.currentTarget;
+ const elementIds = _forms.get(form);
+ elementIds.forEach((elementId) => {
+ const data = _elements.get(elementId);
+ if (!data.isEnabled) {
return;
}
- var dropdownMenu = UiSimpleDropdown.getDropdownMenu(containerId);
- var elementId = elData(elById(containerId), 'input-id');
- var data = _elements.get(elementId);
- var values = _values.get(elementId);
- var item, languageId;
- for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
- item = dropdownMenu.children[i];
- languageId = ~~elData(item, 'language-id');
- if (languageId) {
- var hasMissingValue = false;
- if (data.languageId) {
- if (languageId === data.languageId) {
- hasMissingValue = (data.element.value.trim() === '');
- }
- else {
- hasMissingValue = (!values.get(languageId));
- }
- }
- item.classList[(hasMissingValue ? 'add' : 'remove')]('missingValue');
- }
+ const values = _values.get(elementId);
+ if (data.callbacks.has("submit")) {
+ data.callbacks.get("submit")(data.element);
}
- },
- /**
- * Inserts hidden fields for i18n input on submit.
- *
- * @param {Object} event event object
- */
- _submit: function (event) {
- var elementIds = _forms.get(event.currentTarget);
- var data, elementId, input, values;
- for (var i = 0, length = elementIds.length; i < length; i++) {
- elementId = elementIds[i];
- data = _elements.get(elementId);
- if (data.isEnabled) {
- values = _values.get(elementId);
- if (data.callbacks.has('submit')) {
- data.callbacks.get('submit')(data.element);
- }
- // update with current value
- if (data.languageId) {
- values.set(data.languageId, data.element.value);
- }
- if (values.size) {
- values.forEach(function (value, languageId) {
- input = elCreate('input');
- input.type = 'hidden';
- input.name = elementId + '_i18n[' + languageId + ']';
- input.value = value;
- event.currentTarget.appendChild(input);
- });
- // remove name attribute to enforce i18n values
- data.element.removeAttribute('name');
- }
- }
- }
- },
- /**
- * Returns the values of an input field.
- *
- * @param {string} elementId input element id
- * @return {Dictionary} values stored for the different languages
- */
- getValues: function (elementId) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
- var values = _values.get(elementId);
// update with current value
- values.set(element.languageId, element.element.value);
- return values;
- },
- /**
- * Sets the values of an input field.
- *
- * @param {string} elementId input element id
- * @param {Dictionary} values values for the different languages
- */
- setValues: function (elementId, values) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
- if (Core.isPlainObject(values)) {
- values = Dictionary.fromObject(values);
+ if (data.languageId) {
+ values.set(data.languageId, data.element.value);
}
- element.element.value = '';
- if (values.has(0)) {
- element.element.value = values.get(0);
- values['delete'](0);
- _values.set(elementId, values);
- this._select(elementId, 0, true);
- return;
+ if (values.size) {
+ values.forEach(function (value, languageId) {
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = `${elementId}_i18n[${languageId}]`;
+ input.value = value;
+ form.appendChild(input);
+ });
+ // remove name attribute to enforce i18n values
+ data.element.removeAttribute("name");
}
+ });
+ }
+ /**
+ * Initializes an input field.
+ */
+ function init(elementId, values, availableLanguages, forceSelection) {
+ if (_values.has(elementId)) {
+ return;
+ }
+ const element = document.getElementById(elementId);
+ if (element === null) {
+ throw new Error(`Expected a valid element id, cannot find '${elementId}'.`);
+ }
+ // unescape values
+ const unescapedValues = new Map();
+ Object.entries(values).forEach(([languageId, value]) => {
+ unescapedValues.set(+languageId, StringUtil.unescapeHTML(value));
+ });
+ _values.set(elementId, unescapedValues);
+ initElement(elementId, element, unescapedValues, availableLanguages, forceSelection);
+ }
+ exports.init = init;
+ /**
+ * Registers a callback for an element.
+ */
+ function registerCallback(elementId, eventName, callback) {
+ if (!_values.has(elementId)) {
+ throw new Error(`Unknown element id '${elementId}'.`);
+ }
+ _elements.get(elementId).callbacks.set(eventName, callback);
+ }
+ exports.registerCallback = registerCallback;
+ /**
+ * Unregisters the element with the given id.
+ *
+ * @since 5.2
+ */
+ function unregister(elementId) {
+ if (!_values.has(elementId)) {
+ throw new Error(`Unknown element id '${elementId}'.`);
+ }
+ _values.delete(elementId);
+ _elements.delete(elementId);
+ }
+ exports.unregister = unregister;
+ /**
+ * Returns the values of an input field.
+ */
+ function getValues(elementId) {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+ const values = _values.get(elementId);
+ // update with current value
+ values.set(element.languageId, element.element.value);
+ return values;
+ }
+ exports.getValues = getValues;
+ /**
+ * Sets the values of an input field.
+ */
+ function setValues(elementId, newValues) {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+ element.element.value = "";
+ const values = new Map(Object.entries(newValues).map(([languageId, value]) => {
+ return [+languageId, value];
+ }));
+ if (values.has(0)) {
+ element.element.value = values.get(0);
+ values.delete(0);
_values.set(elementId, values);
- element.languageId = 0;
- //noinspection JSUnresolvedVariable
- this._select(elementId, LANGUAGE_ID, true);
- },
- /**
- * Disables the i18n interface for an input field.
- *
- * @param {string} elementId input element id
- */
- disable: function (elementId) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid element, '" + elementId + "' is not an i18n input field.");
- }
- if (!element.isEnabled)
- return;
- element.isEnabled = false;
- // hide language dropdown
- //noinspection JSCheckFunctionSignatures
- elHide(element.buttonLabel.parentNode);
- var dropdownContainer = element.buttonLabel.parentNode.parentNode;
- dropdownContainer.classList.remove('inputAddon');
- dropdownContainer.classList.remove('dropdown');
- },
- /**
- * Enables the i18n interface for an input field.
- *
- * @param {string} elementId input element id
- */
- enable: function (elementId) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
- if (element.isEnabled)
- return;
- element.isEnabled = true;
- // show language dropdown
- //noinspection JSCheckFunctionSignatures
- elShow(element.buttonLabel.parentNode);
- var dropdownContainer = element.buttonLabel.parentNode.parentNode;
- dropdownContainer.classList.add('inputAddon');
- dropdownContainer.classList.add('dropdown');
- },
- /**
- * Returns true if i18n input is enabled for an input field.
- *
- * @param {string} elementId input element id
- * @return {boolean}
- */
- isEnabled: function (elementId) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
- return element.isEnabled;
- },
- /**
- * Returns true if the value of an i18n input field is valid.
- *
- * If the element is disabled, true is returned.
- *
- * @param {string} elementId input element id
- * @param {boolean} permitEmptyValue if true, input may be empty for all languages
- * @return {boolean} true if input is valid
- */
- validate: function (elementId, permitEmptyValue) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
- if (!element.isEnabled)
- return true;
- var values = _values.get(elementId);
- var dropdownMenu = UiSimpleDropdown.getDropdownMenu(element.element.parentNode.id);
- if (element.languageId) {
- values.set(element.languageId, element.element.value);
- }
- var item, languageId;
- var hasEmptyValue = false, hasNonEmptyValue = false;
- for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
- item = dropdownMenu.children[i];
- languageId = ~~elData(item, 'language-id');
- if (languageId) {
- if (!values.has(languageId) || values.get(languageId).length === 0) {
- // input has non-empty value for previously checked language
- if (hasNonEmptyValue) {
- return false;
- }
- hasEmptyValue = true;
+ select(elementId, 0, true);
+ return;
+ }
+ _values.set(elementId, values);
+ element.languageId = 0;
+ select(elementId, window.LANGUAGE_ID, true);
+ }
+ exports.setValues = setValues;
+ /**
+ * Disables the i18n interface for an input field.
+ */
+ function disable(elementId) {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid element, '${elementId}' is not an i18n input field.`);
+ }
+ if (!element.isEnabled) {
+ return;
+ }
+ element.isEnabled = false;
+ // hide language dropdown
+ const buttonContainer = element.buttonLabel.parentElement;
+ Util_1.default.hide(buttonContainer);
+ const dropdownContainer = buttonContainer.parentElement;
+ dropdownContainer.classList.remove("inputAddon", "dropdown");
+ }
+ exports.disable = disable;
+ /**
+ * Enables the i18n interface for an input field.
+ */
+ function enable(elementId) {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+ if (element.isEnabled) {
+ return;
+ }
+ element.isEnabled = true;
+ // show language dropdown
+ const buttonContainer = element.buttonLabel.parentElement;
+ Util_1.default.show(buttonContainer);
+ const dropdownContainer = buttonContainer.parentElement;
+ dropdownContainer.classList.add("inputAddon", "dropdown");
+ }
+ exports.enable = enable;
+ /**
+ * Returns true if i18n input is enabled for an input field.
+ */
+ function isEnabled(elementId) {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+ return element.isEnabled;
+ }
+ exports.isEnabled = isEnabled;
+ /**
+ * Returns true if the value of an i18n input field is valid.
+ *
+ * If the element is disabled, true is returned.
+ */
+ function validate(elementId, permitEmptyValue) {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+ if (!element.isEnabled) {
+ return true;
+ }
+ const values = _values.get(elementId);
+ const dropdownMenu = Simple_1.default.getDropdownMenu(element.element.parentElement.id);
+ if (element.languageId) {
+ values.set(element.languageId, element.element.value);
+ }
+ let hasEmptyValue = false;
+ let hasNonEmptyValue = false;
+ Array.from(dropdownMenu.children).forEach((item) => {
+ const languageId = ~~item.dataset.languageId;
+ if (languageId) {
+ if (!values.has(languageId) || values.get(languageId).length === 0) {
+ // input has non-empty value for previously checked language
+ if (hasNonEmptyValue) {
+ return false;
}
- else {
- // input has empty value for previously checked language
- if (hasEmptyValue) {
- return false;
- }
- hasNonEmptyValue = true;
+ hasEmptyValue = true;
+ }
+ else {
+ // input has empty value for previously checked language
+ if (hasEmptyValue) {
+ return false;
}
+ hasNonEmptyValue = true;
}
}
- return (!hasEmptyValue || permitEmptyValue);
- }
- };
+ });
+ return !hasEmptyValue || permitEmptyValue;
+ }
+ exports.validate = validate;
});
+++ /dev/null
-/**
- * I18n interface for input and textarea fields.
- *
- * @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Language/Input
- */
-define(['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traverse', 'Dom/Util', 'Ui/SimpleDropdown'], function(Core, Dictionary, Language, ObjectMap, StringUtil, DomTraverse, DomUtil, UiSimpleDropdown) {
- "use strict";
-
- var _elements = new Dictionary();
- var _didInit = false;
- var _forms = new ObjectMap();
- var _values = new Dictionary();
-
- var _callbackDropdownToggle = null;
- var _callbackSubmit = null;
-
- /**
- * @exports WoltLabSuite/Core/Language/Input
- */
- return {
- /**
- * Initializes an input field.
- *
- * @param {string} elementId input element id
- * @param {Object} values preset values per language id
- * @param {Object} availableLanguages language names per language id
- * @param {boolean} forceSelection require i18n input
- */
- init: function(elementId, values, availableLanguages, forceSelection) {
- if (_values.has(elementId)) {
- return;
- }
-
- var element = elById(elementId);
- if (element === null) {
- throw new Error("Expected a valid element id, cannot find '" + elementId + "'.");
- }
-
- this._setup();
-
- // unescape values
- var unescapedValues = new Dictionary();
- for (var key in values) {
- if (values.hasOwnProperty(key)) {
- unescapedValues.set(~~key, StringUtil.unescapeHTML(values[key]));
- }
- }
-
- _values.set(elementId, unescapedValues);
-
- this._initElement(elementId, element, unescapedValues, availableLanguages, forceSelection);
- },
-
- /**
- * Registers a callback for an element.
- *
- * @param {string} elementId
- * @param {string} eventName
- * @param {function} callback
- */
- registerCallback: function (elementId, eventName, callback) {
- if (!_values.has(elementId)) {
- throw new Error("Unknown element id '" + elementId + "'.");
- }
-
- _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.
- */
- _setup: function() {
- if (_didInit) return;
- _didInit = true;
-
- _callbackDropdownToggle = this._dropdownToggle.bind(this);
- _callbackSubmit = this._submit.bind(this);
- },
-
- /**
- * Sets up DOM and event listeners for an input field.
- *
- * @param {string} elementId input element id
- * @param {Element} element input or textarea element
- * @param {Dictionary} values preset values per language id
- * @param {Object} availableLanguages language names per language id
- * @param {boolean} forceSelection require i18n input
- */
- _initElement: function(elementId, element, values, availableLanguages, forceSelection) {
- var container = element.parentNode;
- if (!container.classList.contains('inputAddon')) {
- container = elCreate('div');
- container.className = 'inputAddon' + (element.nodeName === 'TEXTAREA' ? ' inputAddonTextarea' : '');
- //noinspection JSCheckFunctionSignatures
- elData(container, 'input-id', elementId);
-
- var hasFocus = document.activeElement === element;
-
- // DOM manipulation causes focused element to lose focus
- element.parentNode.insertBefore(container, element);
- container.appendChild(element);
-
- if (hasFocus) {
- element.focus();
- }
- }
-
- container.classList.add('dropdown');
- var button = elCreate('span');
- button.className = 'button dropdownToggle inputPrefix';
-
- var span = elCreate('span');
- span.textContent = Language.get('wcf.global.button.disabledI18n');
-
- button.appendChild(span);
- container.insertBefore(button, element);
-
- var dropdownMenu = elCreate('ul');
- dropdownMenu.className = 'dropdownMenu';
- DomUtil.insertAfter(dropdownMenu, button);
-
- var callbackClick = (function(event, isInit) {
- var languageId = ~~elData(event.currentTarget, 'language-id');
-
- var activeItem = DomTraverse.childByClass(dropdownMenu, 'active');
- if (activeItem !== null) activeItem.classList.remove('active');
-
- if (languageId) event.currentTarget.classList.add('active');
-
- this._select(elementId, languageId, isInit || false);
- }).bind(this);
-
- // build language dropdown
- var listItem;
- for (var languageId in availableLanguages) {
- if (availableLanguages.hasOwnProperty(languageId)) {
- listItem = elCreate('li');
- elData(listItem, 'language-id', languageId);
-
- span = elCreate('span');
- span.textContent = availableLanguages[languageId];
-
- listItem.appendChild(span);
- listItem.addEventListener('click', callbackClick);
- dropdownMenu.appendChild(listItem);
- }
- }
-
- if (forceSelection !== true) {
- listItem = elCreate('li');
- listItem.className = 'dropdownDivider';
- dropdownMenu.appendChild(listItem);
-
- listItem = elCreate('li');
- elData(listItem, 'language-id', 0);
- span = elCreate('span');
- span.textContent = Language.get('wcf.global.button.disabledI18n');
- listItem.appendChild(span);
- listItem.addEventListener('click', callbackClick);
- dropdownMenu.appendChild(listItem);
- }
-
- var activeItem = null;
- if (forceSelection === true || values.size) {
- for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
- //noinspection JSUnresolvedVariable
- if (~~elData(dropdownMenu.children[i], 'language-id') === LANGUAGE_ID) {
- activeItem = dropdownMenu.children[i];
- break;
- }
- }
- }
-
- UiSimpleDropdown.init(button);
- UiSimpleDropdown.registerCallback(container.id, _callbackDropdownToggle);
-
- _elements.set(elementId, {
- buttonLabel: button.children[0],
- callbacks: new Dictionary(),
- element: element,
- languageId: 0,
- isEnabled: true,
- forceSelection: forceSelection
- });
-
- // bind to submit event
- var submit = DomTraverse.parentByTag(element, 'FORM');
- if (submit !== null) {
- submit.addEventListener('submit', _callbackSubmit);
-
- var elementIds = _forms.get(submit);
- if (elementIds === undefined) {
- elementIds = [];
- _forms.set(submit, elementIds);
- }
-
- elementIds.push(elementId);
- }
-
- if (activeItem !== null) {
- callbackClick({ currentTarget: activeItem }, true);
- }
- },
-
- /**
- * Selects a language or non-i18n from the dropdown list.
- *
- * @param {string} elementId input element id
- * @param {int} languageId language id or `0` to disable i18n
- * @param {boolean} isInit triggers pre-selection on init
- */
- _select: function(elementId, languageId, isInit) {
- var data = _elements.get(elementId);
-
- var dropdownMenu = UiSimpleDropdown.getDropdownMenu(data.element.closest('.inputAddon').id);
- var item, label = '';
- for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
- item = dropdownMenu.children[i];
-
- var itemLanguageId = elData(item, 'language-id');
- if (itemLanguageId.length && languageId === ~~itemLanguageId) {
- label = item.children[0].textContent;
- }
- }
-
- // save current value
- if (data.languageId !== languageId) {
- var values = _values.get(elementId);
-
- if (data.languageId) {
- values.set(data.languageId, data.element.value);
- }
-
- if (languageId === 0) {
- _values.set(elementId, new Dictionary());
- }
- else if (data.buttonLabel.classList.contains('active') || isInit === true) {
- data.element.value = (values.has(languageId)) ? values.get(languageId) : '';
- }
-
- // update label
- data.buttonLabel.textContent = label;
- data.buttonLabel.classList[(languageId ? 'add' : 'remove')]('active');
-
- data.languageId = languageId;
- }
-
- if (!isInit) {
- data.element.blur();
- data.element.focus();
- }
-
- if (data.callbacks.has('select')) {
- data.callbacks.get('select')(data.element);
- }
- },
-
- /**
- * Callback for dropdowns being opened, flags items with a missing value for one or more languages.
- *
- * @param {string} containerId dropdown container id
- * @param {string} action toggle action, can be `open` or `close`
- */
- _dropdownToggle: function(containerId, action) {
- if (action !== 'open') {
- return;
- }
-
- var dropdownMenu = UiSimpleDropdown.getDropdownMenu(containerId);
- var elementId = elData(elById(containerId), 'input-id');
- var data = _elements.get(elementId);
- var values = _values.get(elementId);
-
- var item, languageId;
- for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
- item = dropdownMenu.children[i];
- languageId = ~~elData(item, 'language-id');
-
- if (languageId) {
- var hasMissingValue = false;
- if (data.languageId) {
- if (languageId === data.languageId) {
- hasMissingValue = (data.element.value.trim() === '');
- }
- else {
- hasMissingValue = (!values.get(languageId));
- }
- }
-
- item.classList[(hasMissingValue ? 'add' : 'remove')]('missingValue');
- }
- }
- },
-
- /**
- * Inserts hidden fields for i18n input on submit.
- *
- * @param {Object} event event object
- */
- _submit: function(event) {
- var elementIds = _forms.get(event.currentTarget);
-
- var data, elementId, input, values;
- for (var i = 0, length = elementIds.length; i < length; i++) {
- elementId = elementIds[i];
- data = _elements.get(elementId);
- if (data.isEnabled) {
- values = _values.get(elementId);
-
- if (data.callbacks.has('submit')) {
- data.callbacks.get('submit')(data.element);
- }
-
- // update with current value
- if (data.languageId) {
- values.set(data.languageId, data.element.value);
- }
-
- if (values.size) {
- values.forEach(function(value, languageId) {
- input = elCreate('input');
- input.type = 'hidden';
- input.name = elementId + '_i18n[' + languageId + ']';
- input.value = value;
-
- event.currentTarget.appendChild(input);
- });
-
- // remove name attribute to enforce i18n values
- data.element.removeAttribute('name');
- }
- }
- }
- },
-
- /**
- * Returns the values of an input field.
- *
- * @param {string} elementId input element id
- * @return {Dictionary} values stored for the different languages
- */
- getValues: function(elementId) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
-
- var values = _values.get(elementId);
-
- // update with current value
- values.set(element.languageId, element.element.value);
-
- return values;
- },
-
- /**
- * Sets the values of an input field.
- *
- * @param {string} elementId input element id
- * @param {Dictionary} values values for the different languages
- */
- setValues: function(elementId, values) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
-
- if (Core.isPlainObject(values)) {
- values = Dictionary.fromObject(values);
- }
-
- element.element.value = '';
-
- if (values.has(0)) {
- element.element.value = values.get(0);
- values['delete'](0);
- _values.set(elementId, values);
- this._select(elementId, 0, true);
- return;
- }
-
- _values.set(elementId, values);
-
- element.languageId = 0;
- //noinspection JSUnresolvedVariable
- this._select(elementId, LANGUAGE_ID, true);
- },
-
- /**
- * Disables the i18n interface for an input field.
- *
- * @param {string} elementId input element id
- */
- disable: function(elementId) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid element, '" + elementId + "' is not an i18n input field.");
- }
-
- if (!element.isEnabled) return;
-
- element.isEnabled = false;
-
- // hide language dropdown
- //noinspection JSCheckFunctionSignatures
- elHide(element.buttonLabel.parentNode);
- var dropdownContainer = element.buttonLabel.parentNode.parentNode;
- dropdownContainer.classList.remove('inputAddon');
- dropdownContainer.classList.remove('dropdown');
- },
-
- /**
- * Enables the i18n interface for an input field.
- *
- * @param {string} elementId input element id
- */
- enable: function(elementId) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
-
- if (element.isEnabled) return;
-
- element.isEnabled = true;
-
- // show language dropdown
- //noinspection JSCheckFunctionSignatures
- elShow(element.buttonLabel.parentNode);
- var dropdownContainer = element.buttonLabel.parentNode.parentNode;
- dropdownContainer.classList.add('inputAddon');
- dropdownContainer.classList.add('dropdown');
- },
-
- /**
- * Returns true if i18n input is enabled for an input field.
- *
- * @param {string} elementId input element id
- * @return {boolean}
- */
- isEnabled: function(elementId) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
-
- return element.isEnabled;
- },
-
- /**
- * Returns true if the value of an i18n input field is valid.
- *
- * If the element is disabled, true is returned.
- *
- * @param {string} elementId input element id
- * @param {boolean} permitEmptyValue if true, input may be empty for all languages
- * @return {boolean} true if input is valid
- */
- validate: function(elementId, permitEmptyValue) {
- var element = _elements.get(elementId);
- if (element === undefined) {
- throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
- }
-
- if (!element.isEnabled) return true;
-
- var values = _values.get(elementId);
-
- var dropdownMenu = UiSimpleDropdown.getDropdownMenu(element.element.parentNode.id);
-
- if (element.languageId) {
- values.set(element.languageId, element.element.value);
- }
-
- var item, languageId;
- var hasEmptyValue = false, hasNonEmptyValue = false;
- for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
- item = dropdownMenu.children[i];
- languageId = ~~elData(item, 'language-id');
-
- if (languageId) {
- if (!values.has(languageId) || values.get(languageId).length === 0) {
- // input has non-empty value for previously checked language
- if (hasNonEmptyValue) {
- return false;
- }
-
- hasEmptyValue = true;
- }
- else {
- // input has empty value for previously checked language
- if (hasEmptyValue) {
- return false;
- }
-
- hasNonEmptyValue = true;
- }
- }
- }
-
- return (!hasEmptyValue || permitEmptyValue);
- }
- };
-});
--- /dev/null
+/**
+ * I18n interface for input and textarea fields.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Language/Input
+ */
+
+import DomUtil from "../Dom/Util";
+import * as Language from "../Language";
+import { NotificationAction } from "../Ui/Dropdown/Data";
+import UiDropdownSimple from "../Ui/Dropdown/Simple";
+import * as StringUtil from "../StringUtil";
+
+type LanguageId = number;
+
+interface I18nValues {
+ // languageID => value
+ [key: string]: string;
+}
+
+interface Languages {
+ // languageID => languageName
+ [key: string]: string;
+}
+
+type Values = Map<LanguageId, string>;
+
+type InputOrTextarea = HTMLInputElement | HTMLTextAreaElement;
+
+type CallbackEvent = "select" | "submit";
+type Callback = (element: InputOrTextarea) => void;
+
+interface ElementData {
+ buttonLabel: HTMLElement;
+ callbacks: Map<CallbackEvent, Callback>;
+ element: InputOrTextarea;
+ languageId: number;
+ isEnabled: boolean;
+ forceSelection: boolean;
+}
+
+const _elements = new Map<string, ElementData>();
+const _forms = new WeakMap<HTMLFormElement, string[]>();
+const _values = new Map<string, Values>();
+
+/**
+ * Sets up DOM and event listeners for an input field.
+ */
+function initElement(
+ elementId: string,
+ element: InputOrTextarea,
+ values: Values,
+ availableLanguages: Languages,
+ forceSelection: boolean,
+): void {
+ let container = element.parentElement!;
+ if (!container.classList.contains("inputAddon")) {
+ container = document.createElement("div");
+ container.className = "inputAddon";
+ if (element.nodeName === "TEXTAREA") {
+ container.classList.add("inputAddonTextarea");
+ }
+ container.dataset.inputId = elementId;
+
+ const hasFocus = document.activeElement === element;
+
+ // DOM manipulation causes focused element to lose focus
+ element.insertAdjacentElement("beforebegin", container);
+ container.appendChild(element);
+
+ if (hasFocus) {
+ element.focus();
+ }
+ }
+
+ container.classList.add("dropdown");
+ const button = document.createElement("span");
+ button.className = "button dropdownToggle inputPrefix";
+
+ const buttonLabel = document.createElement("span");
+ buttonLabel.textContent = Language.get("wcf.global.button.disabledI18n");
+
+ button.appendChild(buttonLabel);
+ container.insertBefore(button, element);
+
+ const dropdownMenu = document.createElement("ul");
+ dropdownMenu.className = "dropdownMenu";
+ button.insertAdjacentElement("afterend", dropdownMenu);
+
+ const callbackClick = (event: MouseEvent | HTMLElement): void => {
+ let target: HTMLElement;
+ if (event instanceof HTMLElement) {
+ target = event;
+ } else {
+ target = event.currentTarget as HTMLElement;
+ }
+
+ const languageId = ~~target.dataset.languageId!;
+
+ const activeItem = dropdownMenu.querySelector(".active");
+ if (activeItem !== null) {
+ activeItem.classList.remove("active");
+ }
+
+ if (languageId) {
+ target.classList.add("active");
+ }
+
+ const isInit = event instanceof HTMLElement;
+ select(elementId, languageId, isInit);
+ };
+
+ // build language dropdown
+ Object.entries(availableLanguages).forEach(([languageId, languageName]) => {
+ const listItem = document.createElement("li");
+ listItem.dataset.languageId = languageId;
+
+ const span = document.createElement("span");
+ span.textContent = languageName;
+
+ listItem.appendChild(span);
+ listItem.addEventListener("click", callbackClick);
+ dropdownMenu.appendChild(listItem);
+ });
+
+ if (!forceSelection) {
+ const divider = document.createElement("li");
+ divider.className = "dropdownDivider";
+ dropdownMenu.appendChild(divider);
+
+ const listItem = document.createElement("li");
+ listItem.dataset.languageId = "0";
+ listItem.addEventListener("click", callbackClick);
+
+ const span = document.createElement("span");
+ span.textContent = Language.get("wcf.global.button.disabledI18n");
+ listItem.appendChild(span);
+
+ dropdownMenu.appendChild(listItem);
+ }
+
+ let activeItem: HTMLElement | undefined = undefined;
+ if (forceSelection || values.size) {
+ activeItem = Array.from(dropdownMenu.children).find((element: HTMLElement) => {
+ return +element.dataset.languageId! === window.LANGUAGE_ID;
+ }) as HTMLElement;
+ }
+
+ UiDropdownSimple.init(button);
+ UiDropdownSimple.registerCallback(container.id, dropdownToggle);
+
+ _elements.set(elementId, {
+ buttonLabel,
+ callbacks: new Map<CallbackEvent, Callback>(),
+ element,
+ languageId: 0,
+ isEnabled: true,
+ forceSelection,
+ });
+
+ // bind to submit event
+ const form = element.closest("form");
+ if (form !== null) {
+ form.addEventListener("submit", submit);
+
+ let elementIds = _forms.get(form);
+ if (elementIds === undefined) {
+ elementIds = [];
+ _forms.set(form, elementIds);
+ }
+
+ elementIds.push(elementId);
+ }
+
+ if (activeItem) {
+ callbackClick(activeItem);
+ }
+}
+
+/**
+ * Selects a language or non-i18n from the dropdown list.
+ */
+function select(elementId: string, languageId: number, isInit: boolean): void {
+ const data = _elements.get(elementId)!;
+
+ const dropdownMenu = UiDropdownSimple.getDropdownMenu(data.element.closest(".inputAddon")!.id)!;
+
+ const item = dropdownMenu.querySelector(`[data-language-id="${languageId}"]`);
+ const label = item ? item.textContent! : "";
+
+ // save current value
+ if (data.languageId !== languageId) {
+ const values = _values.get(elementId)!;
+
+ if (data.languageId) {
+ values.set(data.languageId, data.element.value);
+ }
+
+ if (languageId === 0) {
+ _values.set(elementId, new Map<LanguageId, string>());
+ } else if (data.buttonLabel.classList.contains("active") || isInit) {
+ data.element.value = values.get(languageId) || "";
+ }
+
+ // update label
+ data.buttonLabel.textContent = label;
+ data.buttonLabel.classList[languageId ? "add" : "remove"]("active");
+
+ data.languageId = languageId;
+ }
+
+ if (!isInit) {
+ data.element.blur();
+ data.element.focus();
+ }
+
+ if (data.callbacks.has("select")) {
+ data.callbacks.get("select")!(data.element);
+ }
+}
+
+/**
+ * Callback for dropdowns being opened, flags items with a missing value for one or more languages.
+ */
+function dropdownToggle(containerId: string, action: NotificationAction): void {
+ if (action !== "open") {
+ return;
+ }
+
+ const dropdownMenu = UiDropdownSimple.getDropdownMenu(containerId)!;
+ const container = document.getElementById(containerId)!;
+ const elementId = container.dataset.inputId!;
+ const data = _elements.get(elementId)!;
+ const values = _values.get(elementId)!;
+
+ Array.from(dropdownMenu.children).forEach((item: HTMLElement) => {
+ const languageId = ~~(item.dataset.languageId || "");
+
+ if (languageId) {
+ let hasMissingValue = false;
+ if (data.languageId) {
+ if (languageId === data.languageId) {
+ hasMissingValue = data.element.value.trim() === "";
+ } else {
+ hasMissingValue = !values.get(languageId);
+ }
+ }
+
+ if (hasMissingValue) {
+ item.classList.add("missingValue");
+ } else {
+ item.classList.remove("missingValue");
+ }
+ }
+ });
+}
+
+/**
+ * Inserts hidden fields for i18n input on submit.
+ */
+function submit(event: Event): void {
+ const form = event.currentTarget as HTMLFormElement;
+ const elementIds = _forms.get(form)!;
+
+ elementIds.forEach((elementId) => {
+ const data = _elements.get(elementId)!;
+ if (!data.isEnabled) {
+ return;
+ }
+
+ const values = _values.get(elementId)!;
+
+ if (data.callbacks.has("submit")) {
+ data.callbacks.get("submit")!(data.element);
+ }
+
+ // update with current value
+ if (data.languageId) {
+ values.set(data.languageId, data.element.value);
+ }
+
+ if (values.size) {
+ values.forEach(function (value, languageId) {
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = `${elementId}_i18n[${languageId}]`;
+ input.value = value;
+
+ form.appendChild(input);
+ });
+
+ // remove name attribute to enforce i18n values
+ data.element.removeAttribute("name");
+ }
+ });
+}
+
+/**
+ * Initializes an input field.
+ */
+export function init(
+ elementId: string,
+ values: I18nValues,
+ availableLanguages: Languages,
+ forceSelection: boolean,
+): void {
+ if (_values.has(elementId)) {
+ return;
+ }
+
+ const element = document.getElementById(elementId) as InputOrTextarea;
+ if (element === null) {
+ throw new Error(`Expected a valid element id, cannot find '${elementId}'.`);
+ }
+
+ // unescape values
+ const unescapedValues = new Map<LanguageId, string>();
+ Object.entries(values).forEach(([languageId, value]) => {
+ unescapedValues.set(+languageId, StringUtil.unescapeHTML(value));
+ });
+
+ _values.set(elementId, unescapedValues);
+
+ initElement(elementId, element, unescapedValues, availableLanguages, forceSelection);
+}
+
+/**
+ * Registers a callback for an element.
+ */
+export function registerCallback(elementId: string, eventName: CallbackEvent, callback: Callback): void {
+ if (!_values.has(elementId)) {
+ throw new Error(`Unknown element id '${elementId}'.`);
+ }
+
+ _elements.get(elementId)!.callbacks.set(eventName, callback);
+}
+
+/**
+ * Unregisters the element with the given id.
+ *
+ * @since 5.2
+ */
+export function unregister(elementId: string): void {
+ if (!_values.has(elementId)) {
+ throw new Error(`Unknown element id '${elementId}'.`);
+ }
+
+ _values.delete(elementId);
+ _elements.delete(elementId);
+}
+
+/**
+ * Returns the values of an input field.
+ */
+export function getValues(elementId: string): Values {
+ const element = _elements.get(elementId)!;
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+
+ const values = _values.get(elementId)!;
+
+ // update with current value
+ values.set(element.languageId, element.element.value);
+
+ return values;
+}
+
+/**
+ * Sets the values of an input field.
+ */
+export function setValues(elementId: string, newValues: Values | I18nValues): void {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+
+ element.element.value = "";
+
+ const values = new Map<LanguageId, string>(
+ Object.entries(newValues).map(([languageId, value]) => {
+ return [+languageId, value];
+ }),
+ );
+
+ if (values.has(0)) {
+ element.element.value = values.get(0)!;
+ values.delete(0);
+
+ _values.set(elementId, values);
+ select(elementId, 0, true);
+
+ return;
+ }
+
+ _values.set(elementId, values);
+
+ element.languageId = 0;
+ select(elementId, window.LANGUAGE_ID, true);
+}
+
+/**
+ * Disables the i18n interface for an input field.
+ */
+export function disable(elementId: string): void {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid element, '${elementId}' is not an i18n input field.`);
+ }
+
+ if (!element.isEnabled) {
+ return;
+ }
+
+ element.isEnabled = false;
+
+ // hide language dropdown
+ const buttonContainer = element.buttonLabel.parentElement!;
+ DomUtil.hide(buttonContainer);
+ const dropdownContainer = buttonContainer.parentElement!;
+ dropdownContainer.classList.remove("inputAddon", "dropdown");
+}
+
+/**
+ * Enables the i18n interface for an input field.
+ */
+export function enable(elementId: string): void {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+
+ if (element.isEnabled) {
+ return;
+ }
+
+ element.isEnabled = true;
+
+ // show language dropdown
+ const buttonContainer = element.buttonLabel.parentElement!;
+ DomUtil.show(buttonContainer);
+ const dropdownContainer = buttonContainer.parentElement!;
+ dropdownContainer.classList.add("inputAddon", "dropdown");
+}
+
+/**
+ * Returns true if i18n input is enabled for an input field.
+ */
+export function isEnabled(elementId: string): boolean {
+ const element = _elements.get(elementId);
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+
+ return element.isEnabled;
+}
+
+/**
+ * Returns true if the value of an i18n input field is valid.
+ *
+ * If the element is disabled, true is returned.
+ */
+export function validate(elementId: string, permitEmptyValue: boolean): boolean {
+ const element = _elements.get(elementId)!;
+ if (element === undefined) {
+ throw new Error(`Expected a valid i18n input element, '${elementId}' is not i18n input field.`);
+ }
+
+ if (!element.isEnabled) {
+ return true;
+ }
+
+ const values = _values.get(elementId)!;
+
+ const dropdownMenu = UiDropdownSimple.getDropdownMenu(element.element.parentElement!.id)!;
+
+ if (element.languageId) {
+ values.set(element.languageId, element.element.value);
+ }
+
+ let hasEmptyValue = false;
+ let hasNonEmptyValue = false;
+ Array.from(dropdownMenu.children).forEach((item: HTMLElement) => {
+ const languageId = ~~item.dataset.languageId!;
+
+ if (languageId) {
+ if (!values.has(languageId) || values.get(languageId)!.length === 0) {
+ // input has non-empty value for previously checked language
+ if (hasNonEmptyValue) {
+ return false;
+ }
+
+ hasEmptyValue = true;
+ } else {
+ // input has empty value for previously checked language
+ if (hasEmptyValue) {
+ return false;
+ }
+
+ hasNonEmptyValue = true;
+ }
+ }
+ });
+
+ return !hasEmptyValue || permitEmptyValue;
+}