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 
32 
37 {
41  public function render(Component\Component $component, RendererInterface $default_renderer): string
42  {
43  $this->checkComponent($component);
44 
45  // If the modal is rendered async, we just create a fake container which will be
46  // replaced by the modal upon successful ajax request
48  if ($component->getAsyncRenderUrl()) {
49  return $this->renderAsync($component);
50  }
51 
52  if ($component instanceof Component\Modal\Interruptive) {
53  return $this->renderInterruptive($component, $default_renderer);
54  } elseif ($component instanceof Component\Modal\RoundTrip) {
55  return $this->renderRoundTrip($component, $default_renderer);
56  } elseif ($component instanceof Component\Modal\Lightbox) {
57  return $this->renderLightbox($component, $default_renderer);
58  }
59  throw new \LogicException(self::class . " cannot render component '" . get_class($component) . "'.");
60  }
61 
65  public function registerResources(ResourceRegistry $registry): void
66  {
67  parent::registerResources($registry);
68  $registry->register('./src/UI/templates/js/Modal/modal.js');
69  }
70 
72  {
73  $show = $modal->getShowSignal();
74  $close = $modal->getCloseSignal();
75 
76  $replace = "";
77  if ($modal instanceof Component\Modal\RoundTrip) {
78  $replace = $modal->getReplaceSignal();
79  }
80 
81  $options = array(
82  'ajaxRenderUrl' => $modal->getAsyncRenderUrl(),
83  'keyboard' => $modal->getCloseWithKeyboard()
84  );
85  // ATTENTION, ATTENTION:
86  // with(Additional)OnLoadCode opens a wormhole into the future, where some unspecified
87  // entity magically created an id for the component that can be used to refer to it
88  // via javascript.
89  // This replaced a pattern, where an id was created manually and the java script
90  // code was manually inserted to the (now internal) js-binding of the
91  // AbstractComponentRenderer. (see commit 192144fd1f0e040cadc0149c3dc15fbc4b67858e).
92  // The wormhole solution is considered superior over the manual creation of ids because:
93  // * withAdditionalOnLoadCode introduces no new principles to the UI framework but reuses
94  // an existing one
95  // * withAdditionalOnLoadCode does not require it to expose internals (js-binding) from
96  // the AbstractComponentRenderer and thus does have less coupling
97  // * withAdditionalOnLoadCode allows the framework to decide, when ids are actually
98  // created
99  // * since withAdditionalOnLoadCode refers to some yet unknown future, it disencourages
100  // tempering with the id _here_.
101  return $modal->withAdditionalOnLoadCode(function ($id) use ($show, $close, $options, $replace): string {
102  $options["url"] = "#$id";
103  $options = json_encode($options);
104  $code =
105  "$(document).on('$show', function(event, signalData) { il.UI.modal.showModal('$id', $options, signalData);});" .
106  "$(document).on('$close', function() { il.UI.modal.closeModal('$id');});";
107  if ($replace != "") {
108  $code .= "$(document).on('$replace', function(event, signalData) { il.UI.modal.replaceFromSignal('$id', signalData);});";
109  }
110  return $code;
111  });
112  }
113 
114  protected function renderAsync(Component\Modal\Modal $modal): string
115  {
116  $modal = $this->registerSignals($modal);
117  $id = $this->bindJavaScript($modal);
118  return "<span id='$id'></span>";
119  }
120 
121  protected function renderInterruptive(
123  RendererInterface $default_renderer
124  ): string {
125  $tpl = $this->getTemplate('tpl.interruptive.html', true, true);
126  $modal = $this->registerSignals($modal);
127  $id = $this->bindJavaScript($modal);
128  $tpl->setVariable('ID', $id);
129  $value = $modal->getFormAction();
130  $tpl->setVariable('FORM_ACTION', $value);
131  $tpl->setVariable('TITLE', $modal->getTitle());
132  $tpl->setVariable('MESSAGE', $modal->getMessage());
133 
134  $standard_items = $this->renderInterruptiveItemsByClass(
135  Component\Modal\InterruptiveItem\Standard::class,
136  $modal->getAffectedItems(),
137  $default_renderer
138  );
139  if ($standard_items) {
140  $tpl->setCurrentBlock('with_standard_items');
141  $tpl->setVariable('STANDARD_ITEMS', $standard_items);
142  }
143 
144  $key_value_items = $this->renderInterruptiveItemsByClass(
145  Component\Modal\InterruptiveItem\KeyValue::class,
146  $modal->getAffectedItems(),
147  $default_renderer
148  );
149  if ($key_value_items) {
150  $tpl->setCurrentBlock('with_key_value_items');
151  $tpl->setVariable('KEY_VALUE_ITEMS', $key_value_items);
152  }
153 
154  $tpl->setVariable('ACTION_BUTTON_LABEL', $modal->getActionButtonLabel() ?? $this->txt('delete'));
155  $tpl->setVariable('CANCEL_BUTTON_LABEL', $modal->getCancelButtonLabel() ?? $this->txt('cancel'));
156  $tpl->setVariable('CLOSE_LABEL', $modal->getCancelButtonLabel() ?? $this->txt('cancel'));
157 
158  return $tpl->get();
159  }
160 
168  protected function renderInterruptiveItemsByClass(
169  string $class_name,
170  array $items,
171  RendererInterface $default_renderer
172  ): string {
173  $items_of_class = array_filter(
174  $items,
175  fn($i) => $i instanceof $class_name
176  );
177  $rendered_items = '';
178  foreach ($items_of_class as $item) {
179  $rendered_items .= $default_renderer->render($item);
180  }
181  return $rendered_items;
182  }
183 
184  protected function renderRoundTrip(Component\Modal\RoundTrip $modal, RendererInterface $default_renderer): string
185  {
186  $tpl = $this->getTemplate('tpl.roundtrip.html', true, true);
188  $modal = $this->registerSignals($modal);
189  $id = $this->bindJavaScript($modal);
190  $tpl->setVariable('ID', $id);
191  $tpl->setVariable('TITLE', $modal->getTitle());
192  $tpl->setVariable('CLOSE_LABEL', $this->txt('close'));
193 
194  foreach ($modal->getContent() as $content) {
195  $tpl->setCurrentBlock('with_content');
196  $tpl->setVariable('CONTENT', $default_renderer->render($content));
197  $tpl->parseCurrentBlock();
198  }
199  foreach ($modal->getActionButtons() as $button) {
200  $tpl->setCurrentBlock('with_buttons');
201  $tpl->setVariable('BUTTON', $default_renderer->render($button));
202  $tpl->parseCurrentBlock();
203  }
204 
205  // only render form if it contains any inputs (for now).
206  if (!empty($modal->getInputs())) {
207  // render form in modal body.
208  $tpl->setCurrentBlock('with_form');
209  $tpl->setVariable('FORM', $default_renderer->render($modal->getForm()));
210  $tpl->parseCurrentBlock();
211 
212  // render submit in modal footer.
213  $submit = $this->getUIFactory()->button()->standard(
214  $modal->getSubmitLabel() ?? $this->txt('save'),
215  ''
216  )->withOnClick($modal->getForm()->getSubmitSignal());
217  $tpl->setCurrentBlock('with_submit');
218  $tpl->setVariable('SUBMIT_BUTTON', $default_renderer->render($submit));
219  $tpl->parseCurrentBlock();
220  }
221 
222  $tpl->setVariable('CANCEL_BUTTON_LABEL', $modal->getCancelButtonLabel() ?? $this->txt('cancel'));
223  return $tpl->get();
224  }
225 
226  protected function renderLightbox(Component\Modal\Lightbox $modal, RendererInterface $default_renderer): string
227  {
228  $tpl = $this->getTemplate('tpl.lightbox.html', true, true);
229  $modal = $this->registerSignals($modal);
230  $id = $this->bindJavaScript($modal);
231  $tpl->setVariable('ID', $id);
232  $id_carousel = "{$id}_carousel";
233  $pages = $modal->getPages();
234  $tpl->setVariable('TITLE', $pages[0]->getTitle());
235  $tpl->setVariable('ID_CAROUSEL', $id_carousel);
236  $tpl->setVariable('CLOSE_LABEL', $this->txt('close'));
237  $tpl->setVariable('COLOR_SCHEME', $modal->getScheme());
238 
239  if (count($pages) > 1) {
240  $tpl->setCurrentBlock('has_indicators');
241  foreach ($pages as $index => $page) {
242  $tpl->setCurrentBlock('indicators');
243  $tpl->setVariable('INDEX', $index);
244  $tpl->setVariable('CLASS_ACTIVE', ($index == 0) ? 'active' : '');
245  $tpl->setVariable('ID_CAROUSEL2', $id_carousel);
246  $tpl->parseCurrentBlock();
247  }
248  }
249  $first = true;
250  foreach ($pages as $page) {
251  $this->renderPage($page, $first, $tpl, $default_renderer);
252  $first = false;
253  }
254  if (count($pages) > 1) {
255  $tpl->setCurrentBlock('controls');
256  $tpl->setVariable('ID_CAROUSEL3', $id_carousel);
257  $tpl->parseCurrentBlock();
258  }
259  $tpl->setVariable('ID_CAROUSEL4', $id_carousel);
260  return $tpl->get();
261  }
262 
266  protected function getComponentInterfaceName(): array
267  {
268  return array(
269  Component\Modal\Interruptive::class,
270  Component\Modal\RoundTrip::class,
271  Component\Modal\Lightbox::class,
272  );
273  }
274 
275  private function renderPage(LightboxPage $page, bool $first, Template $tpl, RendererInterface $default_renderer): void
276  {
277  $vertical = false;
278  $components = [$page->getComponent()];
279  if ($page instanceof LightboxTextPage) {
280  $tpl->setCurrentBlock('pages');
281  $tpl->touchBlock('page_type_text');
282  $tpl->parseCurrentBlock();
283  } elseif ($page instanceof LightboxCardPage) {
284  $components = array_merge(
285  $page->getComponent()->getSections(),
286  $page->getComponent()->getHiddenSections()
287  );
288  $vertical = true;
289  }
290  $tpl->setCurrentBlock('pages');
291  $tpl->setVariable('CLASS_ACTIVE', $first ? 'active' : '');
292  $tpl->setVariable('ORIENTATION', $vertical ? 'item-vertical' : '');
293  $tpl->setVariable('TITLE2', htmlentities($page->getTitle(), ENT_QUOTES, 'UTF-8'));
294  $tpl->setVariable('CONTENT', $default_renderer->render($components));
295  if ($page instanceof LightboxDescriptionEnabledPage) {
296  $tpl->setVariable('DESCRIPTION', $page->getDescription());
297  }
298  $tpl->parseCurrentBlock();
299  }
300 }
Registry for resources required by rendered output like Javascript or CSS.
checkComponent(Component $component)
Check if a given component fits this renderer and throw if that is not the case. ...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Factory.php:21
renderPage(LightboxPage $page, bool $first, Template $tpl, RendererInterface $default_renderer)
Definition: Renderer.php:275
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
txt(string $id)
Get a text from the language file.
setCurrentBlock(string $name)
Set the block to work on.
getTitle()
Get the title of this page, displayed as title in the lightbox modal.
setVariable(string $name, $value)
Set a variable in the current block.
renderLightbox(Component\Modal\Lightbox $modal, RendererInterface $default_renderer)
Definition: Renderer.php:226
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:65
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
renderInterruptive(Component\Modal\Interruptive $modal, RendererInterface $default_renderer)
Definition: Renderer.php:121
register(string $name)
Add a dependency.
getComponent()
Get the component representing the media item to be displayed in the modals content section...
parseCurrentBlock()
Parse the block that is currently worked on.
render(Component $component, Renderer $default_renderer)
Render the component if possible and delegate additional rendering to the default_renderer.
renderAsync(Component\Modal\Modal $modal)
Definition: Renderer.php:114
touchBlock(string $name)
Touch a block without working further on it.
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
renderInterruptiveItemsByClass(string $class_name, array $items, RendererInterface $default_renderer)
Filters items by provided class, and renders only those.
Definition: Renderer.php:168
registerSignals(Component\Modal\Modal $modal)
Definition: Renderer.php:71
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.