2 * Abstract implementation of the JavaScript component of a form field handling
5 * @author Matthias Schmidt
6 * @copyright 2001-2018 WoltLab GmbH
7 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
8 * @module WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
11 define(['Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language'], function(DomChangeListener, DomTraverse, DomUtil, EventKey, Language) {
17 function AbstractPackageList(formFieldId, existingPackages) {
18 this.init(formFieldId, existingPackages);
20 AbstractPackageList.prototype = {
22 * Initializes the package list handler.
24 * @param {string} formFieldId id of the associated form field
25 * @param {object[]} existingPackages data of existing packages
27 init: function(formFieldId, existingPackages) {
28 this._formFieldId = formFieldId;
30 this._packageList = elById(this._formFieldId + '_packageList');
31 if (this._packageList === null) {
32 throw new Error("Cannot find package list for packages field with id '" + this._formFieldId + "'.");
35 this._packageIdentifier = elById(this._formFieldId + '_packageIdentifier');
36 if (this._packageIdentifier === null) {
37 throw new Error("Cannot find package identifier form field for packages field with id '" + this._formFieldId + "'.");
39 this._packageIdentifier.addEventListener('keypress', this._keyPress.bind(this));
41 this._addButton = elById(this._formFieldId + '_addButton');
42 if (this._addButton === null) {
43 throw new Error("Cannot find add button for packages field with id '" + this._formFieldId + "'.");
45 this._addButton.addEventListener('click', this._addPackage.bind(this));
47 this._form = this._packageList.closest('form');
48 if (this._form === null) {
49 throw new Error("Cannot find form element for packages field with id '" + this._formFieldId + "'.");
51 this._form.addEventListener('submit', this._submit.bind(this));
53 existingPackages.forEach(this._addPackageByData.bind(this));
57 * Adds a package to the package list as a consequence of the given
58 * event. If the package data is invalid, an error message is shown
59 * and no package is added.
61 * @param {Event} event event that triggered trying to add the package
63 _addPackage: function(event) {
64 event.preventDefault();
65 event.stopPropagation();
68 if (!this._validateInput()) {
72 this._addPackageByData(this._getInputData());
77 this._packageIdentifier.focus();
81 * Adds a package to the package list using the given package data.
83 * @param {object} packageData
85 _addPackageByData: function(packageData) {
86 // add package to list
87 var listItem = elCreate('li');
88 this._populateListItem(listItem, packageData);
91 var deleteButton = elCreate('span');
92 deleteButton.className = 'icon icon16 fa-times pointer jsTooltip';
93 elAttr(deleteButton, 'title', Language.get('wcf.global.button.delete'));
94 deleteButton.addEventListener('click', this._removePackage.bind(this));
95 DomUtil.prepend(deleteButton, listItem);
97 this._packageList.appendChild(listItem);
99 DomChangeListener.trigger();
103 * Creates the hidden fields when the form is submitted.
105 * @param {HTMLElement} listElement package list element from the package list
106 * @param {int} index package index
108 _createSubmitFields: function(listElement, index) {
109 var packageIdentifier = elCreate('input');
110 elAttr(packageIdentifier, 'type', 'hidden');
111 elAttr(packageIdentifier, 'name', this._formFieldId + '[' + index + '][packageIdentifier]')
112 packageIdentifier.value = elData(listElement, 'package-identifier');
113 this._form.appendChild(packageIdentifier);
117 * Empties the input fields.
120 this._packageIdentifier.value = '';
124 * Returns the error element for the given form field element.
125 * If `createIfNonExistent` is not given or `false`, `null` is returned
126 * if there is no error element, otherwise an empty error element
127 * is created and returned.
129 * @param {?boolean} createIfNonExistent
130 * @return {?HTMLElement}
132 _getErrorElement: function(element, createIfNoNExistent) {
133 var error = DomTraverse.nextByClass(element, 'innerError');
135 if (error === null && createIfNoNExistent) {
136 error = elCreate('small');
137 error.className = 'innerError';
139 DomUtil.insertAfter(error, element);
146 * Returns the current data of the input fields to add a new package.
150 _getInputData: function() {
152 packageIdentifier: this._packageIdentifier.value
157 * Returns the error element for the package identifier form field.
158 * If `createIfNonExistent` is not given or `false`, `null` is returned
159 * if there is no error element, otherwise an empty error element
160 * is created and returned.
162 * @param {?boolean} createIfNonExistent
163 * @return {?HTMLElement}
165 _getPackageIdentifierErrorElement: function(createIfNonExistent) {
166 return this._getErrorElement(this._packageIdentifier, createIfNonExistent);
170 * Adds a package to the package list after pressing ENTER in a
173 * @param {Event} event
175 _keyPress: function(event) {
176 if (EventKey.Enter(event)) {
177 this._addPackage(event);
182 * Adds all necessary package-relavant data to the given list item.
184 * @param {HTMLElement} listItem package list element holding package data
185 * @param {object} packageData package data
187 _populateListItem(listItem, packageData) {
188 elData(listItem, 'package-identifier', packageData.packageIdentifier);
192 * Removes a package by clicking on its delete button.
194 * @param {Event} event delete button click event
196 _removePackage: function(event) {
197 elRemove(event.currentTarget.closest('li'));
199 // remove field errors if the last package has been deleted
201 !this._packageList.childElementCount &&
202 this._packageList.nextElementSibling.tagName === 'SMALL' &&
203 this._packageList.nextElementSibling.classList.contains('innerError')
205 elRemove(this._packageList.nextElementSibling);
210 * Adds all necessary (hidden) form fields to the form when
211 * submitting the form.
213 _submit: function() {
214 DomTraverse.childrenByTag(this._packageList, 'LI').forEach(this._createSubmitFields.bind(this));
218 * Returns `true` if the currently entered package data is valid.
219 * Otherwise `false` is returned and relevant error messages are
224 _validateInput: function() {
225 return this._validatePackageIdentifier();
229 * Returns `true` if the currently entered package identifier is
230 * valid. Otherwise `false` is returned and an error message is
235 _validatePackageIdentifier: function() {
236 var packageIdentifier = this._packageIdentifier.value;
238 if (packageIdentifier === '') {
239 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.global.form.error.empty');
244 if (packageIdentifier.length < 3) {
245 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.minimumLength');
249 else if (packageIdentifier.length > 191) {
250 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.maximumLength');
255 // see `wcf\data\package\Package::isValidPackageName()`
256 if (!packageIdentifier.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/)) {
257 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.format');
262 // check if package has already been added
263 var duplicate = false;
264 DomTraverse.childrenByTag(this._packageList, 'LI').forEach(function(listItem, index) {
265 if (elData(listItem, 'package-identifier') === packageIdentifier) {
271 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.duplicate');
276 // remove outdated errors
277 var error = this._getPackageIdentifierErrorElement();
278 if (error !== null) {
286 * Returns `true` if the given version is valid. Otherwise `false`
287 * is returned and an error message is shown.
289 * @param {string} version validated version
290 * @param {function} versionErrorGetter returns the version error element
293 _validateVersion: function(version, versionErrorGetter) {
294 // see `wcf\data\package\Package::isValidVersion()`
295 // the version is no a required attribute
296 if (version !== '') {
297 if (version.length > 255) {
298 versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.maximumLength');
303 // see `wcf\data\package\Package::isValidVersion()`
304 if (!version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) {
305 versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format');
311 // remove outdated errors
312 var error = versionErrorGetter();
313 if (error !== null) {
321 return AbstractPackageList;