Skip to content

Commit b1d4130

Browse files
thibsyschmitz-ilias
andcommitted
[FIX] #46588 UI: Input\Container\Filter\FilterInput JavaScript binding (#11103)
* Fixes https://mantis.ilias.de/view.php?id=46588 * Update HTML of filter input context * Update rendering unit tests * Add new filter example --------- Co-authored-by: Tim Schmitz <schmitz@leifos.de>
1 parent e5606b3 commit b1d4130

7 files changed

Lines changed: 177 additions & 101 deletions

File tree

components/ILIAS/UI/src/Implementation/Component/Input/Field/Duration.php

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -298,17 +298,31 @@ protected function getConstraintForRequirement(): ?Constraint
298298
*/
299299
public function getUpdateOnLoadCode(): Closure
300300
{
301-
return fn($id) => "var combinedDuration = function() {
302-
var options = [];
303-
$('#$id').find('input').each(function() {
304-
options.push($(this).val());
305-
});
306-
return options.join(' - ');
307-
}
308-
$('#$id').on('input', function(event) {
309-
il.UI.input.onFieldUpdate(event, '$id', combinedDuration());
310-
});
311-
il.UI.input.onFieldUpdate(event, '$id', combinedDuration());";
301+
return static fn($id) => <<<JS
302+
(function () {
303+
function formatDateTimeValue(value) {
304+
const date = new Date(value);
305+
if (value.includes('T')) {
306+
return date.toLocaleString([], { dateStyle: 'short', timeStyle: 'short' });
307+
}
308+
return date.toLocaleDateString();
309+
}
310+
function reduceDateTimeInputs(inputs) {
311+
return Array
312+
.from(dateTimeInputs)
313+
.map((input) => (input.value) ? formatDateTimeValue(input.value) : '')
314+
.join(' - ');
315+
}
316+
const durationField = document.getElementById('$id');
317+
const dateTimeInputs = durationField.querySelectorAll('.c-field-datetime');
318+
dateTimeInputs.forEach((input) => {
319+
input.addEventListener('input', (event) => {
320+
il.UI.input.onFieldUpdate(event, '$id', reduceDateTimeInputs(dateTimeInputs));
321+
});
322+
});
323+
il.UI.input.onFieldUpdate(undefined, '$id', reduceDateTimeInputs(dateTimeInputs));
324+
})();
325+
JS;
312326
}
313327

314328
public function withLabels(string $start_label, string $end_label): C\Input\Field\Duration

components/ILIAS/UI/src/Implementation/Component/Input/Field/FilterContextRenderer.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ protected function wrapInFilterContext(
151151
return false; // stop event propagation
152152
});");
153153

154+
$tpl->setVariable("UI_COMPONENT_NAME", $this->getComponentCanonicalNameAttribute($component));
155+
$tpl->setVariable("INPUT_NAME", $component->getName());
156+
157+
if ($component->getOnLoadCode() !== null) {
158+
$binding_id = $this->bindJavaScript($component) ?? $this->createId();
159+
$tpl->setVariable("BINDING_ID", $binding_id);
160+
}
161+
154162
$tpl->setCurrentBlock("addon_left");
155163
$tpl->setVariable("LABEL", $component->getLabel());
156164
if ($id_pointing_to_input) {
@@ -161,7 +169,7 @@ protected function wrapInFilterContext(
161169
$tpl->parseCurrentBlock();
162170
$tpl->setCurrentBlock("filter_field");
163171
if ($component->isComplex()) {
164-
$tpl->setVariable("FILTER_FIELD", $this->renderProxyField($input_html, $default_renderer));
172+
$tpl->setVariable("FILTER_FIELD", $this->renderProxyField($component, $input_html, $default_renderer));
165173
} else {
166174
$tpl->setVariable("FILTER_FIELD", $input_html);
167175
}
@@ -179,6 +187,7 @@ protected function maybeDisable(FormInput $component, Template $tpl): void
179187
}
180188

181189
protected function renderProxyField(
190+
FormInput $component,
182191
string $input_html,
183192
RendererInterface $default_renderer
184193
): string {
@@ -210,7 +219,6 @@ protected function renderDurationField(F\Duration $component, RendererInterface
210219
$input_html .= $default_renderer->render($input);
211220

212221
$tpl = $this->getTemplate("tpl.duration.html", true, true);
213-
$id = $this->bindJSandApplyId($component, $tpl);
214222
$tpl->setVariable('DURATION', $input_html);
215223

216224
return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get());

components/ILIAS/UI/src/Implementation/Component/Input/Field/MultiSelect.php

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,25 @@ protected function getConstraintForRequirement(): ?Constraint
8585
*/
8686
public function getUpdateOnLoadCode(): Closure
8787
{
88-
return fn($id) => "(function() {
89-
var checkedBoxes = function() {
90-
var options = [];
91-
$('#$id').find('li').each(function() {
92-
if ($(this).find('input').prop('checked')) {
93-
options.push($(this).find('span').text());
94-
}
95-
});
96-
return options.join(', ');
97-
}
98-
$('#$id').on('input', function(event) {
99-
il.UI.input.onFieldUpdate(event, '$id', checkedBoxes());
100-
});
101-
il.UI.input.onFieldUpdate(event, '$id', checkedBoxes());
102-
})();";
88+
return static fn($id) => <<<JS
89+
(function () {
90+
function reduceMultiSelectCheckboxInputs(inputs) {
91+
return Array
92+
.from(inputs)
93+
.filter((input) => input.checked)
94+
.map((input) => input.parentElement.querySelector('.c-field-multiselect__label-text')?.textContent ?? '')
95+
.join(', ');
96+
}
97+
const multiSelectField = document.getElementById('$id');
98+
const multiSelectCheckboxInputs = multiSelectField.querySelectorAll('.c-field-multiselect input[type="checkbox"]');
99+
multiSelectCheckboxInputs.forEach((input) => {
100+
input.addEventListener('input', (event) => {
101+
il.UI.input.onFieldUpdate(event, '$id', reduceMultiSelectCheckboxInputs(multiSelectCheckboxInputs));
102+
});
103+
});
104+
il.UI.input.onFieldUpdate(undefined, '$id', reduceMultiSelectCheckboxInputs(multiSelectCheckboxInputs));
105+
})();
106+
JS;
103107
}
104108

