ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
Renderer.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
34 use ILIAS\Data\URI;
36 use LogicException;
37 
39 {
40  public const BLOCK_MAINBAR_ENTRIES = 'trigger_item';
41  public const BLOCK_MAINBAR_TOOLS = 'tool_trigger_item';
42  public const BLOCK_METABAR_ENTRIES = 'meta_element';
43 
44  private array $trigger_signals = [];
45 
49  public function render(Component\Component $component, RendererInterface $default_renderer): string
50  {
51  $this->checkComponent($component);
52 
53  if ($component instanceof MainBar) {
54  return $this->renderMainbar($component, $default_renderer);
55  }
56  if ($component instanceof MetaBar) {
57  return $this->renderMetabar($component, $default_renderer);
58  }
59  if ($component instanceof Footer) {
60  return $this->renderFooter($component, $default_renderer);
61  }
62  if ($component instanceof ModeInfo) {
63  return $this->renderModeInfo($component, $default_renderer);
64  }
65  if ($component instanceof Component\MainControls\SystemInfo) {
66  return $this->renderSystemInfo($component, $default_renderer);
67  }
68  throw new LogicException("Cannot render: " . get_class($component));
69  }
70 
71  protected function calculateMainBarTreePosition($pos, $slate)
72  {
73  if (!$slate instanceof Slate && !$slate instanceof MainBar) {
74  return $slate;
75  }
76  return $slate
77  ->withMainBarTreePosition($pos)
78  ->withMappedSubNodes(
79  function ($num, $slate, $is_tool = false) use ($pos) {
80  if ($is_tool) {
81  $pos = 'T';
82  }
83  return $this->calculateMainBarTreePosition("$pos:$num", $slate);
84  }
85  );
86  }
87 
88  protected function renderToolEntry(
89  string $entry_id,
90  string $mb_id,
91  MainBar $component,
92  UITemplateWrapper $tpl,
93  RendererInterface $default_renderer
94  ): string {
95  $hidden = $component->getInitiallyHiddenToolIds();
96  $close_buttons = $component->getCloseButtons();
97 
98  $is_removeable = array_key_exists($entry_id, $close_buttons);
99  $is_hidden = in_array($entry_id, $hidden);
100 
101  if ($is_removeable) {
102  $trigger_signal = $component->getTriggerSignal($mb_id, $component::ENTRY_ACTION_REMOVE);
103  $this->trigger_signals[] = $trigger_signal;
104  $btn_removetool = $close_buttons[$entry_id]
105  ->withAdditionalOnloadCode(
106  fn($id) => "il.UI.maincontrols.mainbar.addPartIdAndEntry('$mb_id', 'remover', '$id', true);"
107  )
108  ->withOnClick($trigger_signal);
109 
110  $tpl->setCurrentBlock("tool_removal");
111  $tpl->setVariable("REMOVE_TOOL", $default_renderer->render($btn_removetool));
112  $tpl->parseCurrentBlock();
113  }
114 
115  $is_removeable = $is_removeable ? 'true' : 'false';
116  $is_hidden = $is_hidden ? 'true' : 'false';
117  return "il.UI.maincontrols.mainbar.addToolEntry('$mb_id', $is_removeable, $is_hidden, '$entry_id');";
118  }
119 
120  protected function renderMainbarEntry(
121  array $entries,
122  string $block,
123  MainBar $component,
124  UITemplateWrapper $tpl,
125  RendererInterface $default_renderer
126  ): void {
127  $f = $this->getUIFactory();
128  foreach ($entries as $k => $entry) {
129  $button = $entry;
130  $slate = null;
131  $js = '';
132 
133  if ($entry instanceof Slate) {
134  $slate = $entry;
135  $mb_id = $entry->getMainBarTreePosition();
136  $is_tool = $block === static::BLOCK_MAINBAR_TOOLS;
137  if ($is_tool) {
138  $js = $this->renderToolEntry($k, $mb_id, $component, $tpl, $default_renderer);
139  }
140 
141  $trigger_signal = $component->getTriggerSignal($mb_id, $component::ENTRY_ACTION_TRIGGER);
142  $this->trigger_signals[] = $trigger_signal;
143  $button = $f->button()->bulky($entry->getSymbol(), $entry->getName(), '#')
144  ->withOnClick($trigger_signal);
145  } else {
146  //add Links/Buttons as toplevel entries
147  $pos = array_search($k, array_keys($entries));
148  $mb_id = '0:' . $pos;
149  $is_tool = false;
150  }
151 
152  $button = $button->withAdditionalOnLoadCode(
153  function ($id) use ($js, $mb_id, $k, $is_tool): string {
154  $add_as_tool = $is_tool ? 'true' : 'false';
155  $js .= "
156  il.UI.maincontrols.mainbar.addPartIdAndEntry('$mb_id', 'triggerer', '$id', $add_as_tool);
157  il.UI.maincontrols.mainbar.addMapping('$k','$mb_id');
158  ";
159  return $js;
160  }
161  )->withAriaRole(IBulky::MENUITEM);
162 
163  $tpl->setCurrentBlock($block);
164  $tpl->setVariable("BUTTON", $default_renderer->render($button));
165  $tpl->parseCurrentBlock();
166 
167  if ($slate) {
168  $entry = $entry->withAriaRole(ISlate::MENU);
169 
170  $tpl->setCurrentBlock("slate_item");
171  $tpl->setVariable("SLATE", $default_renderer->render($entry));
172  $tpl->parseCurrentBlock();
173  }
174  }
175  }
176 
177  protected function renderMainbar(MainBar $component, RendererInterface $default_renderer): string
178  {
179  $f = $this->getUIFactory();
180  $tpl = $this->getTemplate("tpl.mainbar.html", true, true);
181 
182  $tpl->setVariable("ARIA_LABEL", $this->txt('mainbar_aria_label'));
183  $more_btn_label = $this->txt('mainbar_more_label');
187  $more_slate = $f->mainControls()->slate()->combined(
188  $more_btn_label,
189  $f->symbol()->glyph()->more()
190  );
191  $more_slate = $more_slate->withAriaRole(ISlate::MENU);
192  $component = $component->withAdditionalEntry(
193  '_mb_more_entry',
194  $more_slate
195  );
196  $component = $this->calculateMainBarTreePosition("0", $component);
197 
198  $mb_entries = [
199  static::BLOCK_MAINBAR_ENTRIES => $component->getEntries(),
200  static::BLOCK_MAINBAR_TOOLS => $component->getToolEntries()
201  ];
202 
203  foreach ($mb_entries as $block => $entries) {
204  $this->renderMainbarEntry(
205  $entries,
206  $block,
207  $component,
208  $tpl,
209  $default_renderer
210  );
211  }
212 
213  //tools-section trigger
214  if (count($component->getToolEntries()) > 0) {
215  $btn_tools = $component->getToolsButton()
216  ->withOnClick($component->getToggleToolsSignal());
217 
218  $tpl->setCurrentBlock("tools_trigger");
219  $tpl->setVariable("BUTTON", $default_renderer->render($btn_tools));
220  $tpl->parseCurrentBlock();
221  }
222 
223  //disengage all, close slates
224  $btn_disengage = $f->button()->bulky($f->symbol()->glyph()->collapseHorizontal("#"), $this->txt('close'), "#")
225  ->withOnClick($component->getDisengageAllSignal());
226  $tpl->setVariable("CLOSE_SLATES", $default_renderer->render($btn_disengage));
227 
228 
229  $id = $this->bindMainbarJS($component);
230  $tpl->setVariable('ID', $id);
231 
232  return $tpl->get();
233  }
234 
235  protected function renderMetabar(MetaBar $component, RendererInterface $default_renderer): string
236  {
237  $f = $this->getUIFactory();
238  $tpl = $this->getTemplate("tpl.metabar.html", true, true);
239  $active = '';
240  $signals = [
241  'entry' => $component->getEntryClickSignal(),
242  'close_slates' => $component->getDisengageAllSignal()
243  ];
244  $entries = $component->getEntries();
245 
246  $more_label = $this->txt('show_more');
247  $more_symbol = $f->symbol()->glyph()->disclosure()
248  ->withCounter($f->counter()->novelty(0))
249  ->withCounter($f->counter()->status(0));
253  $more_slate = $f->mainControls()->slate()->combined($more_label, $more_symbol);
254  $more_slate = $more_slate->withAriaRole(ISlate::MENU);
255  $entries[] = $more_slate;
256 
258  $tpl,
259  $default_renderer,
260  $signals['entry'],
261  static::BLOCK_METABAR_ENTRIES,
262  $entries,
263  $active
264  );
265 
266  $component = $component->withOnLoadCode(
267  function ($id) use ($signals) {
268  $entry_signal = $signals['entry'];
269  $close_slates_signal = $signals['close_slates'];
270  return "
271  il.UI.maincontrols.metabar.init('$id');
272  il.UI.maincontrols.metabar.get('$id').registerSignals(
273  '$entry_signal',
274  '$close_slates_signal',
275  );
276  il.UI.maincontrols.metabar.get('$id').init();
277  window.addEventListener(
278  'resize',
279  ()=>{il.UI.maincontrols.metabar.get('$id').init()}
280  );
281  ";
282  }
283  );
284  $tpl->setVariable('ARIA_LABEL', $this->txt('metabar_aria_label'));
285 
286  $id = $this->bindJavaScript($component);
287  $tpl->setVariable('ID', $id);
288  return $tpl->get();
289  }
290 
291  protected function renderModeInfo(ModeInfo $component, RendererInterface $default_renderer): string
292  {
293  $tpl = $this->getTemplate("tpl.mode_info.html", true, true);
294  $tpl->setVariable('MODE_TITLE', $component->getModeTitle());
295  $base_URI = $component->getCloseAction()->getBaseURI();
296  $query = $component->getCloseAction()->getQuery();
297  $action = $base_URI . '?' . $query;
298  $close = $this->getUIFactory()->symbol()->glyph()->close($action);
299  $tpl->setVariable('CLOSE_GLYPH', $default_renderer->render($close));
300 
301  return $tpl->get();
302  }
303 
304  protected function renderSystemInfo(
305  Component\MainControls\SystemInfo $component,
306  RendererInterface $default_renderer
307  ): string {
308  $tpl = $this->getTemplate("tpl.system_info.html", true, true);
309  $tpl->setVariable('HEADLINE', $component->getHeadLine());
310  $tpl->setVariable('BODY', $component->getInformationText());
311  $tpl->setVariable('DENOTATION', $component->getDenotation());
312  switch ($component->getDenotation()) {
315  $tpl->setVariable('LIVE', 'aria-live="polite"');
316  break;
318  $tpl->setVariable('ROLE', 'role="alert"');
319  break;
320  }
321  if ($component->isDismissable()) {
322  $close = $this->getUIFactory()->symbol()->glyph()->close("#");
323  $signal = $component->getCloseSignal();
324  $close = $close->withOnClick($signal);
325  $tpl->setVariable('CLOSE_BUTTON', $default_renderer->render($close));
326  $tpl->setVariable('CLOSE_URI', (string) $component->getDismissAction());
327  $component = $component->withAdditionalOnLoadCode(fn($id) => "$(document).on('$signal', function() { il.UI.maincontrols.system_info.close('$id'); });");
328  }
329 
330  $more = $this->getUIFactory()->symbol()->glyph()->more("#");
331  $tpl->setVariable('MORE_BUTTON', $default_renderer->render($more));
332 
333  $component = $component->withAdditionalOnLoadCode(fn($id) => "il.UI.maincontrols.system_info.init('$id')");
334 
335  $id = $this->bindJavaScript($component);
336  $tpl->setVariable('ID', $id);
337  $tpl->setVariable('ID_HEADLINE', $id . "_headline");
338  $tpl->setVariable('ID_DESCRIPTION', $id . "_description");
339 
340  return $tpl->get();
341  }
342 
343 
344  protected function renderTriggerButtonsAndSlates(
345  UITemplateWrapper $tpl,
346  RendererInterface $default_renderer,
347  Signal $entry_signal,
348  string $block,
349  array $entries,
350  string $active = null
351  ): void {
352  foreach ($entries as $id => $entry) {
353  $use_block = $block;
354  $engaged = (string) $id === $active;
355  $slate = null;
356  if ($entry instanceof Slate) {
357  $f = $this->getUIFactory();
358  $secondary_signal = $entry->getToggleSignal();
359  $clickable = $f->button()->bulky($entry->getSymbol(), $entry->getName(), '#')
361  ->withOnClick($entry_signal)
362  ->appendOnClick($secondary_signal)
363  ->withAriaRole(IBulky::MENUITEM);
364 
365  $slate = $entry;
366  } elseif ($entry instanceof IBulky) {
367  $clickable = $entry;
368  $clickable = $clickable->withAriaRole(IBulky::MENUITEM);
369  $slate = null;
370  } else {
371  $clickable = $entry;
372  }
373 
374  $clickable_html = $default_renderer->render($clickable);
375 
376  if ($slate) {
377  $tpl->setCurrentBlock("slate_item");
378  $tpl->setVariable("SLATE", $default_renderer->render($slate));
379  $tpl->parseCurrentBlock();
380  }
381 
382  $tpl->setCurrentBlock($use_block);
383  $tpl->setVariable("BUTTON", $clickable_html);
384  $tpl->parseCurrentBlock();
385  }
386  }
387 
388  protected function bindMainbarJS(MainBar $component): ?string
389  {
390  $trigger_signals = $this->trigger_signals;
391 
392  $inititally_active = $component->getActive();
393 
394  $component = $component->withOnLoadCode(
395  function ($id) use ($component, $trigger_signals, $inititally_active): string {
396  $disengage_all_signal = $component->getDisengageAllSignal();
397  $tools_toggle_signal = $component->getToggleToolsSignal();
398 
399  $js = "il.UI.maincontrols.mainbar.addTriggerSignal('$disengage_all_signal');";
400  $js .= "il.UI.maincontrols.mainbar.addTriggerSignal('$tools_toggle_signal');";
401 
402  foreach ($trigger_signals as $signal) {
403  $js .= "il.UI.maincontrols.mainbar.addTriggerSignal('$signal');";
404  }
405 
406  foreach ($component->getToolEntries() as $k => $tool) {
407  $signal = $component->getEngageToolSignal($k);
408  $js .= "il.UI.maincontrols.mainbar.addTriggerSignal('$signal');";
409  }
410 
411  $js .= "
412  window.addEventListener('resize', il.UI.maincontrols.mainbar.adjustToScreenSize);
413  il.UI.maincontrols.mainbar.init('$inititally_active');
414  ";
415  return $js;
416  }
417  );
418 
419  return $this->bindJavaScript($component);
420  }
421 
422  protected function renderFooter(Footer $component, RendererInterface $default_renderer): string
423  {
424  $tpl = $this->getTemplate("tpl.footer.html", true, true);
425  $links = $component->getLinks();
426  $modalsWithTriggers = $component->getModals();
427  $links = array_merge($links, array_column($modalsWithTriggers, 1));
428 
429  if ($links) {
430  $link_list = $this->getUIFactory()->listing()->unordered($links);
431  $tpl->setVariable('LINKS', $default_renderer->render($link_list));
432  }
433 
434  if ($modalsWithTriggers !== []) {
435  $tpl->setVariable('MODALS', $default_renderer->render(
436  array_column($modalsWithTriggers, 0)
437  ));
438  }
439 
440  $tpl->setVariable('TEXT', $component->getText());
441 
442  $perm_url = $component->getPermanentURL();
443  if ($perm_url instanceof URI) {
444  $code = function (string $id) use ($perm_url): string {
445  $id = $this->jsonEncode($id);
446  $perm_url = $this->jsonEncode((string) $perm_url);
447 
448  return "document.getElementById($id).addEventListener('click', e => il.Footer.permalink.copyText($perm_url)
449  .then(() => il.Footer.permalink.showTooltip(e.target.nextElementSibling, 5000)));";
450  };
451  $button = $this->getUIFactory()->button()->shy($this->txt('copy_perma_link'), '')->withAdditionalOnLoadCode($code);
452  $tpl->setVariable('PERMANENT', $default_renderer->render($button));
453  $tpl->setVariable('PERMANENT_TOOLTIP', $this->txt('perma_link_copied'));
454  }
455  return $tpl->get();
456  }
457 
461  public function registerResources(ResourceRegistry $registry): void
462  {
463  parent::registerResources($registry);
464  $registry->register('./src/UI/templates/js/MainControls/dist/mainbar.js');
465  $registry->register('./src/UI/templates/js/MainControls/dist/maincontrols.min.js');
466  $registry->register('./src/GlobalScreen/Client/dist/GS.js');
467  $registry->register('./src/UI/templates/js/MainControls/system_info.js');
468  $registry->register('./src/UI/templates/js/MainControls/dist/footer.min.js');
469  }
470 
474  protected function getComponentInterfaceName(): array
475  {
476  return array(
477  MetaBar::class,
478  MainBar::class,
479  Footer::class,
480  ModeInfo::class,
481  Component\MainControls\SystemInfo::class
482  );
483  }
484 
485  private function jsonEncode($value): string
486  {
487  return json_encode($value, JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_THROW_ON_ERROR);
488  }
489 }
Registry for resources required by rendered output like Javascript or CSS.
This describes the MainBar.
Definition: MainBar.php:33
checkComponent(Component $component)
Check if a given component fits this renderer and throw if that is not the case. ...
getDisengageAllSignal()
This signal disengages all slates when triggered.
getInitiallyHiddenToolIds()
There are tools that are rendered invisible before first activation.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
renderToolEntry(string $entry_id, string $mb_id, MainBar $component, UITemplateWrapper $tpl, RendererInterface $default_renderer)
Definition: Renderer.php:88
txt(string $id)
Get a text from the language file.
getEntryClickSignal()
The Signal is triggered when any Entry is being clicked.
renderFooter(Footer $component, RendererInterface $default_renderer)
Definition: Renderer.php:422
getDisengageAllSignal()
This signal disengages all slates when triggered.
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
renderSystemInfo(Component\MainControls\SystemInfo $component, RendererInterface $default_renderer)
Definition: Renderer.php:304
renderModeInfo(ModeInfo $component, RendererInterface $default_renderer)
Definition: Renderer.php:291
The scope of this class is split ilias-conform URI&#39;s into components.
Definition: URI.php:18
withAdditionalEntry(string $id, $entry)
Append an entry.
getToolsButton()
Returns the button of the tools-trigger.
register(string $name)
Add a dependency.
This describes the MetaBar.
Definition: MetaBar.php:32
renderMainbarEntry(array $entries, string $block, MainBar $component, UITemplateWrapper $tpl, RendererInterface $default_renderer)
Definition: Renderer.php:120
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
render(Component\Component $component, RendererInterface $default_renderer)
Definition: Renderer.php:49
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Factory.php:21
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:461
getToggleToolsSignal()
Signal to toggle the tools-section.
getEngageToolSignal(string $tool_id)
Signal to engage a tool from outside the MainBar.
getCloseButtons()
Buttons to close tools; maybe configure with callback.
This describes the Footer.
Definition: Footer.php:32
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Combined.php:20
renderTriggerButtonsAndSlates(UITemplateWrapper $tpl, RendererInterface $default_renderer, Signal $entry_signal, string $block, array $entries, string $active=null)
Definition: Renderer.php:344
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.