ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
Renderer.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
25use ILIAS\UI\Renderer as RendererInterface;
27use LogicException;
28
30{
31 public const DEFAULT_DROPDOWN_LABEL = 'label_fieldselection';
32 public const DEFAULT_BUTTON_LABEL = 'label_fieldselection_refresh';
33 public const DEFAULT_SORTATION_DROPDOWN_LABEL = 'label_sortation';
34 public const DEFAULT_DROPDOWN_LABEL_OFFSET = 'label_pagination_offset';
35 public const DEFAULT_DROPDOWN_LABEL_LIMIT = 'label_pagination_limit';
36 public const DEFAULT_MODE_LABEL = 'label_modeviewcontrol';
37
38 public function render(Component\Component $component, RendererInterface $default_renderer): string
39 {
40 switch (true) {
41 case ($component instanceof FieldSelection):
42 return $this->renderFieldSelection($component, $default_renderer);
43 case ($component instanceof Sortation):
44 return $this->renderSortation($component, $default_renderer);
45 case ($component instanceof Pagination):
46 return $this->renderPagination($component, $default_renderer);
47 case ($component instanceof Component\Input\ViewControl\Group):
48 return $default_renderer->render($component->getInputs());
49 case ($component instanceof Component\Input\ViewControl\NullControl):
50 return '';
51 case ($component instanceof Component\Input\ViewControl\Mode):
52 return $this->renderMode($component, $default_renderer);
53
54 default:
55 $this->cannotHandleComponent($component);
56 }
57 }
58
59 protected function renderFieldSelection(FieldSelection $component, RendererInterface $default_renderer): string
60 {
61 $tpl = $this->getTemplate("tpl.viewcontrol_fieldselection.html", true, true);
62 $ui_factory = $this->getUIFactory();
63
64 $set_values = $component->getValue() ?? [];
65 foreach ($component->getOptions() as $opt_value => $opt_label) {
66 $tpl->setCurrentBlock("option");
67 $tpl->setVariable("OPTION_ID", $this->getJavascriptBinding()->createId());
68 $tpl->setVariable("OPTION_VALUE", $opt_value);
69 $tpl->setVariable("OPTION_LABEL", $opt_label);
70 if (in_array($opt_value, $set_values)) {
71 $tpl->setVariable("CHECKED", 'checked');
72 }
73 $tpl->parseCurrentBlock();
74
75 if (in_array($opt_value, $set_values)) {
76 $tpl->setCurrentBlock("value");
77 $tpl->setVariable("NAME", $component->getName());
78 $tpl->setVariable("VALUE", $opt_value);
79 $tpl->parseCurrentBlock();
80 }
81 }
82
83 $internal_signal = $component->getInternalSignal();
84 $param_name = $component->getName();
85 if ($container_submit_signal = $component->getOnChangeSignal()) {
86 $component = $component->withAdditionalOnLoadCode(
87 fn($id) => "il.UI.Input.Viewcontrols.FieldSelection.init(
88 document.getElementById('{$id}'),
89 '{$internal_signal}',
90 '{$container_submit_signal}',
91 '{$param_name}'
92 );"
93 );
94 }
95
96 $component = $component
98 fn($id) =>
99 "$('#{$id} > .dropdown-menu').on('click', (event) => event.stopPropagation());"
100 )->withAdditionalOnLoadCode(
101 fn($id) =>
102 "il.UI.dropdown.init(document.getElementById('{$id}'));"
103 );
104
105 $id = $this->bindJavaScript($component);
106 $container_submit_signal = $component->getOnChangeSignal();
107 $button_label = $component->getButtonLabel() !== '' ?
108 $component->getButtonLabel() : $this->txt(self::DEFAULT_BUTTON_LABEL);
109 $button = $ui_factory->button()->standard($button_label, '#')
110 ->withOnClick($internal_signal);
111
112 $tpl->setVariable('ID', $id);
113 $tpl->setVariable("ID_MENU", $id . '_ctrl');
114 $tpl->setVariable("ARIA_LABEL", $this->txt(self::DEFAULT_DROPDOWN_LABEL));
115 $tpl->setVariable("BUTTON", $default_renderer->render($button));
116
117 return $tpl->get();
118 }
119
120 protected function renderSortation(Sortation $component, RendererInterface $default_renderer): string
121 {
122 $tpl = $this->getTemplate("tpl.viewcontrol_sortation.html", true, true);
123 $ui_factory = $this->getUIFactory();
124
125 foreach ($component->getOptions() as $opt_label => $order) {
126 $opt_value = $order->join(':', fn($ret, $key, $value) => [$key, $value]);
127 $internal_signal = $component->getInternalSignal();
128 $internal_signal->addOption('value', $opt_value);
129 $item = $ui_factory->button()->shy((string) $opt_label, '#')
130 ->withOnClick($internal_signal);
131 $tpl->setCurrentBlock("option");
132 $tpl->setVariable("OPTION", $default_renderer->render($item));
133
134 if ($opt_value === $component->getValue()) {
135 $tpl->touchBlock("selected");
136 $tpl->setCurrentBlock("option");
137 }
138 $tpl->parseCurrentBlock();
139 }
140
141 if ($container_submit_signal = $component->getOnChangeSignal()) {
142 $component = $component->withAdditionalOnLoadCode(
143 fn($id) => "il.UI.Input.Viewcontrols.Sortation.init(
144 document.getElementById('{$id}'),
145 '{$internal_signal}',
146 '{$container_submit_signal}',
147 );"
148 );
149 }
150
151 $component = $component->withAdditionalOnLoadCode(
152 fn($id) => "il.UI.dropdown.init(document.getElementById('{$id}'));"
153 );
154
155 $id = $this->bindJavaScript($component);
156
157 $tpl->setVariable('ID', $id);
158 $tpl->setVariable("ID_MENU", $id . '_ctrl');
159 $tpl->setVariable("ARIA_LABEL", $this->txt(self::DEFAULT_SORTATION_DROPDOWN_LABEL));
160
161 $tpl->setVariable(
162 "VALUES",
163 $default_renderer->render(
164 $component->getInputGroup()
165 )
166 );
167
168 return $tpl->get();
169 }
170
174 protected function buildRanges(
175 int $total_count,
176 int $page_limit
177 ): array {
178 $data_factory = $this->getDataFactory();
179 if ($page_limit >= $total_count) {
180 return [$data_factory->range(0, $page_limit)];
181 }
182 foreach (range(0, $total_count - 1, $page_limit) as $idx => $start) {
183 $ranges[] = $data_factory->range($start, $page_limit);
184 }
185 return $ranges;
186 }
187
191 protected function findCurrentPage(array $ranges, int $offset): int
192 {
193 foreach ($ranges as $idx => $range) {
194 if ($offset >= $range->getStart() && $offset < $range->getEnd()) {
195 return $idx;
196 }
197 }
198 throw new LogicException('offset is not in any range');
199 }
200
205 protected function sliceRangesToVisibleEntries(array $ranges, int $current, int $number_of_visible_entries): array
206 {
207 $first = reset($ranges);
208 $last = end($ranges);
209
210 $start = max(0, $current - floor(($number_of_visible_entries - 1) / 2));
211 if ($start + $number_of_visible_entries >= count($ranges)) {
212 $start = max(0, count($ranges) - $number_of_visible_entries);
213 }
214
215 $entries = array_slice($ranges, (int) $start, $number_of_visible_entries);
216
217 if (! in_array($first, $entries)) {
218 array_shift($entries);
219 array_unshift($entries, $first);
220 }
221 if (! in_array($last, $entries)) {
222 array_pop($entries);
223 array_push($entries, $last);
224 }
225 return $entries;
226 }
227
228 protected function renderPagination(Pagination $component, RendererInterface $default_renderer): string
229 {
230 $tpl = $this->getTemplate("tpl.viewcontrol_pagination.html", true, true);
231 $ui_factory = $this->getUIFactory();
232 $internal_signal = $component->getInternalSignal();
233 $limit_options = $component->getLimitOptions();
234 $total_count = $component->getTotalCount();
235
236 list(Pagination::FNAME_OFFSET => $offset, Pagination::FNAME_LIMIT => $limit) = array_map('intval', $component->getValue());
237 $limit = $limit > 0 ? $limit : reset($limit_options);
238 $offset = $offset >= $total_count ? 0 : $offset;
239
240 if (! $total_count) {
241 $input = $ui_factory->input()->field()->numeric('offset')->withValue($offset);
242 $apply = $ui_factory->button()->standard('apply', '');
243 $tpl->setVariable("INPUT", $default_renderer->render($input));
244 $tpl->setVariable("BUTTON", $default_renderer->render($apply));
245 } else {
246 $ranges = $this->buildRanges($total_count, $limit);
247 $current = $this->findCurrentPage($ranges, $offset);
248
249 if ($limit >= $total_count) {
250 $entries = $ranges;
251 } else {
252 $entries = $this->sliceRangesToVisibleEntries($ranges, $current, $component->getNumberOfVisibleEntries());
253 }
254
255 if (count($entries) > 1) {
256 foreach ($ranges as $idx => $range) {
257 if (in_array($range, $entries)) {
258 $signal = clone $internal_signal;
259 $signal->addOption('offset', $range->getStart());
260 $signal->addOption('limit', $limit);
261 $tpl->setCurrentBlock("entry");
262 $entry = $ui_factory->button()->shy((string) ($idx + 1), '#')->withOnClick($signal);
263 if ($idx === $current) {
264 $entry = $entry->withEngagedState(true);
265 }
266 $tpl->setVariable("ENTRY", $default_renderer->render($entry));
267 $tpl->parseCurrentBlock();
268 } elseif ($idx === 1 || $idx === count($ranges) - 2) {
269 $tpl->setCurrentBlock("entry");
270 $tpl->touchBlock("spacer");
271 $tpl->parseCurrentBlock();
272 }
273 }
274
275 if ($current > 0) {
276 $range = $ranges[$current - 1];
277 $signal = clone $internal_signal;
278 $signal->addOption('offset', $range->getStart());
279 $signal->addOption('limit', $limit);
280 $btn_left = $ui_factory->button()->shy('', $signal ?? '#')
281 ->withSymbol($ui_factory->symbol()->glyph()->back());
282 $tpl->setVariable("LEFT_ROCKER", $default_renderer->render($btn_left));
283 }
284
285 if ($current < count($ranges) - 1) {
286 $range = $ranges[$current + 1];
287 $signal = clone $internal_signal;
288 $signal->addOption('offset', $range->getStart());
289 $signal->addOption('limit', $limit);
290 $btn_right = $ui_factory->button()->shy('', $signal ?? '#')
291 ->withSymbol($ui_factory->symbol()->glyph()->next());
292 $tpl->setVariable("RIGHT_ROCKER", $default_renderer->render($btn_right));
293 }
294 }
295 }
296
297 foreach ($component->getLimitOptions() as $option) {
298 $signal = clone $internal_signal;
299 $signal->addOption('offset', $offset);
300 $signal->addOption('limit', (string) $option);
301 $option_label = $option === \PHP_INT_MAX ? $this->txt('ui_pagination_unlimited') : (string) $option;
302
303 $item = $ui_factory->button()->shy($option_label, '#')
304 ->withOnClick($signal);
305 $tpl->setCurrentBlock("option_limit");
306 $tpl->setVariable("OPTION_LIMIT", $default_renderer->render($item));
307 if ($option === $limit) {
308 $tpl->touchBlock("selected");
309 $tpl->setCurrentBlock("option_limit");
310 }
311 $tpl->parseCurrentBlock();
312 }
313
314 if ($container_submit_signal = $component->getOnChangeSignal()) {
315 $component = $component->withAdditionalOnLoadCode(
316 fn($id) => "il.UI.Input.Viewcontrols.Pagination.init(
317 document.getElementById('{$id}'),
318 '{$internal_signal}',
319 '{$container_submit_signal}',
320 );"
321 );
322 }
323
324 $component = $component->withAdditionalOnLoadCode(
325 fn($id) => "
326 il.UI.dropdown.init(
327 document.getElementById('{$id}').querySelector(
328 '.dropdown.il-viewcontrol-pagination__num-of-items'
329 )
330 );
331 "
332 );
333 $id = $this->bindJavaScript($component);
334
335 $tpl->setVariable('ID', $id);
336 $tpl->setVariable("ID_MENU_OFFSET", $id . '_ctrl_offset');
337 $tpl->setVariable("ARIA_LABEL_OFFSET", $this->txt(self::DEFAULT_DROPDOWN_LABEL_OFFSET));
338 $tpl->setVariable("ID_MENU_LIMIT", $id . '_ctrl_limit');
339 $tpl->setVariable("ARIA_LABEL_LIMIT", $this->txt(self::DEFAULT_DROPDOWN_LABEL_LIMIT));
340
341 $tpl->setVariable(
342 "VALUES",
343 $default_renderer->render(
344 $component->getInputGroup()
345 )
346 );
347
348 return $tpl->get();
349 }
350
351
352 protected function renderMode(Mode $component, RendererInterface $default_renderer): string
353 {
354 $tpl = $this->getTemplate("tpl.viewcontrol_mode.html", true, true);
355 $ui_factory = $this->getUIFactory();
356
357 $container_submit_signal = $component->getOnChangeSignal();
358 $options = $component->getOptions();
359 $set_value = $component->getValue() ?? array_key_first($options);
360
361 $out = [];
362 foreach ($options as $opt_value => $opt_label) {
363 $out[] = $ui_factory->button()->standard($opt_label, '#')
364 ->withEngagedState($opt_value === $set_value)
365 ->withOnLoadCode(
366 static fn($id): string =>
367 "il.UI.Input.Viewcontrols.Mode.init(
368 document.getElementById('{$id}'),
369 '{$opt_value}',
370 '{$container_submit_signal}',
371 );"
372 );
373 }
374 $tpl->setVariable('BUTTONS', $default_renderer->render($out));
375 $tpl->setVariable('VALUE', $set_value);
376 $tpl->setVariable("NAME", $component->getName());
377 $tpl->setVariable("ARIA_LABEL", $this->txt(self::DEFAULT_MODE_LABEL));
378
379 $id = $this->bindJavaScript($component);
380 return $tpl->get();
381 }
382
386 public function registerResources(ResourceRegistry $registry): void
387 {
388 parent::registerResources($registry);
389 $registry->register('assets/js/dropdown.js');
390 $registry->register('assets/js/input.viewcontrols.min.js');
391 }
392}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$out
Definition: buildRTE.php:24
This implements commonalities between inputs.
Definition: Input.php:43
getName()
The name of the input as used in HTML.
Definition: Input.php:192
getValue()
Get the value that is displayed in the input client side.
Definition: Input.php:89
sliceRangesToVisibleEntries(array $ranges, int $current, int $number_of_visible_entries)
Definition: Renderer.php:205
renderMode(Mode $component, RendererInterface $default_renderer)
Definition: Renderer.php:352
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:386
renderFieldSelection(FieldSelection $component, RendererInterface $default_renderer)
Definition: Renderer.php:59
renderPagination(Pagination $component, RendererInterface $default_renderer)
Definition: Renderer.php:228
render(Component\Component $component, RendererInterface $default_renderer)
Definition: Renderer.php:38
renderSortation(Sortation $component, RendererInterface $default_renderer)
Definition: Renderer.php:120
cannotHandleComponent(Component $component)
This method MUST be called by derived component renderers, if.
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
Groups are a special kind of input because they are a monoid operation.
Definition: Group.php:38
withAdditionalOnLoadCode(Closure $binder)
Add some onload-code to the component instead of replacing the existing one.
Registry for resources required by rendered output like Javascript or CSS.
register(string $name)
Add a dependency.
An entity that renders components to a string output.
Definition: Renderer.php:31
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Factory.php:21