1<?php 2 3/* Copyright (c) 2018 Nils Haagen <nils.haagen@concepts.and-training.de> Extended GPL, see docs/LICENSE */ 4 5namespace ILIAS\UI\Implementation\Component\MainControls; 6 7use ILIAS\UI\Implementation\Render\AbstractComponentRenderer; 8use ILIAS\UI\Renderer as RendererInterface; 9use ILIAS\UI\Component; 10use ILIAS\UI\Component\Signal; 11use ILIAS\UI\Component\MainControls\MainBar; 12use ILIAS\UI\Component\MainControls\MetaBar; 13use ILIAS\UI\Component\MainControls\Slate\Slate; 14use ILIAS\UI\Component\MainControls\Footer; 15use ILIAS\UI\Implementation\Component\Button\Bulky as IBulky; 16use ILIAS\UI\Implementation\Component\MainControls\Slate\Slate as ISlate; 17use ILIAS\UI\Implementation\Render\Template as UITemplateWrapper; 18use ILIAS\Data\URI; 19 20class Renderer extends AbstractComponentRenderer 21{ 22 const BLOCK_MAINBAR_ENTRIES = 'trigger_item'; 23 const BLOCK_MAINBAR_TOOLS = 'tool_trigger_item'; 24 const BLOCK_METABAR_ENTRIES = 'meta_element'; 25 26 private $signals_for_tools = []; 27 private $trigger_signals = []; 28 29 /** 30 * @inheritdoc 31 */ 32 public function render(Component\Component $component, RendererInterface $default_renderer) 33 { 34 $this->checkComponent($component); 35 36 if ($component instanceof MainBar) { 37 return $this->renderMainbar($component, $default_renderer); 38 } 39 if ($component instanceof MetaBar) { 40 return $this->renderMetabar($component, $default_renderer); 41 } 42 if ($component instanceof Footer) { 43 return $this->renderFooter($component, $default_renderer); 44 } 45 if ($component instanceof ModeInfo) { 46 return $this->renderModeInfo($component, $default_renderer); 47 } 48 } 49 50 protected function calculateMainBarTreePosition($pos, $slate) 51 { 52 if (!$slate instanceof Slate && !$slate instanceof MainBar) { 53 return $slate; 54 } 55 return $slate 56 ->withMainBarTreePosition($pos) 57 ->withMappedSubNodes( 58 function ($num, $slate, $is_tool = false) use ($pos) { 59 if ($is_tool) { 60 $pos = 'T'; 61 } 62 return $this->calculateMainBarTreePosition("$pos:$num", $slate); 63 } 64 ); 65 } 66 67 protected function renderToolEntry( 68 Slate $entry, 69 string $entry_id, 70 string $mb_id, 71 MainBar $component, 72 UITemplateWrapper $tpl, 73 RendererInterface $default_renderer 74 ) : string { 75 $hidden = $component->getInitiallyHiddenToolIds(); 76 $close_buttons = $component->getCloseButtons(); 77 78 $is_removeable = array_key_exists($entry_id, $close_buttons); 79 $is_hidden = in_array($entry_id, $hidden); 80 81 if ($is_removeable) { 82 $trigger_signal = $component->getTriggerSignal($mb_id, $component::ENTRY_ACTION_REMOVE); 83 $this->trigger_signals[] = $trigger_signal; 84 $btn_removetool = $close_buttons[$entry_id] 85 ->withAdditionalOnloadCode( 86 function ($id) use ($mb_id) { 87 return "il.UI.maincontrols.mainbar.addPartIdAndEntry('{$mb_id}', 'remover', '{$id}', true);"; 88 } 89 ) 90 ->withOnClick($trigger_signal); 91 92 $tpl->setCurrentBlock("tool_removal"); 93 $tpl->setVariable("REMOVE_TOOL", $default_renderer->render($btn_removetool)); 94 $tpl->parseCurrentBlock(); 95 } 96 97 $is_removeable = $is_removeable ? 'true':'false'; 98 $is_hidden = $is_hidden ? 'true':'false'; 99 return "il.UI.maincontrols.mainbar.addToolEntry('{$mb_id}', {$is_removeable}, {$is_hidden}, '{$entry_id}');"; 100 } 101 102 protected function renderMainbarEntry( 103 array $entries, 104 string $block, 105 MainBar $component, 106 UITemplateWrapper $tpl, 107 RendererInterface $default_renderer 108 ) { 109 $f = $this->getUIFactory(); 110 foreach ($entries as $k => $entry) { 111 $button = $entry; 112 $slate = null; 113 $js = ''; 114 115 if ($entry instanceof Slate) { 116 $slate = $entry; 117 $mb_id = $entry->getMainBarTreePosition(); 118 $is_tool = $block === static::BLOCK_MAINBAR_TOOLS; 119 if ($is_tool) { 120 $js = $this->renderToolEntry($entry, $k, $mb_id, $component, $tpl, $default_renderer); 121 } 122 123 $trigger_signal = $component->getTriggerSignal($mb_id, $component::ENTRY_ACTION_TRIGGER); 124 $this->trigger_signals[] = $trigger_signal; 125 $button = $f->button()->bulky($entry->getSymbol(), $entry->getName(), '#') 126 ->withAriaRole(IBulky::MENUITEM) 127 ->withOnClick($trigger_signal); 128 } else { 129 //add Links/Buttons as toplevel entries 130 $pos = array_search($k, array_keys($entries)); 131 $mb_id = '0:' . $pos; 132 $is_tool = false; 133 } 134 135 $button = $button->withAdditionalOnLoadCode( 136 function ($id) use ($js, $mb_id, $k, $is_tool) { 137 $add_as_tool = $is_tool ? 'true':'false'; 138 $js .= " 139 il.UI.maincontrols.mainbar.addPartIdAndEntry('{$mb_id}', 'triggerer', '{$id}', {$add_as_tool}); 140 il.UI.maincontrols.mainbar.addMapping('{$k}','{$mb_id}'); 141 "; 142 return $js; 143 } 144 ); 145 $tpl->setCurrentBlock($block); 146 $tpl->setVariable("BUTTON", $default_renderer->render($button)); 147 $tpl->parseCurrentBlock(); 148 149 if ($slate) { 150 $entry = $entry->withAriaRole(ISlate::MENU); 151 152 $tpl->setCurrentBlock("slate_item"); 153 $tpl->setVariable("SLATE", $default_renderer->render($entry)); 154 $tpl->parseCurrentBlock(); 155 } 156 } 157 } 158 159 protected function renderMainbar(MainBar $component, RendererInterface $default_renderer) 160 { 161 $f = $this->getUIFactory(); 162 $tpl = $this->getTemplate("tpl.mainbar.html", true, true); 163 164 $tpl->setVariable("ARIA_LABEL", $this->txt('mainbar_aria_label')); 165 166 //add "more"-slate 167 $more_slate = $f->maincontrols()->slate()->combined( 168 $component->getMoreButton()->getLabel(), 169 $f->symbol()->glyph()->more() 170 )->withAriaRole(ISlate::MENU); 171 $component = $component->withAdditionalEntry( 172 '_mb_more_entry', 173 $more_slate 174 ); 175 176 $component = $this->calculateMainBarTreePosition("0", $component); 177 178 $mb_entries = [ 179 static::BLOCK_MAINBAR_ENTRIES => $component->getEntries(), 180 static::BLOCK_MAINBAR_TOOLS => $component->getToolEntries() 181 ]; 182 183 foreach ($mb_entries as $block => $entries) { 184 $this->renderMainbarEntry( 185 $entries, 186 $block, 187 $component, 188 $tpl, 189 $default_renderer 190 ); 191 } 192 193 //tools-section trigger 194 if (count($component->getToolEntries()) > 0) { 195 $btn_tools = $component->getToolsButton() 196 ->withOnClick($component->getToggleToolsSignal()) 197 ->withAriaRole(IBulky::MENUITEM); 198 199 $tpl->setCurrentBlock("tools_trigger"); 200 $tpl->setVariable("BUTTON", $default_renderer->render($btn_tools)); 201 $tpl->parseCurrentBlock(); 202 } 203 204 //disengage all, close slates 205 $btn_disengage = $f->button()->bulky($f->symbol()->glyph()->back("#"), "close", "#") 206 ->withOnClick($component->getDisengageAllSignal()); 207 $tpl->setVariable("CLOSE_SLATES", $default_renderer->render($btn_disengage)); 208 209 210 $id = $this->bindMainbarJS($component); 211 $tpl->setVariable('ID', $id); 212 213 return $tpl->get(); 214 } 215 216 protected function renderMetabar(MetaBar $component, RendererInterface $default_renderer) 217 { 218 $f = $this->getUIFactory(); 219 $tpl = $this->getTemplate("tpl.metabar.html", true, true); 220 $active = ''; 221 $signals = [ 222 'entry' => $component->getEntryClickSignal(), 223 'close_slates' => $component->getDisengageAllSignal() 224 ]; 225 $entries = $component->getEntries(); 226 227 $more_label = 'more'; 228 $more_symbol = $f->symbol()->glyph()->disclosure() 229 ->withCounter($f->counter()->novelty(0)) 230 ->withCounter($f->counter()->status(0)); 231 $more_slate = $f->maincontrols()->slate()->combined($more_label, $more_symbol, $f->legacy('')) 232 ->withAriaRole(ISlate::MENU); 233 $entries[] = $more_slate; 234 235 $this->renderTriggerButtonsAndSlates( 236 $tpl, 237 $default_renderer, 238 $signals['entry'], 239 static::BLOCK_METABAR_ENTRIES, 240 $entries, 241 $active 242 ); 243 244 $component = $component->withOnLoadCode( 245 function ($id) use ($signals) { 246 $entry_signal = $signals['entry']; 247 $close_slates_signal = $signals['close_slates']; 248 return " 249 il.UI.maincontrols.metabar.registerSignals( 250 '{$id}', 251 '{$entry_signal}', 252 '{$close_slates_signal}', 253 ); 254 il.UI.maincontrols.metabar.init(); 255 $(window).resize(il.UI.maincontrols.metabar.init); 256 "; 257 } 258 ); 259 $tpl->setVariable('ARIA_LABEL', $this->txt('metabar_aria_label')); 260 261 $id = $this->bindJavaScript($component); 262 $tpl->setVariable('ID', $id); 263 return $tpl->get(); 264 } 265 266 protected function renderModeInfo(ModeInfo $component, RendererInterface $default_renderer) 267 { 268 $tpl = $this->getTemplate("tpl.mode_info.html", true, true); 269 $tpl->setVariable('MODE_TITLE', $component->getModeTitle()); 270 $base_URI = $component->getCloseAction()->getBaseURI(); 271 $query = $component->getCloseAction()->getQuery(); 272 $action = $base_URI . '?' . $query; 273 $close = $this->getUIFactory()->symbol()->glyph()->close($action); 274 $tpl->setVariable('CLOSE_GLYPH', $default_renderer->render($close)); 275 276 return $tpl->get(); 277 } 278 279 280 protected function renderTriggerButtonsAndSlates( 281 UITemplateWrapper $tpl, 282 RendererInterface $default_renderer, 283 Signal $entry_signal, 284 string $block, 285 array $entries, 286 string $active = null, 287 array $initially_hidden_ids = [], 288 array $close_buttons = [], 289 Signal $tool_removal_signal = null 290 ) { 291 foreach ($entries as $id => $entry) { 292 $use_block = $block; 293 $engaged = (string) $id === $active; 294 295 if ($entry instanceof Slate) { 296 $f = $this->getUIFactory(); 297 $secondary_signal = $entry->getToggleSignal(); 298 $button = $f->button()->bulky($entry->getSymbol(), $entry->getName(), '#') 299 ->withOnClick($entry_signal) 300 ->appendOnClick($secondary_signal) 301 ->withEngagedState($engaged) 302 ->withAriaRole(IBulky::MENUITEM); 303 304 $slate = $entry; 305 } else { 306 $button = $entry; 307 $slate = null; 308 } 309 310 $button_html = $default_renderer->render($button); 311 312 $tpl->setCurrentBlock($use_block); 313 $tpl->setVariable("BUTTON", $button_html); 314 $tpl->parseCurrentBlock(); 315 316 if ($slate) { 317 $slate = $slate->withAriaRole(ISlate::MENU); 318 319 $tpl->setCurrentBlock("slate_item"); 320 $tpl->setVariable("SLATE", $default_renderer->render($slate)); 321 $tpl->parseCurrentBlock(); 322 } 323 } 324 } 325 326 protected function bindMainbarJS(MainBar $component) : string 327 { 328 $trigger_signals = $this->trigger_signals; 329 330 $inititally_active = $component->getActive(); 331 332 $component = $component->withOnLoadCode( 333 function ($id) use ($component, $trigger_signals, $inititally_active) { 334 $disengage_all_signal = $component->getDisengageAllSignal(); 335 $tools_toggle_signal = $component->getToggleToolsSignal(); 336 337 $js = "il.UI.maincontrols.mainbar.addTriggerSignal('{$disengage_all_signal}');"; 338 $js .= "il.UI.maincontrols.mainbar.addTriggerSignal('{$tools_toggle_signal}');"; 339 340 foreach ($trigger_signals as $signal) { 341 $js .= "il.UI.maincontrols.mainbar.addTriggerSignal('{$signal}');"; 342 } 343 344 foreach ($component->getToolEntries() as $k => $tool) { 345 $signal = $component->getEngageToolSignal($k); 346 $js .= "il.UI.maincontrols.mainbar.addTriggerSignal('{$signal}');"; 347 } 348 349 $js .= " 350 $(window).resize(il.UI.maincontrols.mainbar.adjustToScreenSize); 351 il.UI.maincontrols.mainbar.init('{$inititally_active}'); 352 "; 353 return $js; 354 } 355 ); 356 357 $id = $this->bindJavaScript($component); 358 return $id; 359 } 360 361 protected function renderFooter(Footer $component, RendererInterface $default_renderer) 362 { 363 $tpl = $this->getTemplate("tpl.footer.html", true, true); 364 $links = $component->getLinks(); 365 if ($links) { 366 $link_list = $this->getUIFactory()->listing()->unordered($links); 367 $tpl->setVariable('LINKS', $default_renderer->render($link_list)); 368 } 369 370 $tpl->setVariable('TEXT', $component->getText()); 371 372 $perm_url = $component->getPermanentURL(); 373 if ($perm_url instanceof URI) { 374 // Since URI::__toString() is only available in ILIAS >= 7 we have to do that on our own... 375 $uri = $perm_url->getBaseURI(); 376 $query = $perm_url->getQuery(); 377 if ($query) { 378 $uri .= '?' . $query; 379 } 380 $fragment = $perm_url->getFragment(); 381 if ($fragment) { 382 $uri .= '#' . $fragment; 383 } 384 385 $tpl->setVariable('PERMA_LINK_LABEL', $this->txt('perma_link')); 386 $tpl->setVariable('PERMANENT_URL', $uri); 387 } 388 return $tpl->get(); 389 } 390 391 /** 392 * @inheritdoc 393 */ 394 public function registerResources(\ILIAS\UI\Implementation\Render\ResourceRegistry $registry) 395 { 396 parent::registerResources($registry); 397 $registry->register('./src/UI/templates/js/MainControls/mainbar.js'); 398 $registry->register('./src/UI/templates/js/MainControls/metabar.js'); 399 $registry->register('./src/GlobalScreen/Client/dist/GS.js'); 400 $registry->register('./src/UI/templates/js/MainControls/footer.js'); 401 } 402 403 /** 404 * @inheritdoc 405 */ 406 protected function getComponentInterfaceName() 407 { 408 return array( 409 MetaBar::class, 410 MainBar::class, 411 Footer::class, 412 ModeInfo::class 413 ); 414 } 415} 416