105109
/**
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ILIAS\UI\examples\Input\Container\Filter\Standard;
6+
7+
/**
8+
* ---
9+
* description: >
10+
* Example showing a Filter Container and Filter Inputs with additional JavaScript on-load-code
11+
* attached to them.
12+
*
13+
* expected output: >
14+
* ILIAS shows the rendered Filter Component with several Filter Inputs. When opening the browser
15+
* console, for each of the Filter Input, as well as the Filter Container, a log entry that refers
16+
* to their ID will be visible.
17+
* ---
18+
*/
19+
function with_additional_on_load_code(): string
20+
{
21+
global $DIC;
22+
23+
$factory = $DIC->ui()->factory();
24+
$renderer = $DIC->ui()->renderer();
25+
26+
$pseudo_load_code = static fn($name) => static fn($id) => "console.log('Loaded $name with ID: ' + '$id');";
27+
28+
$pseudo_options = [
29+
'A' => 'Option A',
30+
'B' => 'Option B',
31+
'C' => 'Option C',
32+
];
33+
34+
$filter_inputs = [
35+
$factory->input()->field()->multiSelect('multi-select', $pseudo_options)->withAdditionalOnLoadCode($pseudo_load_code('multi-select')),
36+
$factory->input()->field()->select('single-select', $pseudo_options)->withAdditionalOnLoadCode($pseudo_load_code('single-select')),
37+
$factory->input()->field()->duration('duration')->withAdditionalOnLoadCode($pseudo_load_code('duration')),
38+
$factory->input()->field()->dateTime('datetime')->withAdditionalOnLoadCode($pseudo_load_code('datetime')),
39+
$factory->input()->field()->numeric('numeric')->withAdditionalOnLoadCode($pseudo_load_code('numeric')),
40+
$factory->input()->field()->text('text')->withAdditionalOnLoadCode($pseudo_load_code('text')),
41+
];
42+
43+
$filter = $factory->input()->container()->filter()->standard(
44+
'#',
45+
'#',
46+
'#',
47+
'#',
48+
'#',
49+
'#',
50+
$filter_inputs,
51+
array_map(static fn() => true, $filter_inputs),
52+
true,
53+
true,
54+
)->withAdditionalOnLoadCode($pseudo_load_code('filter'));
55+
56+
return $renderer->render($filter);
57+
}

components/ILIAS/UI/src/templates/default/Input/tpl.context_filter.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="col-md-6 col-lg-4 il-popover-container">
2-
<div class="input-group">
2+
<div data-il-ui-component="{UI_COMPONENT_NAME}" data-il-ui-input-name="{INPUT_NAME}"<!-- BEGIN binding --> id="{BINDING_ID}"<!-- END binding --> class="input-group">
33
<!-- BEGIN addon_left -->
44
<label <!-- BEGIN for -->for="{ID}" <!-- END for --> class="input-group-addon leftaddon">{LABEL}</label>
55
<!-- END addon_left -->

components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public function testRenderTextWithFilterContext(): void
128128

