From f29da777f98e597bdc6888e9e6e7b332d5aebd72 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Tue, 12 Jan 2021 10:57:15 +0100 Subject: [PATCH] Convert `Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList` to TypeScript --- .../Devtools/Project/AbstractPackageList.js | 313 +++++++---------- .../Devtools/Project/AbstractPackageList.js | 322 ------------------ .../Devtools/Project/AbstractPackageList.ts | 263 ++++++++++++++ 3 files changed, 380 insertions(+), 518 deletions(-) delete mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.js create mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.ts diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.js index 94403ad4ff..05a7136136 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.js @@ -1,272 +1,193 @@ /** - * Abstract implementation of the JavaScript component of a form field handling - * a list of packages. + * Abstract implementation of the JavaScript component of a form field handling a list of packages. * - * @author Matthias Schmidt - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList - * @since 5.2 + * @author Matthias Schmidt + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList + * @since 5.2 */ -define(['Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language'], function (DomChangeListener, DomTraverse, DomUtil, EventKey, Language) { +define(["require", "exports", "tslib", "../../../../../../Core", "../../../../../../Language", "../../../../../../Dom/Traverse", "../../../../../../Dom/Change/Listener", "../../../../../../Dom/Util"], function (require, exports, tslib_1, Core, Language, DomTraverse, Listener_1, Util_1) { "use strict"; - /** - * @constructor - */ - function AbstractPackageList(formFieldId, existingPackages) { - this.init(formFieldId, existingPackages); - } - ; - AbstractPackageList.prototype = { - /** - * Initializes the package list handler. - * - * @param {string} formFieldId id of the associated form field - * @param {object[]} existingPackages data of existing packages - */ - init: function (formFieldId, existingPackages) { - this._formFieldId = formFieldId; - this._packageList = elById(this._formFieldId + '_packageList'); - if (this._packageList === null) { - throw new Error("Cannot find package list for packages field with id '" + this._formFieldId + "'."); - } - this._packageIdentifier = elById(this._formFieldId + '_packageIdentifier'); - if (this._packageIdentifier === null) { - throw new Error("Cannot find package identifier form field for packages field with id '" + this._formFieldId + "'."); - } - this._packageIdentifier.addEventListener('keypress', this._keyPress.bind(this)); - this._addButton = elById(this._formFieldId + '_addButton'); - if (this._addButton === null) { - throw new Error("Cannot find add button for packages field with id '" + this._formFieldId + "'."); - } - this._addButton.addEventListener('click', this._addPackage.bind(this)); - this._form = this._packageList.closest('form'); - if (this._form === null) { - throw new Error("Cannot find form element for packages field with id '" + this._formFieldId + "'."); - } - this._form.addEventListener('submit', this._submit.bind(this)); - existingPackages.forEach(this._addPackageByData.bind(this)); - }, + Core = tslib_1.__importStar(Core); + Language = tslib_1.__importStar(Language); + DomTraverse = tslib_1.__importStar(DomTraverse); + Listener_1 = tslib_1.__importDefault(Listener_1); + Util_1 = tslib_1.__importDefault(Util_1); + class AbstractPackageList { + constructor(formFieldId, existingPackages) { + this.formFieldId = formFieldId; + this.packageList = document.getElementById(`${this.formFieldId}_packageList`); + if (this.packageList === null) { + throw new Error(`Cannot find package list for packages field with id '${this.formFieldId}'.`); + } + this.packageIdentifier = document.getElementById(`${this.formFieldId}_packageIdentifier`); + if (this.packageIdentifier === null) { + throw new Error(`Cannot find package identifier form field for packages field with id '${this.formFieldId}'.`); + } + this.packageIdentifier.addEventListener("keypress", (ev) => this.keyPress(ev)); + this.addButton = document.getElementById(`${this.formFieldId}_addButton`); + if (this.addButton === null) { + throw new Error(`Cannot find add button for packages field with id '${this.formFieldId}'.`); + } + this.addButton.addEventListener("click", (ev) => this.addPackage(ev)); + this.form = this.packageList.closest("form"); + if (this.form === null) { + throw new Error(`Cannot find form element for packages field with id '${this.formFieldId}'.`); + } + this.form.addEventListener("submit", () => this.submit()); + existingPackages.forEach((data) => this.addPackageByData(data)); + } /** - * Adds a package to the package list as a consequence of the given - * event. If the package data is invalid, an error message is shown - * and no package is added. + * Adds a package to the package list as a consequence of the given event. * - * @param {Event} event event that triggered trying to add the package + * If the package data is invalid, an error message is shown and no package is added. */ - _addPackage: function (event) { + addPackage(event) { event.preventDefault(); event.stopPropagation(); // validate data - if (!this._validateInput()) { + if (!this.validateInput()) { return; } - this._addPackageByData(this._getInputData()); + this.addPackageByData(this.getInputData()); // empty fields - this._emptyInput(); - this._packageIdentifier.focus(); - }, + this.emptyInput(); + this.packageIdentifier.focus(); + } /** * Adds a package to the package list using the given package data. - * - * @param {object} packageData */ - _addPackageByData: function (packageData) { + addPackageByData(packageData) { // add package to list - var listItem = elCreate('li'); - this._populateListItem(listItem, packageData); + const listItem = document.createElement("li"); + this.populateListItem(listItem, packageData); // add delete button - var deleteButton = elCreate('span'); - deleteButton.className = 'icon icon16 fa-times pointer jsTooltip'; - elAttr(deleteButton, 'title', Language.get('wcf.global.button.delete')); - deleteButton.addEventListener('click', this._removePackage.bind(this)); - DomUtil.prepend(deleteButton, listItem); - this._packageList.appendChild(listItem); - DomChangeListener.trigger(); - }, + const deleteButton = document.createElement("span"); + deleteButton.className = "icon icon16 fa-times pointer jsTooltip"; + deleteButton.title = Language.get("wcf.global.button.delete"); + deleteButton.addEventListener("click", (ev) => this.removePackage(ev)); + listItem.insertAdjacentElement("afterbegin", deleteButton); + this.packageList.appendChild(listItem); + Listener_1.default.trigger(); + } /** * Creates the hidden fields when the form is submitted. - * - * @param {HTMLElement} listElement package list element from the package list - * @param {int} index package index */ - _createSubmitFields: function (listElement, index) { - var packageIdentifier = elCreate('input'); - elAttr(packageIdentifier, 'type', 'hidden'); - elAttr(packageIdentifier, 'name', this._formFieldId + '[' + index + '][packageIdentifier]'); - packageIdentifier.value = elData(listElement, 'package-identifier'); - this._form.appendChild(packageIdentifier); - }, + createSubmitFields(listElement, index) { + const packageIdentifier = document.createElement("input"); + packageIdentifier.type = "hidden"; + packageIdentifier.name = `${this.formFieldId}[${index}][packageIdentifier]`; + packageIdentifier.value = listElement.dataset.packageIdentifier; + this.form.appendChild(packageIdentifier); + } /** * Empties the input fields. */ - _emptyInput: function () { - this._packageIdentifier.value = ''; - }, - /** - * Returns the error element for the given form field element. - * If `createIfNonExistent` is not given or `false`, `null` is returned - * if there is no error element, otherwise an empty error element - * is created and returned. - * - * @param {?boolean} createIfNonExistent - * @return {?HTMLElement} - */ - _getErrorElement: function (element, createIfNoNExistent) { - var error = DomTraverse.nextByClass(element, 'innerError'); - if (error === null && createIfNoNExistent) { - error = elCreate('small'); - error.className = 'innerError'; - DomUtil.insertAfter(error, element); - } - return error; - }, + emptyInput() { + this.packageIdentifier.value = ""; + } /** * Returns the current data of the input fields to add a new package. - * - * @return {object} */ - _getInputData: function () { + getInputData() { return { - packageIdentifier: this._packageIdentifier.value + packageIdentifier: this.packageIdentifier.value, }; - }, - /** - * Returns the error element for the package identifier form field. - * If `createIfNonExistent` is not given or `false`, `null` is returned - * if there is no error element, otherwise an empty error element - * is created and returned. - * - * @param {?boolean} createIfNonExistent - * @return {?HTMLElement} - */ - _getPackageIdentifierErrorElement: function (createIfNonExistent) { - return this._getErrorElement(this._packageIdentifier, createIfNonExistent); - }, + } /** - * Adds a package to the package list after pressing ENTER in a - * text field. - * - * @param {Event} event + * Adds a package to the package list after pressing ENTER in a text field. */ - _keyPress: function (event) { - if (EventKey.Enter(event)) { - this._addPackage(event); + keyPress(event) { + if (event.key === "Enter") { + this.addPackage(event); } - }, + } /** * Adds all necessary package-relavant data to the given list item. - * - * @param {HTMLElement} listItem package list element holding package data - * @param {object} packageData package data */ - _populateListItem: function (listItem, packageData) { - elData(listItem, 'package-identifier', packageData.packageIdentifier); - }, + populateListItem(listItem, packageData) { + listItem.dataset.packageIdentifier = packageData.packageIdentifier; + } /** * Removes a package by clicking on its delete button. - * - * @param {Event} event delete button click event */ - _removePackage: function (event) { - elRemove(event.currentTarget.closest('li')); + removePackage(event) { + event.currentTarget.closest("li").remove(); // remove field errors if the last package has been deleted - if (!this._packageList.childElementCount && - this._packageList.nextElementSibling.tagName === 'SMALL' && - this._packageList.nextElementSibling.classList.contains('innerError')) { - elRemove(this._packageList.nextElementSibling); - } - }, + Util_1.default.innerError(this.packageList, ""); + } /** - * Adds all necessary (hidden) form fields to the form when - * submitting the form. + * Adds all necessary (hidden) form fields to the form when submitting the form. */ - _submit: function () { - DomTraverse.childrenByTag(this._packageList, 'LI').forEach(this._createSubmitFields.bind(this)); - }, + submit() { + DomTraverse.childrenByTag(this.packageList, "LI").forEach((listItem, index) => this.createSubmitFields(listItem, index)); + } /** - * Returns `true` if the currently entered package data is valid. - * Otherwise `false` is returned and relevant error messages are - * shown. - * - * @return {boolean} + * Returns `true` if the currently entered package data is valid. Otherwise `false` is returned and relevant error + * messages are shown. */ - _validateInput: function () { - return this._validatePackageIdentifier(); - }, + validateInput() { + return this.validatePackageIdentifier(); + } /** - * Returns `true` if the currently entered package identifier is - * valid. Otherwise `false` is returned and an error message is - * shown. - * - * @return {boolean} + * Returns `true` if the currently entered package identifier is valid. Otherwise `false` is returned and an error + * message is shown. */ - _validatePackageIdentifier: function () { - var packageIdentifier = this._packageIdentifier.value; - if (packageIdentifier === '') { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.global.form.error.empty'); + validatePackageIdentifier() { + const packageIdentifier = this.packageIdentifier.value; + if (packageIdentifier === "") { + Util_1.default.innerError(this.packageIdentifier, Language.get("wcf.global.form.error.empty")); return false; } if (packageIdentifier.length < 3) { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.minimumLength'); + Util_1.default.innerError(this.packageIdentifier, Language.get("wcf.acp.devtools.project.packageIdentifier.error.minimumLength")); return false; } else if (packageIdentifier.length > 191) { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.maximumLength'); + Util_1.default.innerError(this.packageIdentifier, Language.get("wcf.acp.devtools.project.packageIdentifier.error.maximumLength")); return false; } - // see `wcf\data\package\Package::isValidPackageName()` - if (!packageIdentifier.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/)) { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.format'); + if (!AbstractPackageList.packageIdentifierRegExp.test(packageIdentifier)) { + Util_1.default.innerError(this.packageIdentifier, Language.get("wcf.acp.devtools.project.packageIdentifier.error.format")); return false; } // check if package has already been added - var duplicate = false; - DomTraverse.childrenByTag(this._packageList, 'LI').forEach(function (listItem, index) { - if (elData(listItem, 'package-identifier') === packageIdentifier) { - duplicate = true; - } - }); + const duplicate = DomTraverse.childrenByTag(this.packageList, "LI").some((listItem) => listItem.dataset.packageIdentifier === packageIdentifier); if (duplicate) { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.duplicate'); + Util_1.default.innerError(this.packageIdentifier, Language.get("wcf.acp.devtools.project.packageIdentifier.error.duplicate")); return false; } // remove outdated errors - var error = this._getPackageIdentifierErrorElement(); - if (error !== null) { - elRemove(error); - } + Util_1.default.innerError(this.packageIdentifier, ""); return true; - }, + } /** - * Returns `true` if the given version is valid. Otherwise `false` - * is returned and an error message is shown. - * - * @param {string} version validated version - * @param {function} versionErrorGetter returns the version error element - * @return {boolean} + * Returns `true` if the given version is valid. Otherwise `false` is returned and an error message is shown. */ - _validateVersion: function (version, versionErrorGetter) { + validateVersion(versionElement) { + const version = versionElement.value; // see `wcf\data\package\Package::isValidVersion()` // the version is no a required attribute - if (version !== '') { + if (version !== "") { if (version.length > 255) { - versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.maximumLength'); + Util_1.default.innerError(versionElement, Language.get("wcf.acp.devtools.project.packageVersion.error.maximumLength")); return false; } - // see `wcf\data\package\Package::isValidVersion()` - if (!version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) { - versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format'); + if (!AbstractPackageList.versionRegExp.test(version)) { + Util_1.default.innerError(versionElement, Language.get("wcf.acp.devtools.project.packageVersion.error.format")); return false; } } // remove outdated errors - var error = versionErrorGetter(); - if (error !== null) { - elRemove(error); - } + Util_1.default.innerError(versionElement, ""); return true; } - }; + } + // see `wcf\data\package\Package::isValidPackageName()` + AbstractPackageList.packageIdentifierRegExp = new RegExp(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/); + // see `wcf\data\package\Package::isValidVersion()` + AbstractPackageList.versionRegExp = new RegExp(/^([0-9]+).([0-9]+)\.([0-9]+)( (a|alpha|b|beta|d|dev|rc|pl) ([0-9]+))?$/i); + Core.enableLegacyInheritance(AbstractPackageList); return AbstractPackageList; }); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.js deleted file mode 100644 index a6ac35a10e..0000000000 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.js +++ /dev/null @@ -1,322 +0,0 @@ -/** - * Abstract implementation of the JavaScript component of a form field handling - * a list of packages. - * - * @author Matthias Schmidt - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList - * @since 5.2 - */ -define(['Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language'], function(DomChangeListener, DomTraverse, DomUtil, EventKey, Language) { - "use strict"; - - /** - * @constructor - */ - function AbstractPackageList(formFieldId, existingPackages) { - this.init(formFieldId, existingPackages); - }; - AbstractPackageList.prototype = { - /** - * Initializes the package list handler. - * - * @param {string} formFieldId id of the associated form field - * @param {object[]} existingPackages data of existing packages - */ - init: function(formFieldId, existingPackages) { - this._formFieldId = formFieldId; - - this._packageList = elById(this._formFieldId + '_packageList'); - if (this._packageList === null) { - throw new Error("Cannot find package list for packages field with id '" + this._formFieldId + "'."); - } - - this._packageIdentifier = elById(this._formFieldId + '_packageIdentifier'); - if (this._packageIdentifier === null) { - throw new Error("Cannot find package identifier form field for packages field with id '" + this._formFieldId + "'."); - } - this._packageIdentifier.addEventListener('keypress', this._keyPress.bind(this)); - - this._addButton = elById(this._formFieldId + '_addButton'); - if (this._addButton === null) { - throw new Error("Cannot find add button for packages field with id '" + this._formFieldId + "'."); - } - this._addButton.addEventListener('click', this._addPackage.bind(this)); - - this._form = this._packageList.closest('form'); - if (this._form === null) { - throw new Error("Cannot find form element for packages field with id '" + this._formFieldId + "'."); - } - this._form.addEventListener('submit', this._submit.bind(this)); - - existingPackages.forEach(this._addPackageByData.bind(this)); - }, - - /** - * Adds a package to the package list as a consequence of the given - * event. If the package data is invalid, an error message is shown - * and no package is added. - * - * @param {Event} event event that triggered trying to add the package - */ - _addPackage: function(event) { - event.preventDefault(); - event.stopPropagation(); - - // validate data - if (!this._validateInput()) { - return; - } - - this._addPackageByData(this._getInputData()); - - // empty fields - this._emptyInput(); - - this._packageIdentifier.focus(); - }, - - /** - * Adds a package to the package list using the given package data. - * - * @param {object} packageData - */ - _addPackageByData: function(packageData) { - // add package to list - var listItem = elCreate('li'); - this._populateListItem(listItem, packageData); - - // add delete button - var deleteButton = elCreate('span'); - deleteButton.className = 'icon icon16 fa-times pointer jsTooltip'; - elAttr(deleteButton, 'title', Language.get('wcf.global.button.delete')); - deleteButton.addEventListener('click', this._removePackage.bind(this)); - DomUtil.prepend(deleteButton, listItem); - - this._packageList.appendChild(listItem); - - DomChangeListener.trigger(); - }, - - /** - * Creates the hidden fields when the form is submitted. - * - * @param {HTMLElement} listElement package list element from the package list - * @param {int} index package index - */ - _createSubmitFields: function(listElement, index) { - var packageIdentifier = elCreate('input'); - elAttr(packageIdentifier, 'type', 'hidden'); - elAttr(packageIdentifier, 'name', this._formFieldId + '[' + index + '][packageIdentifier]') - packageIdentifier.value = elData(listElement, 'package-identifier'); - this._form.appendChild(packageIdentifier); - }, - - /** - * Empties the input fields. - */ - _emptyInput: function() { - this._packageIdentifier.value = ''; - }, - - /** - * Returns the error element for the given form field element. - * If `createIfNonExistent` is not given or `false`, `null` is returned - * if there is no error element, otherwise an empty error element - * is created and returned. - * - * @param {?boolean} createIfNonExistent - * @return {?HTMLElement} - */ - _getErrorElement: function(element, createIfNoNExistent) { - var error = DomTraverse.nextByClass(element, 'innerError'); - - if (error === null && createIfNoNExistent) { - error = elCreate('small'); - error.className = 'innerError'; - - DomUtil.insertAfter(error, element); - } - - return error; - }, - - /** - * Returns the current data of the input fields to add a new package. - * - * @return {object} - */ - _getInputData: function() { - return { - packageIdentifier: this._packageIdentifier.value - }; - }, - - /** - * Returns the error element for the package identifier form field. - * If `createIfNonExistent` is not given or `false`, `null` is returned - * if there is no error element, otherwise an empty error element - * is created and returned. - * - * @param {?boolean} createIfNonExistent - * @return {?HTMLElement} - */ - _getPackageIdentifierErrorElement: function(createIfNonExistent) { - return this._getErrorElement(this._packageIdentifier, createIfNonExistent); - }, - - /** - * Adds a package to the package list after pressing ENTER in a - * text field. - * - * @param {Event} event - */ - _keyPress: function(event) { - if (EventKey.Enter(event)) { - this._addPackage(event); - } - }, - - /** - * Adds all necessary package-relavant data to the given list item. - * - * @param {HTMLElement} listItem package list element holding package data - * @param {object} packageData package data - */ - _populateListItem: function(listItem, packageData) { - elData(listItem, 'package-identifier', packageData.packageIdentifier); - }, - - /** - * Removes a package by clicking on its delete button. - * - * @param {Event} event delete button click event - */ - _removePackage: function(event) { - elRemove(event.currentTarget.closest('li')); - - // remove field errors if the last package has been deleted - if ( - !this._packageList.childElementCount && - this._packageList.nextElementSibling.tagName === 'SMALL' && - this._packageList.nextElementSibling.classList.contains('innerError') - ) { - elRemove(this._packageList.nextElementSibling); - } - }, - - /** - * Adds all necessary (hidden) form fields to the form when - * submitting the form. - */ - _submit: function() { - DomTraverse.childrenByTag(this._packageList, 'LI').forEach(this._createSubmitFields.bind(this)); - }, - - /** - * Returns `true` if the currently entered package data is valid. - * Otherwise `false` is returned and relevant error messages are - * shown. - * - * @return {boolean} - */ - _validateInput: function() { - return this._validatePackageIdentifier(); - }, - - /** - * Returns `true` if the currently entered package identifier is - * valid. Otherwise `false` is returned and an error message is - * shown. - * - * @return {boolean} - */ - _validatePackageIdentifier: function() { - var packageIdentifier = this._packageIdentifier.value; - - if (packageIdentifier === '') { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.global.form.error.empty'); - - return false; - } - - if (packageIdentifier.length < 3) { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.minimumLength'); - - return false; - } - else if (packageIdentifier.length > 191) { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.maximumLength'); - - return false; - } - - // see `wcf\data\package\Package::isValidPackageName()` - if (!packageIdentifier.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/)) { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.format'); - - return false; - } - - // check if package has already been added - var duplicate = false; - DomTraverse.childrenByTag(this._packageList, 'LI').forEach(function(listItem, index) { - if (elData(listItem, 'package-identifier') === packageIdentifier) { - duplicate = true; - } - }); - - if (duplicate) { - this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.duplicate'); - - return false; - } - - // remove outdated errors - var error = this._getPackageIdentifierErrorElement(); - if (error !== null) { - elRemove(error); - } - - return true; - }, - - /** - * Returns `true` if the given version is valid. Otherwise `false` - * is returned and an error message is shown. - * - * @param {string} version validated version - * @param {function} versionErrorGetter returns the version error element - * @return {boolean} - */ - _validateVersion: function(version, versionErrorGetter) { - // see `wcf\data\package\Package::isValidVersion()` - // the version is no a required attribute - if (version !== '') { - if (version.length > 255) { - versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.maximumLength'); - - return false; - } - - // see `wcf\data\package\Package::isValidVersion()` - if (!version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) { - versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format'); - - return false; - } - } - - // remove outdated errors - var error = versionErrorGetter(); - if (error !== null) { - elRemove(error); - } - - return true; - } - }; - - return AbstractPackageList; -}); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.ts new file mode 100644 index 0000000000..955a36a22f --- /dev/null +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList.ts @@ -0,0 +1,263 @@ +/** + * Abstract implementation of the JavaScript component of a form field handling a list of packages. + * + * @author Matthias Schmidt + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/AbstractPackageList + * @since 5.2 + */ + +import * as Core from "../../../../../../Core"; +import * as Language from "../../../../../../Language"; +import * as DomTraverse from "../../../../../../Dom/Traverse"; +import DomChangeListener from "../../../../../../Dom/Change/Listener"; +import DomUtil from "../../../../../../Dom/Util"; +import { PackageData } from "./Data"; + +abstract class AbstractPackageList { + protected readonly addButton: HTMLAnchorElement; + protected readonly form: HTMLFormElement; + protected readonly formFieldId: string; + protected readonly packageList: HTMLOListElement; + protected readonly packageIdentifier: HTMLInputElement; + + // see `wcf\data\package\Package::isValidPackageName()` + protected static packageIdentifierRegExp = new RegExp(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/); + + // see `wcf\data\package\Package::isValidVersion()` + protected static versionRegExp = new RegExp( + /^([0-9]+).([0-9]+)\.([0-9]+)( (a|alpha|b|beta|d|dev|rc|pl) ([0-9]+))?$/i, + ); + + constructor(formFieldId: string, existingPackages: TPackageData[]) { + this.formFieldId = formFieldId; + + this.packageList = document.getElementById(`${this.formFieldId}_packageList`) as HTMLOListElement; + if (this.packageList === null) { + throw new Error(`Cannot find package list for packages field with id '${this.formFieldId}'.`); + } + + this.packageIdentifier = document.getElementById(`${this.formFieldId}_packageIdentifier`) as HTMLInputElement; + if (this.packageIdentifier === null) { + throw new Error(`Cannot find package identifier form field for packages field with id '${this.formFieldId}'.`); + } + this.packageIdentifier.addEventListener("keypress", (ev) => this.keyPress(ev)); + + this.addButton = document.getElementById(`${this.formFieldId}_addButton`) as HTMLAnchorElement; + if (this.addButton === null) { + throw new Error(`Cannot find add button for packages field with id '${this.formFieldId}'.`); + } + this.addButton.addEventListener("click", (ev) => this.addPackage(ev)); + + this.form = this.packageList.closest("form") as HTMLFormElement; + if (this.form === null) { + throw new Error(`Cannot find form element for packages field with id '${this.formFieldId}'.`); + } + this.form.addEventListener("submit", () => this.submit()); + + existingPackages.forEach((data) => this.addPackageByData(data)); + } + + /** + * Adds a package to the package list as a consequence of the given event. + * + * If the package data is invalid, an error message is shown and no package is added. + */ + protected addPackage(event: Event): void { + event.preventDefault(); + event.stopPropagation(); + + // validate data + if (!this.validateInput()) { + return; + } + + this.addPackageByData(this.getInputData()); + + // empty fields + this.emptyInput(); + + this.packageIdentifier.focus(); + } + + /** + * Adds a package to the package list using the given package data. + */ + protected addPackageByData(packageData: TPackageData): void { + // add package to list + const listItem = document.createElement("li"); + this.populateListItem(listItem, packageData); + + // add delete button + const deleteButton = document.createElement("span"); + deleteButton.className = "icon icon16 fa-times pointer jsTooltip"; + deleteButton.title = Language.get("wcf.global.button.delete"); + deleteButton.addEventListener("click", (ev) => this.removePackage(ev)); + listItem.insertAdjacentElement("afterbegin", deleteButton); + + this.packageList.appendChild(listItem); + + DomChangeListener.trigger(); + } + + /** + * Creates the hidden fields when the form is submitted. + */ + protected createSubmitFields(listElement: HTMLLIElement, index: number): void { + const packageIdentifier = document.createElement("input"); + packageIdentifier.type = "hidden"; + packageIdentifier.name = `${this.formFieldId}[${index}][packageIdentifier]`; + packageIdentifier.value = listElement.dataset.packageIdentifier!; + this.form.appendChild(packageIdentifier); + } + + /** + * Empties the input fields. + */ + protected emptyInput(): void { + this.packageIdentifier.value = ""; + } + + /** + * Returns the current data of the input fields to add a new package. + */ + protected getInputData(): TPackageData { + return { + packageIdentifier: this.packageIdentifier.value, + } as TPackageData; + } + + /** + * Adds a package to the package list after pressing ENTER in a text field. + */ + protected keyPress(event: KeyboardEvent): void { + if (event.key === "Enter") { + this.addPackage(event); + } + } + + /** + * Adds all necessary package-relavant data to the given list item. + */ + protected populateListItem(listItem: HTMLLIElement, packageData: TPackageData): void { + listItem.dataset.packageIdentifier = packageData.packageIdentifier; + } + + /** + * Removes a package by clicking on its delete button. + */ + protected removePackage(event: Event): void { + (event.currentTarget as HTMLElement).closest("li")!.remove(); + + // remove field errors if the last package has been deleted + DomUtil.innerError(this.packageList, ""); + } + + /** + * Adds all necessary (hidden) form fields to the form when submitting the form. + */ + protected submit(): void { + DomTraverse.childrenByTag(this.packageList, "LI").forEach((listItem, index) => + this.createSubmitFields(listItem, index), + ); + } + + /** + * Returns `true` if the currently entered package data is valid. Otherwise `false` is returned and relevant error + * messages are shown. + */ + protected validateInput(): boolean { + return this.validatePackageIdentifier(); + } + + /** + * Returns `true` if the currently entered package identifier is valid. Otherwise `false` is returned and an error + * message is shown. + */ + protected validatePackageIdentifier(): boolean { + const packageIdentifier = this.packageIdentifier.value; + + if (packageIdentifier === "") { + DomUtil.innerError(this.packageIdentifier, Language.get("wcf.global.form.error.empty")); + + return false; + } + + if (packageIdentifier.length < 3) { + DomUtil.innerError( + this.packageIdentifier, + Language.get("wcf.acp.devtools.project.packageIdentifier.error.minimumLength"), + ); + + return false; + } else if (packageIdentifier.length > 191) { + DomUtil.innerError( + this.packageIdentifier, + Language.get("wcf.acp.devtools.project.packageIdentifier.error.maximumLength"), + ); + + return false; + } + + if (!AbstractPackageList.packageIdentifierRegExp.test(packageIdentifier)) { + DomUtil.innerError( + this.packageIdentifier, + Language.get("wcf.acp.devtools.project.packageIdentifier.error.format"), + ); + + return false; + } + + // check if package has already been added + const duplicate = DomTraverse.childrenByTag(this.packageList, "LI").some( + (listItem) => listItem.dataset.packageIdentifier === packageIdentifier, + ); + + if (duplicate) { + DomUtil.innerError( + this.packageIdentifier, + Language.get("wcf.acp.devtools.project.packageIdentifier.error.duplicate"), + ); + + return false; + } + + // remove outdated errors + DomUtil.innerError(this.packageIdentifier, ""); + + return true; + } + + /** + * Returns `true` if the given version is valid. Otherwise `false` is returned and an error message is shown. + */ + protected validateVersion(versionElement: HTMLInputElement): boolean { + const version = versionElement.value; + + // see `wcf\data\package\Package::isValidVersion()` + // the version is no a required attribute + if (version !== "") { + if (version.length > 255) { + DomUtil.innerError(versionElement, Language.get("wcf.acp.devtools.project.packageVersion.error.maximumLength")); + + return false; + } + + if (!AbstractPackageList.versionRegExp.test(version)) { + DomUtil.innerError(versionElement, Language.get("wcf.acp.devtools.project.packageVersion.error.format")); + + return false; + } + } + + // remove outdated errors + DomUtil.innerError(versionElement, ""); + + return true; + } +} + +Core.enableLegacyInheritance(AbstractPackageList); + +export = AbstractPackageList; -- 2.20.1