[!!!][FEATURE] Add showIconTable option for selectSingle fields
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / SelectSingleElement.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Element;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\StringUtility;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
21 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
22
23 /**
24 * Creates a widget where only one item can be selected.
25 * This is either a select drop-down if no size config is given or set to 1, or a select box.
26 *
27 * This is rendered for type=select, maxitems=1
28 */
29 class SelectSingleElement extends AbstractFormElement
30 {
31 /**
32 * Render single element
33 *
34 * @return array As defined in initializeResultArray() of AbstractNode
35 */
36 public function render()
37 {
38 $table = $this->data['tableName'];
39 $field = $this->data['fieldName'];
40 $row = $this->data['databaseRow'];
41 $parameterArray = $this->data['parameterArray'];
42 $config = $parameterArray['fieldConf']['config'];
43
44 $selectItems = $parameterArray['fieldConf']['config']['items'];
45
46 // Creating the label for the "No Matching Value" entry.
47 $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
48 ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
49 : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
50
51 // Check against inline uniqueness
52 /** @var InlineStackProcessor $inlineStackProcessor */
53 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
54 $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
55 $inlineParent = $inlineStackProcessor->getStructureLevel(-1);
56 $uniqueIds = null;
57 if (is_array($inlineParent) && $inlineParent['uid']) {
58 $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
59 $inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix();
60 if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
61 $uniqueIds = $this->data['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
62 $parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,'
63 . GeneralUtility::quoteJSvalue($inlineObjectName . '-' . $table) . ','
64 . GeneralUtility::quoteJSvalue($inlineFormName) . ','
65 . GeneralUtility::quoteJSvalue($row['uid']) . ');';
66 }
67 // hide uid of parent record for symmetric relations
68 if (
69 $inlineParent['config']['foreign_table'] == $table
70 && ($inlineParent['config']['foreign_field'] == $field || $inlineParent['config']['symmetric_field'] == $field)
71 ) {
72 $uniqueIds[] = $inlineParent['uid'];
73 }
74 }
75
76 // Initialization:
77 $selectId = StringUtility::getUniqueId('tceforms-select-');
78 $selectedIndex = 0;
79 $selectedIcon = '';
80 $selectedValueFound = false;
81 $size = (int)$config['size'];
82
83 // Style set on <select/>
84 $options = '';
85 $disabled = false;
86 if (!empty($config['readOnly'])) {
87 $disabled = true;
88 }
89
90 // Prepare groups
91 $selectItemCounter = 0;
92 $selectItemGroupCount = 0;
93 $selectItemGroups = array();
94 $selectIcons = array();
95 $selectedValue = '';
96 $hasIcons = false;
97
98 if (!empty($parameterArray['itemFormElValue'])) {
99 $selectedValue = (string)$parameterArray['itemFormElValue'][0];
100 }
101
102 foreach ($selectItems as $item) {
103 if ($item[1] === '--div--') {
104 // IS OPTGROUP
105 if ($selectItemCounter !== 0) {
106 $selectItemGroupCount++;
107 }
108 $selectItemGroups[$selectItemGroupCount]['header'] = array(
109 'title' => $item[0],
110 );
111 } else {
112 // IS ITEM
113 $title = htmlspecialchars($item['0'], ENT_COMPAT, 'UTF-8', false);
114 $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $title, $title) : '';
115 $selected = $selectedValue === (string)$item[1];
116
117 if ($selected) {
118 $selectedIndex = $selectItemCounter;
119 $selectedIcon = $icon;
120 $selectedValueFound = true;
121 }
122
123 $selectItemGroups[$selectItemGroupCount]['items'][] = array(
124 'title' => $title,
125 'value' => $item[1],
126 'icon' => $icon,
127 'selected' => $selected,
128 'index' => $selectItemCounter
129 );
130
131 // ICON
132 if ($icon) {
133 $selectIcons[] = array(
134 'title' => $title,
135 'icon' => $icon,
136 'index' => $selectItemCounter,
137 );
138 }
139
140 $selectItemCounter++;
141 }
142 }
143
144 // No-matching-value:
145 if ($selectedValue && !$selectedValueFound && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
146 $noMatchingLabel = @sprintf($noMatchingLabel, $selectedValue);
147 $options = '<option value="' . htmlspecialchars($selectedValue) . '" selected="selected">' . htmlspecialchars($noMatchingLabel) . '</option>';
148 } elseif (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
149 $selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
150 }
151
152 // Process groups
153 foreach ($selectItemGroups as $selectItemGroup) {
154 // suppress groups without items
155 if (empty($selectItemGroup['items'])) {
156 continue;
157 }
158
159 $optionGroup = is_array($selectItemGroup['header']);
160 $options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', false) . '">' : '');
161
162 if (is_array($selectItemGroup['items'])) {
163 foreach ($selectItemGroup['items'] as $item) {
164 $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
165 htmlspecialchars($item['icon']) . '"'
166 . ($item['selected'] ? ' selected="selected"' : '') . '>' . $item['title'] . '</option>';
167 }
168 $hasIcons = !empty($item['icon']);
169 }
170
171 $options .= ($optionGroup ? '</optgroup>' : '');
172 }
173
174 // Build the element
175 $html = ['<div class="form-control-wrap">'];
176
177 if ($hasIcons) {
178 $html[] = '<div class="input-group">';
179 $html[] = '<span class="input-group-addon input-group-icon">';
180 $html[] = $selectedIcon;
181 $html[] = '</span>';
182 }
183
184 $html[] = '<select'
185 . ' id="' . $selectId . '"'
186 . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
187 . $this->getValidationDataAsDataAttribute($config)
188 . ' class="form-control form-control-adapt"'
189 . ($size ? ' size="' . $size . '"' : '')
190 . ($disabled ? ' disabled="disabled"' : '')
191 . '>';
192 $html[] = $options;
193 $html[] = '</select>';
194
195 if ($hasIcons) {
196 $html[] = '</div>';
197 }
198
199 $html[] = '</div>';
200
201 // Create icon table:
202 if (!empty($selectIcons) && !empty($config['showIconTable'])) {
203 $selectIconColumns = (int)$config['selicon_cols'];
204
205 if (!$selectIconColumns) {
206 $selectIconColumns = count($selectIcons);
207 }
208
209 $selectIconColumns = ($selectIconColumns > 12 ? 12 : $selectIconColumns);
210 $selectIconRows = ceil(count($selectIcons) / $selectIconColumns);
211 $selectIcons = array_pad($selectIcons, $selectIconRows * $selectIconColumns, '');
212
213 $html[] = '<div class="t3js-forms-select-single-icons table-icons table-fit table-fit-inline-block">';
214 $html[] = '<table class="table table-condensed table-white table-center">';
215 $html[] = '<tbody>';
216 $html[] = '<tr>';
217
218 foreach ($selectIcons as $i => $selectIcon) {
219 if ($i % $selectIconColumns === 0 && $i !== 0) {
220 $html[] = '</tr>';
221 $html[] = '<tr>';
222 }
223
224 $html[] = '<td>';
225
226 if (is_array($selectIcon)) {
227 $html[] = '<a href="#" title="' . $selectIcon['title'] . '" data-select-index="' . $selectIcon['index'] . '">';
228 $html[] = $selectIcon['icon'];
229 $html[] = '</a>';
230 }
231
232 $html[] = '</td>';
233 }
234
235 $html[] = '</tr>';
236 $html[] = '</tbody>';
237 $html[] = '</table>';
238 $html[] = '</div>';
239 }
240
241 $html = implode(LF, $html);
242
243 // Wizards:
244 if (!$disabled) {
245 $html = $this->renderWizards(
246 array($html),
247 $config['wizards'],
248 $table,
249 $row,
250 $field,
251 $parameterArray,
252 $parameterArray['itemFormElName'],
253 BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
254 );
255 }
256
257 $resultArray = $this->initializeResultArray();
258 $resultArray['html'] = $html;
259 $resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SelectSingleElement' => implode(LF, [
260 'function(SelectSingleElement) {',
261 'SelectSingleElement.initialize(',
262 GeneralUtility::quoteJSvalue('#' . $selectId) . ',',
263 '{',
264 'onChange: function() {',
265 implode('', $parameterArray['fieldChangeFunc']),
266 '},',
267 'onFocus: function() {',
268 $parameterArray['onFocus'],
269 '},',
270 '}',
271 ');',
272 '}',
273 ])];
274
275 return $resultArray;
276 }
277 }