d3fc9385adaf71ee734a3ac0692670ed63e9caaa
[GitHub/WoltLab/WCF.git] /
1 /**
2 * Abstract implementation of the JavaScript component of a form field handling
3 * a list of packages.
4 *
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
9 * @since 3.2
10 */
11 define(['Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language'], function(DomChangeListener, DomTraverse, DomUtil, EventKey, Language) {
12 "use strict";
13
14 /**
15 * @constructor
16 */
17 function AbstractPackageList(formFieldId, existingPackages) {
18 this.init(formFieldId, existingPackages);
19 };
20 AbstractPackageList.prototype = {
21 /**
22 * Initializes the package list handler.
23 *
24 * @param {string} formFieldId id of the associated form field
25 * @param {object[]} existingPackages data of existing packages
26 */
27 init: function(formFieldId, existingPackages) {
28 this._formFieldId = formFieldId;
29
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 + "'.");
33 }
34
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 + "'.");
38 }
39 this._packageIdentifier.addEventListener('keypress', this._keyPress.bind(this));
40
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 + "'.");
44 }
45 this._addButton.addEventListener('click', this._addPackage.bind(this));
46
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 + "'.");
50 }
51 this._form.addEventListener('submit', this._submit.bind(this));
52
53 existingPackages.forEach(this._addPackageByData.bind(this));
54 },
55
56 /**
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.
60 *
61 * @param {Event} event event that triggered trying to add the package
62 */
63 _addPackage: function(event) {
64 event.preventDefault();
65 event.stopPropagation();
66
67 // validate data
68 if (!this._validateInput()) {
69 return;
70 }
71
72 this._addPackageByData(this._getInputData());
73
74 // empty fields
75 this._emptyInput();
76
77 this._packageIdentifier.focus();
78 },
79
80 /**
81 * Adds a package to the package list using the given package data.
82 *
83 * @param {object} packageData
84 */
85 _addPackageByData: function(packageData) {
86 // add package to list
87 var listItem = elCreate('li');
88 this._populateListItem(listItem, packageData);
89
90 // add delete button
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);
96
97 this._packageList.appendChild(listItem);
98
99 DomChangeListener.trigger();
100 },
101
102 /**
103 * Creates the hidden fields when the form is submitted.
104 *
105 * @param {HTMLElement} listElement package list element from the package list
106 * @param {int} index package index
107 */
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);
114 },
115
116 /**
117 * Empties the input fields.
118 */
119 _emptyInput() {
120 this._packageIdentifier.value = '';
121 },
122
123 /**
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.
128 *
129 * @param {?boolean} createIfNonExistent
130 * @return {?HTMLElement}
131 */
132 _getErrorElement: function(element, createIfNoNExistent) {
133 var error = DomTraverse.nextByClass(element, 'innerError');
134
135 if (error === null && createIfNoNExistent) {
136 error = elCreate('small');
137 error.className = 'innerError';
138
139 DomUtil.insertAfter(error, element);
140 }
141
142 return error;
143 },
144
145 /**
146 * Returns the current data of the input fields to add a new package.
147 *
148 * @return {object}
149 */
150 _getInputData: function() {
151 return {
152 packageIdentifier: this._packageIdentifier.value
153 };
154 },
155
156 /**
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.
161 *
162 * @param {?boolean} createIfNonExistent
163 * @return {?HTMLElement}
164 */
165 _getPackageIdentifierErrorElement: function(createIfNonExistent) {
166 return this._getErrorElement(this._packageIdentifier, createIfNonExistent);
167 },
168
169 /**
170 * Adds a package to the package list after pressing ENTER in a
171 * text field.
172 *
173 * @param {Event} event
174 */
175 _keyPress: function(event) {
176 if (EventKey.Enter(event)) {
177 this._addPackage(event);
178 }
179 },
180
181 /**
182 * Adds all necessary package-relavant data to the given list item.
183 *
184 * @param {HTMLElement} listItem package list element holding package data
185 * @param {object} packageData package data
186 */
187 _populateListItem(listItem, packageData) {
188 elData(listItem, 'package-identifier', packageData.packageIdentifier);
189 },
190
191 /**
192 * Removes a package by clicking on its delete button.
193 *
194 * @param {Event} event delete button click event
195 */
196 _removePackage: function(event) {
197 elRemove(event.currentTarget.closest('li'));
198
199 // remove field errors if the last package has been deleted
200 if (
201 !this._packageList.childElementCount &&
202 this._packageList.nextElementSibling.tagName === 'SMALL' &&
203 this._packageList.nextElementSibling.classList.contains('innerError')
204 ) {
205 elRemove(this._packageList.nextElementSibling);
206 }
207 },
208
209 /**
210 * Adds all necessary (hidden) form fields to the form when
211 * submitting the form.
212 */
213 _submit: function() {
214 DomTraverse.childrenByTag(this._packageList, 'LI').forEach(this._createSubmitFields.bind(this));
215 },
216
217 /**
218 * Returns `true` if the currently entered package data is valid.
219 * Otherwise `false` is returned and relevant error messages are
220 * shown.
221 *
222 * @return {boolean}
223 */
224 _validateInput: function() {
225 return this._validatePackageIdentifier();
226 },
227
228 /**
229 * Returns `true` if the currently entered package identifier is
230 * valid. Otherwise `false` is returned and an error message is
231 * shown.
232 *
233 * @return {boolean}
234 */
235 _validatePackageIdentifier: function() {
236 var packageIdentifier = this._packageIdentifier.value;
237
238 if (packageIdentifier === '') {
239 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.global.form.error.empty');
240
241 return false;
242 }
243
244 if (packageIdentifier.length < 3) {
245 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.minimumLength');
246
247 return false;
248 }
249 else if (packageIdentifier.length > 191) {
250 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.maximumLength');
251
252 return false;
253 }
254
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');
258
259 return false;
260 }
261
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) {
266 duplicate = true;
267 }
268 });
269
270 if (duplicate) {
271 this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.duplicate');
272
273 return false;
274 }
275
276 // remove outdated errors
277 var error = this._getPackageIdentifierErrorElement();
278 if (error !== null) {
279 elRemove(error);
280 }
281
282 return true;
283 },
284
285 /**
286 * Returns `true` if the given version is valid. Otherwise `false`
287 * is returned and an error message is shown.
288 *
289 * @param {string} version validated version
290 * @param {function} versionErrorGetter returns the version error element
291 * @return {boolean}
292 */
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');
299
300 return false;
301 }
302
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');
306
307 return false;
308 }
309 }
310
311 // remove outdated errors
312 var error = versionErrorGetter();
313 if (error !== null) {
314 elRemove(error);
315 }
316
317 return true;
318 }
319 };
320
321 return AbstractPackageList;
322 });