129129
$expected = $this->brutallyTrimHTML('
130130
<div class="col-md-6 col-lg-4 il-popover-container">
131-
<div class="input-group">
131+
<div data-il-ui-component="text-field-input" data-il-ui-input-name="" class="input-group">
132132
<label for="id_1" class="input-group-addon leftaddon">label</label>
133133
<input id="id_1" type="text" class="c-field-text" />
134134
<span class="input-group-addon rightaddon">
@@ -152,8 +152,7 @@ public function testRenderNumericWithFilterContext(): void
152152
$html = $this->brutallyTrimHTML($fr->render($numeric));
153153

154154
$expected = $this->brutallyTrimHTML('
155-
<div class="col-md-6 col-lg-4 il-popover-container">
156-
<div class="input-group">
155+
<div class="col-md-6 col-lg-4 il-popover-container"><div data-il-ui-component="numeric-field-input" data-il-ui-input-name="" class="input-group">
157156
<label for="id_1" class="input-group-addon leftaddon">label</label>
158157
<input id="id_1" type="number" step="1" class="c-field-number" />
159158
<span class="input-group-addon rightaddon">
@@ -178,8 +177,7 @@ public function testRenderSelectWithFilterContext(): void
178177
$html = $this->brutallyTrimHTML($fr->render($select));
179178

180179
$expected = $this->brutallyTrimHTML('
181-
<div class="col-md-6 col-lg-4 il-popover-container">
182-
<div class="input-group">
180+
<div class="col-md-6 col-lg-4 il-popover-container"><div data-il-ui-component="select-field-input" data-il-ui-input-name="" class="input-group">
183181
<label for="id_1" class="input-group-addon leftaddon">label</label>
184182
<select id="id_1">
185183
<option selected="selected" value="">-</option>
@@ -207,8 +205,7 @@ public function testRenderMultiSelectWithHasOptionFilter(): void
207205
$html = $this->brutallyTrimHTML($fr->render($multi));
208206

209207
$expected = $this->brutallyTrimHTML('
210-
<div class="col-md-6 col-lg-4 il-popover-container">
211-
<div class="input-group">
208+
<div class="col-md-6 col-lg-4 il-popover-container"><div data-il-ui-component="multi-select-field-input" data-il-ui-input-name="" class="input-group">
212209
<label class="input-group-addon leftaddon">label</label>
213210
<span role="button" tabindex="0" class="form-control il-filter-field" id="id_3" data-placement="bottom"></span>
214211
<div class="il-standard-popover-content" style="display:none;" id="id_1"></div>
@@ -218,7 +215,6 @@ public function testRenderMultiSelectWithHasOptionFilter(): void
218215
</span>
219216
</span>
220217
</div>
221-
{POPOVER}
222218
</div>
223219
');
224220
$this->assertHTMLEquals($expected, $html);
@@ -234,8 +230,7 @@ public function testRenderDateTimeWithFilterContext(): void
234230
$html = $this->brutallyTrimHTML($fr->render($datetime));
235231

236232
$expected = $this->brutallyTrimHTML('
237-
<div class="col-md-6 col-lg-4 il-popover-container">
238-
<div class="input-group">
233+
<div class="col-md-6 col-lg-4 il-popover-container"><div data-il-ui-component="date-time-field-input" data-il-ui-input-name="" class="input-group">
239234
<label for="id_1" class="input-group-addon leftaddon">label</label>
240235
<div class="c-input-group">
241236
<input id="id_1" type="date" class="c-field-datetime" />
@@ -288,18 +283,16 @@ public function testRenderDurationWithFilterContext(): void
288283

289284

290285
$expected = $this->brutallyTrimHTML('
291-
<div class="col-md-6 col-lg-4 il-popover-container">
292-
<div class="input-group">
286+
<div class="col-md-6 col-lg-4 il-popover-container"><div data-il-ui-component="duration-field-input" data-il-ui-input-name="" class="input-group">
293287
<label class="input-group-addon leftaddon">label</label>
294-
<span role="button" tabindex="0" class="form-control il-filter-field" id="id_7" data-placement="bottom"></span>
295-
<div class="il-standard-popover-content" style="display:none;" id="id_5"></div>
288+
<span role="button" tabindex="0" class="form-control il-filter-field" id="id_6" data-placement="bottom"></span>
289+
<div class="il-standard-popover-content" style="display:none;" id="id_4"></div>
296290
<span class="input-group-addon rightaddon">
297-
<a class="glyph" href="" aria-label="remove" id="id_8">
291+
<a class="glyph" href="" aria-label="remove" id="id_7">
298292
<span class="glyphicon glyphicon-minus-sign" aria-hidden="true"></span>
299293
</a>
300294
</span>
301295
</div>
302-
{POPOVER}
303296
</div>
304297
');
305298
$this->assertHTMLEquals($expected, $html);

0 commit comments

Comments
 (0)