ILIAS  trunk Revision v11.0_alpha-1713-gd8962da2f67
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
Renderer.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
27 use 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  foreach ($ranges as $idx => $range) {
256  if (in_array($range, $entries)) {
257  $signal = clone $internal_signal;
258  $signal->addOption('offset', $range->getStart());
259  $signal->addOption('limit', $limit);
260  $tpl->setCurrentBlock("entry");
261  $entry = $ui_factory->button()->shy((string) ($idx + 1), '#')->withOnClick($signal);
262  if ($idx === $current) {
263  $entry = $entry->withEngagedState(true);
264  }
265  $tpl->setVariable("ENTRY", $default_renderer->render($entry));
266  $tpl->parseCurrentBlock();
267  } else {
268  if ($idx === 1 || $idx === count($ranges) - 2) {
269  $tpl->setCurrentBlock("entry");
270  $tpl->touchBlock("spacer");
271  $tpl->parseCurrentBlock();
272  }
273  }
274  }
275 
276  $signal = null;
277  if ($current > 0 && count($entries) > 1) {
278  $range = $ranges[$current - 1];
279  $signal = clone $internal_signal;
280  $signal->addOption('offset', $range->getStart());
281  $signal->addOption('limit', $limit);
282  }
283  $btn_left = $ui_factory->button()->shy('', $signal ?? '#')
284  ->withSymbol($ui_factory->symbol()->glyph()->back())
285  ->withUnavailableAction($signal === null);
286  $tpl->setVariable("LEFT_ROCKER", $default_renderer->render($btn_left));
287 
288  $signal = null;
289  if ($current < count($ranges) - 1) {
290  $range = $ranges[$current + 1];
291  $signal = clone $internal_signal;
292  $signal->addOption('offset', $range->getStart());
293  $signal->addOption('limit', $limit);
294  }
295  $btn_right = $ui_factory->button()->shy('', $signal ?? '#')
296  ->withSymbol($ui_factory->symbol()->glyph()->next())
297  ->withUnavailableAction($signal === null);
298  $tpl->setVariable("RIGHT_ROCKER", $default_renderer->render($btn_right));
299  }
300 
301  foreach ($component->getLimitOptions() as $option) {
302  $signal = clone $internal_signal;
303  $signal->addOption('offset', $offset);
304  $signal->addOption('limit', (string) $option);
305  $option_label = $option === \PHP_INT_MAX ? $this->txt('ui_pagination_unlimited') : (string) $option;
306 
307  $item = $ui_factory->button()->shy($option_label, '#')
308  ->withOnClick($signal);
309  $tpl->setCurrentBlock("option_limit");
310  $tpl->setVariable("OPTION_LIMIT", $default_renderer->render($item));
311  if ($option === $limit) {
312  $tpl->touchBlock("selected");
313  $tpl->setCurrentBlock("option_limit");
314  }
315  $tpl->parseCurrentBlock();
316  }
317 
318  if ($container_submit_signal = $component->getOnChangeSignal()) {
319  $component = $component->withAdditionalOnLoadCode(
320  fn($id) => "il.UI.Input.Viewcontrols.Pagination.init(
321  document.getElementById('{$id}'),
322  '{$internal_signal}',
323  '{$container_submit_signal}',
324  );"
325  );
326  }
327 
328  $component = $component->withAdditionalOnLoadCode(
329  fn($id) => "
330  il.UI.dropdown.init(
331  document.getElementById('{$id}').querySelector(
332  '.dropdown.il-viewcontrol-pagination__num-of-items'
333  )
334  );
335  "
336  );
337  $id = $this->bindJavaScript($component);
338 
339  $tpl->setVariable('ID', $id);
340  $tpl->setVariable("ID_MENU_OFFSET", $id . '_ctrl_offset');
341  $tpl->setVariable("ARIA_LABEL_OFFSET", $this->txt(self::DEFAULT_DROPDOWN_LABEL_OFFSET));
342  $tpl->setVariable("ID_MENU_LIMIT", $id . '_ctrl_limit');
343  $tpl->setVariable("ARIA_LABEL_LIMIT", $this->txt(self::DEFAULT_DROPDOWN_LABEL_LIMIT));
344 
345  $tpl->setVariable(
346  "VALUES",
347  $default_renderer->render(
348  $component->getInputGroup()
349  )
350  );
351 
352  return $tpl->get();
353  }
354 
355 
356  protected function renderMode(Mode $component, RendererInterface $default_renderer): string
357  {
358  $tpl = $this->getTemplate("tpl.viewcontrol_mode.html", true, true);
359  $ui_factory = $this->getUIFactory();
360 
361  $container_submit_signal = $component->getOnChangeSignal();
362  $options = $component->getOptions();
363  $set_value = $component->getValue() ?? array_key_first($options);
364 
365  $out = [];
366  foreach ($options as $opt_value => $opt_label) {
367  $out[] = $ui_factory->button()->standard($opt_label, '#')
368  ->withEngagedState($opt_value === $set_value)
369  ->withOnLoadCode(
370  static fn($id): string =>
371  "il.UI.Input.Viewcontrols.Mode.init(
372  document.getElementById('{$id}'),
373  '{$opt_value}',
374  '{$container_submit_signal}',
375  );"
376  );
377  }
378  $tpl->setVariable('BUTTONS', $default_renderer->render($out));
379  $tpl->setVariable('VALUE', $set_value);
380  $tpl->setVariable("NAME", $component->getName());
381  $tpl->setVariable("ARIA_LABEL", $this->txt(self::DEFAULT_MODE_LABEL));
382 
383  $id = $this->bindJavaScript($component);
384  return $tpl->get();
385  }
386 
390  public function registerResources(ResourceRegistry $registry): void
391  {
392  parent::registerResources($registry);
393  $registry->register('assets/js/dropdown.js');
394  $registry->register('assets/js/input.viewcontrols.min.js');
395  }
396 }
renderSortation(Sortation $component, RendererInterface $default_renderer)
Definition: Renderer.php:120
This implements commonalities between inputs.
Definition: Input.php:42
Registry for resources required by rendered output like Javascript or CSS.
txt(string $id)
Get a text from the language file.
render(Component\Component $component, RendererInterface $default_renderer)
Definition: Renderer.php:38
renderMode(Mode $component, RendererInterface $default_renderer)
Definition: Renderer.php:356
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
$out
Definition: buildRTE.php:24
cannotHandleComponent(Component $component)
This method MUST be called by derived component renderers, if.
sliceRangesToVisibleEntries(array $ranges, int $current, int $number_of_visible_entries)
Definition: Renderer.php:205
register(string $name)
Add a dependency.
getValue()
Get the value that is displayed in the input client side.
Definition: Input.php:89
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:390
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
withAdditionalOnLoadCode(Closure $binder)
Add some onload-code to the component instead of replacing the existing one.
renderFieldSelection(FieldSelection $component, RendererInterface $default_renderer)
Definition: Renderer.php:59
renderPagination(Pagination $component, RendererInterface $default_renderer)
Definition: Renderer.php:228
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Factory.php:21
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.