ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
Renderer.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
26use ILIAS\UI\Renderer as RendererInterface;
29use ILIAS\UI\Implementation\Component\Input\Container\Form\FormWithoutSubmitButton;
32
37{
41 public function render(Component\Component $component, RendererInterface $default_renderer): string
42 {
43 if (!$component instanceof Component\Modal\Modal) {
44 $this->cannotHandleComponent($component);
45 }
46
48 $component = $this->addTriggererOnLoadCode($component);
49
50 // If the modal is rendered async, we just create a fake container which will be
51 // replaced by the modal upon successful ajax request
52 if ($component->getAsyncRenderUrl()) {
53 return $this->renderAsync($component);
54 }
55
56 if ($component instanceof Component\Modal\Interruptive) {
57 return $this->renderInterruptive($component, $default_renderer);
58 } elseif ($component instanceof Component\Modal\RoundTrip) {
59 return $this->renderRoundTrip($component, $default_renderer);
60 } elseif ($component instanceof Component\Modal\Lightbox) {
61 return $this->renderLightbox($component, $default_renderer);
62 }
63 $this->cannotHandleComponent($component);
64 }
65
69 public function registerResources(ResourceRegistry $registry): void
70 {
71 parent::registerResources($registry);
72 $registry->register('assets/js/modal.min.js');
73 }
74
76 {
77 $show = $modal->getShowSignal();
78 $close = $modal->getCloseSignal();
79
80 $replace = "";
81 if ($modal instanceof Component\Modal\RoundTrip) {
82 $replace = $modal->getReplaceSignal();
83 }
84
85 $options = array(
86 'ajaxRenderUrl' => $modal->getAsyncRenderUrl(),
87 'keyboard' => $modal->getCloseWithKeyboard()
88 );
89 // ATTENTION, ATTENTION:
90 // with(Additional)OnLoadCode opens a wormhole into the future, where some unspecified
91 // entity magically created an id for the component that can be used to refer to it
92 // via javascript.
93 // This replaced a pattern, where an id was created manually and the java script
94 // code was manually inserted to the (now internal) js-binding of the
95 // AbstractComponentRenderer. (see commit 192144fd1f0e040cadc0149c3dc15fbc4b67858e).
96 // The wormhole solution is considered superior over the manual creation of ids because:
97 // * withAdditionalOnLoadCode introduces no new principles to the UI framework but reuses
98 // an existing one
99 // * withAdditionalOnLoadCode does not require it to expose internals (js-binding) from
100 // the AbstractComponentRenderer and thus does have less coupling
101 // * withAdditionalOnLoadCode allows the framework to decide, when ids are actually
102 // created
103 // * since withAdditionalOnLoadCode refers to some yet unknown future, it disencourages
104 // tempering with the id _here_.
105 return $modal->withAdditionalOnLoadCode(function ($id) use ($show, $close, $options, $replace): string {
106 $options["url"] = "#$id";
107 $options = json_encode($options);
108 $code =
109 "$(document).on('$show', function(event, signalData) {
110 let modal = document.getElementById('$id');
111 il.UI.modal.showModal(modal, $options, signalData, '$close');
112 });";
113
114 if ($replace != "") {
115 $code .= "$(document).on('$replace', function(event, signalData) {
116 const id = event.target.closest('.c-modal').id;
117 il.UI.core.replaceContent(id, signalData.options.url, 'component');
118 });";
119 }
120 return $code;
121 });
122 }
123
124 protected function renderAsync(Component\Modal\Modal $modal): string
125 {
126 $modal = $this->registerSignals($modal);
127 $id = $this->bindJavaScript($modal);
128 return "<span id='$id'></span>";
129 }
130
131 protected function renderInterruptive(
133 RendererInterface $default_renderer
134 ): string {
135 $tpl = $this->getTemplate('tpl.interruptive.html', true, true);
136 $modal = $this->registerSignals($modal);
137 $id = $this->bindJavaScript($modal);
138 $tpl->setVariable('ID', $id);
139 $value = $modal->getFormAction();
140 $tpl->setVariable('FORM_ACTION', $value);
141 $tpl->setVariable('TITLE', $modal->getTitle());
142 $tpl->setVariable('MESSAGE', $modal->getMessage());
143
144 $standard_items = $this->renderInterruptiveItemsByClass(
145 Component\Modal\InterruptiveItem\Standard::class,
146 $modal->getAffectedItems(),
147 $default_renderer
148 );
149 if ($standard_items) {
150 $tpl->setCurrentBlock('with_standard_items');
151 $tpl->setVariable('STANDARD_ITEMS', $standard_items);
152 }
153
154 $key_value_items = $this->renderInterruptiveItemsByClass(
155 Component\Modal\InterruptiveItem\KeyValue::class,
156 $modal->getAffectedItems(),
157 $default_renderer
158 );
159 if ($key_value_items) {
160 $tpl->setCurrentBlock('with_key_value_items');
161 $tpl->setVariable('KEY_VALUE_ITEMS', $key_value_items);
162 }
163
164 $tpl->setVariable('ACTION_BUTTON_LABEL', $modal->getActionButtonLabel() ?? $this->txt('delete'));
165 $tpl->setVariable('CANCEL_BUTTON_LABEL', $modal->getCancelButtonLabel() ?? $this->txt('cancel'));
166 $tpl->setVariable('CLOSE_LABEL', $modal->getCancelButtonLabel() ?? $this->txt('cancel'));
167
168 return $tpl->get();
169 }
170
179 string $class_name,
180 array $items,
181 RendererInterface $default_renderer
182 ): string {
183 $items_of_class = array_filter(
184 $items,
185 fn($i) => $i instanceof $class_name
186 );
187 $rendered_items = '';
188 foreach ($items_of_class as $item) {
189 $rendered_items .= $default_renderer->render($item);
190 }
191 return $rendered_items;
192 }
193
194 protected function renderRoundTrip(Component\Modal\RoundTrip $modal, RendererInterface $default_renderer): string
195 {
196 $tpl = $this->getTemplate('tpl.roundtrip.html', true, true);
198 $modal = $this->registerSignals($modal);
199 $id = $this->bindJavaScript($modal);
200 $tpl->setVariable('ID', $id);
201 $tpl->setVariable('TITLE', $modal->getTitle());
202 $tpl->setVariable('CLOSE_LABEL', $this->txt('close'));
203
204 foreach ($modal->getContent() as $content) {
205 $tpl->setCurrentBlock('with_content');
206 $tpl->setVariable('CONTENT', $default_renderer->render($content));
208 }
209 foreach ($modal->getActionButtons() as $button) {
210 $tpl->setCurrentBlock('with_buttons');
211 $tpl->setVariable('BUTTON', $default_renderer->render($button));
213 }
214
215 // only render form if it contains any inputs (for now).
216 if (!empty($modal->getInputs())) {
217 // render form in modal body.
218 $tpl->setCurrentBlock('with_form');
219 $tpl->setVariable('FORM', $default_renderer->render($modal->getForm()));
221
222 // render submit in modal footer.
223 $submit = $this->getUIFactory()->button()->standard(
224 $modal->getSubmitLabel() ?? $this->txt('save'),
225 ''
226 )->withOnClick($modal->getForm()->getSubmitSignal());
227 $tpl->setCurrentBlock('with_submit');
228 $tpl->setVariable('SUBMIT_BUTTON', $default_renderer->render($submit));
230 }
231
232 $tpl->setVariable('CANCEL_BUTTON_LABEL', $modal->getCancelButtonLabel() ?? $this->txt('cancel'));
233 return $tpl->get();
234 }
235
236 protected function renderLightbox(Component\Modal\Lightbox $modal, RendererInterface $default_renderer): string
237 {
238 $tpl = $this->getTemplate('tpl.lightbox.html', true, true);
239 $modal = $this->registerSignals($modal);
240 $id = $this->bindJavaScript($modal);
241 $tpl->setVariable('ID', $id);
242 $id_carousel = "{$id}_carousel";
243 $pages = $modal->getPages();
244 $tpl->setVariable('TITLE', $pages[0]->getTitle());
245 $tpl->setVariable('ID_CAROUSEL', $id_carousel);
246 $tpl->setVariable('CLOSE_LABEL', $this->txt('close'));
247 $tpl->setVariable('COLOR_SCHEME', $modal->getScheme());
248
249 if (count($pages) > 1) {
250 $tpl->setCurrentBlock('has_indicators');
251 foreach ($pages as $index => $page) {
252 $tpl->setCurrentBlock('indicators');
253 $tpl->setVariable('INDEX', $index);
254 $tpl->setVariable('CLASS_ACTIVE', ($index == 0) ? 'active' : '');
255 $tpl->setVariable('ID_CAROUSEL2', $id_carousel);
257 }
258 }
259 $first = true;
260 foreach ($pages as $page) {
261 $this->renderPage($page, $first, $tpl, $default_renderer);
262 $first = false;
263 }
264 if (count($pages) > 1) {
265 $tpl->touchBlock('controls');
267 }
268 return $tpl->get();
269 }
270
271 private function renderPage(LightboxPage $page, bool $first, Template $tpl, RendererInterface $default_renderer): void
272 {
273 $vertical = false;
274 $components = [$page->getComponent()];
275 if ($page instanceof LightboxTextPage) {
276 $tpl->setCurrentBlock('pages');
277 $tpl->touchBlock('page_type_text');
279 } elseif ($page instanceof LightboxCardPage) {
280 $components = array_merge(
281 $page->getComponent()->getSections(),
282 $page->getComponent()->getHiddenSections()
283 );
284 $vertical = true;
285 }
286 $tpl->setCurrentBlock('pages');
287 $tpl->setVariable('CLASS_ACTIVE', $first ? 'active' : '');
288 $tpl->setVariable('ORIENTATION', $vertical ? 'item-vertical' : '');
289 $tpl->setVariable('TITLE2', htmlentities($page->getTitle(), ENT_QUOTES, 'UTF-8'));
290 $tpl->setVariable('CONTENT', $default_renderer->render($components));
291 if ($page instanceof LightboxDescriptionEnabledPage) {
292 $tpl->setVariable('DESCRIPTION', $page->getDescription());
293 }
295 }
296}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$components
renderPage(LightboxPage $page, bool $first, Template $tpl, RendererInterface $default_renderer)
Definition: Renderer.php:271
renderInterruptiveItemsByClass(string $class_name, array $items, RendererInterface $default_renderer)
Filters items by provided class, and renders only those.
Definition: Renderer.php:178
renderInterruptive(Component\Modal\Interruptive $modal, RendererInterface $default_renderer)
Definition: Renderer.php:131
registerSignals(Component\Modal\Modal $modal)
Definition: Renderer.php:75
renderAsync(Component\Modal\Modal $modal)
Definition: Renderer.php:124
renderLightbox(Component\Modal\Lightbox $modal, RendererInterface $default_renderer)
Definition: Renderer.php:236
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:69
cannotHandleComponent(Component $component)
This method MUST be called by derived component renderers, if.
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.
addTriggererOnLoadCode(Triggerer $triggerer)
Add onload-code for triggerer.
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
setVariable(string $variable, $value='')
Sets the given variable to the given value.
touchBlock(string $block)
overwrites ITX::touchBlock.
parseCurrentBlock(string $block_name=self::DEFAULT_BLOCK)
Parses the given block.
setCurrentBlock(string $part=self::DEFAULT_BLOCK)
Sets the template to the given block.
get(string $part=self::DEFAULT_BLOCK)
Renders the given block and returns the html string.
Interface to be extended by components that have the possibility to bind to Javascript.
render(Component $component, Renderer $default_renderer)
Render the component if possible and delegate additional rendering to the default_renderer.
Registry for resources required by rendered output like Javascript or CSS.
register(string $name)
Add a dependency.
Interface to templating as it is used in the UI framework.
Definition: Template.php:29
An entity that renders components to a string output.
Definition: Renderer.php:31
readonly ilGlobalTemplateInterface $tpl