[BUGFIX] Check if field exists when deleting items from `group` field
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Private / TypeScript / FormEngine / Element / AbstractSortableSelectItems.ts
1 /*
2  * This file is part of the TYPO3 CMS project.
3  *
4  * It is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU General Public License, either version 2
6  * of the License, or any later version.
7  *
8  * For the full copyright and license information, please read the
9  * LICENSE.txt file that was distributed with this source code.
10  *
11  * The TYPO3 project - inspiring people to share!
12  */
13
14 import * as $ from 'jquery';
15 import FormEngine = require('TYPO3/CMS/Backend/FormEngine');
16 import FormEngineValidation = require('TYPO3/CMS/Backend/FormEngineValidation');
17
18 export abstract class AbstractSortableSelectItems {
19
20   /**
21    * Moves currently selected options from a select field to the very top,
22    * can be multiple entries as well
23    *
24    * @param {HTMLSelectElement} fieldElement
25    */
26   private static moveOptionToTop(fieldElement: HTMLSelectElement): void {
27     Array.from(fieldElement.querySelectorAll(':checked')).reverse().forEach((optionEl: HTMLOptionElement): void => {
28       fieldElement.insertBefore(optionEl, fieldElement.firstElementChild);
29     });
30   }
31
32   /**
33    * Moves currently selected options from a select field as the very last entries
34    *
35    * @param {HTMLSelectElement} fieldElement
36    */
37   private static moveOptionToBottom(fieldElement: HTMLSelectElement): void {
38     Array.from(fieldElement.querySelectorAll(':checked')).forEach((optionEl: HTMLOptionElement): void => {
39       fieldElement.insertBefore(optionEl, null);
40     });
41   }
42
43   /**
44    * Moves currently selected options from a select field up by one position,
45    * can be multiple entries as well
46    *
47    * @param {HTMLSelectElement} fieldElement
48    */
49   private static moveOptionUp(fieldElement: HTMLSelectElement): void {
50     const allChildren = Array.from(fieldElement.children);
51     const selectedOptions = Array.from(fieldElement.querySelectorAll(':checked'));
52     for (let optionEl of selectedOptions) {
53       if (allChildren.indexOf(optionEl) === 0 && optionEl.previousElementSibling === null) {
54         break;
55       }
56
57       fieldElement.insertBefore(optionEl, optionEl.previousElementSibling);
58     }
59   }
60
61   /**
62    * Moves currently selected options from a select field up by one position,
63    * can be multiple entries as well
64    *
65    * @param {HTMLSelectElement} fieldElement
66    */
67   private static moveOptionDown(fieldElement: HTMLSelectElement): void {
68     const allChildren = Array.from(fieldElement.children).reverse();
69     const selectedOptions = Array.from(fieldElement.querySelectorAll(':checked')).reverse();
70     for (let optionEl of selectedOptions) {
71       if (allChildren.indexOf(optionEl) === 0 && optionEl.nextElementSibling === null) {
72         break;
73       }
74
75       fieldElement.insertBefore(optionEl, optionEl.nextElementSibling.nextElementSibling);
76     }
77   }
78
79   /**
80    * Removes currently selected options from a select field
81    *
82    * @param {HTMLSelectElement} fieldElement
83    * @param {HTMLSelectElement} availableFieldElement
84    */
85   private static removeOption(fieldElement: HTMLSelectElement, availableFieldElement: HTMLSelectElement): void {
86     Array.from(fieldElement.querySelectorAll(':checked')).forEach((option: HTMLOptionElement): void => {
87       const originalOption = <HTMLOptionElement>availableFieldElement.querySelector('option[value="' + option.value + '"]');
88       if (originalOption !== null) {
89         originalOption.classList.remove('hidden');
90         originalOption.disabled = false;
91       }
92
93       fieldElement.removeChild(option);
94     });
95   }
96
97   /**
98    * @param {HTMLSelectElement} fieldElement
99    */
100   protected registerSortableEventHandler = (fieldElement: HTMLSelectElement): void => {
101     const aside = fieldElement.closest('.form-wizards-wrap').querySelector('.form-wizards-items-aside');
102     if (aside === null) {
103       return;
104     }
105
106     aside.addEventListener('click', (e): void => {
107       let target: HTMLAnchorElement;
108
109       if ((target = <HTMLAnchorElement>(<Element>e.target).closest('.t3js-btn-option')) === null) {
110         if ((<Element>e.target).matches('.t3js-btn-option')) {
111           target = <HTMLAnchorElement>e.target;
112         }
113
114         return;
115       }
116
117       e.preventDefault();
118
119       const relatedFieldName = target.dataset.fieldname;
120
121       if (target.classList.contains('t3js-btn-moveoption-top')) {
122         AbstractSortableSelectItems.moveOptionToTop(fieldElement);
123       } else if (target.classList.contains('t3js-btn-moveoption-up')) {
124         AbstractSortableSelectItems.moveOptionUp(fieldElement);
125       } else if (target.classList.contains('t3js-btn-moveoption-down')) {
126         AbstractSortableSelectItems.moveOptionDown(fieldElement);
127       } else if (target.classList.contains('t3js-btn-moveoption-bottom')) {
128         AbstractSortableSelectItems.moveOptionToBottom(fieldElement);
129       } else if (target.classList.contains('t3js-btn-removeoption')) {
130         AbstractSortableSelectItems.removeOption(
131           fieldElement,
132           <HTMLSelectElement>FormEngine.getFieldElement(relatedFieldName, '_avail').get(0),
133         );
134       }
135
136       FormEngine.updateHiddenFieldValueFromSelect(fieldElement, FormEngine.getFieldElement(relatedFieldName).get(0));
137       FormEngine.legacyFieldChangedCb();
138       FormEngineValidation.markFieldAsChanged($(fieldElement));
139       FormEngineValidation.validate();
140     });
141   }
142 }