104fd306cSNickeau<?php 204fd306cSNickeau 304fd306cSNickeaunamespace ComboStrap; 404fd306cSNickeau 504fd306cSNickeau 604fd306cSNickeauuse syntax_plugin_combo_label; 704fd306cSNickeauuse syntax_plugin_combo_tab; 804fd306cSNickeau 904fd306cSNickeau/** 1004fd306cSNickeau * The tabs component is a little bit a nasty one 1104fd306cSNickeau * because it's used in three cases: 1204fd306cSNickeau * * the new syntax to enclose the panels 1304fd306cSNickeau * * the new syntax to create the tabs 1404fd306cSNickeau * * the old syntax to create the tabs 1504fd306cSNickeau * The code is using the context to manage this cases 1604fd306cSNickeau * 1704fd306cSNickeau * Full example can be found 1804fd306cSNickeau * in the Javascript section of tabs and navs 1904fd306cSNickeau * https://getbootstrap.com/docs/5.0/components/navs-tabs/#javascript-behavior 2004fd306cSNickeau * 2104fd306cSNickeau * Vertical Pills 2204fd306cSNickeau * https://getbootstrap.com/docs/4.0/components/navs/#vertical 23*1e6623d9Sgerardnico * 24*1e6623d9Sgerardnico * React: https://react.dev/reference/react/Children#calling-a-render-prop-to-customize-rendering 2504fd306cSNickeau */ 2604fd306cSNickeauclass TabsTag 2704fd306cSNickeau{ 2804fd306cSNickeau 2904fd306cSNickeau 3004fd306cSNickeau /** 3104fd306cSNickeau * A tabs with this context will render the `ul` HTML tags 3204fd306cSNickeau * (ie tabs enclose the navigation partition) 3304fd306cSNickeau */ 3404fd306cSNickeau public const NAVIGATION_CONTEXT = "navigation-context"; 3504fd306cSNickeau public const PILLS_TYPE = "pills"; 3604fd306cSNickeau public const TABS_SKIN = "tabs"; 3704fd306cSNickeau public const ENCLOSED_TABS_TYPE = "enclosed-tabs"; 3804fd306cSNickeau public const ENCLOSED_PILLS_TYPE = "enclosed-pills"; 3904fd306cSNickeau public const LABEL = 'label'; 4004fd306cSNickeau public const SELECTED_ATTRIBUTE = "selected"; 4104fd306cSNickeau public const TAG = 'tabs'; 4204fd306cSNickeau /** 4304fd306cSNickeau * A key attributes to set on in the instructions the attributes 4404fd306cSNickeau * of panel 4504fd306cSNickeau */ 4604fd306cSNickeau public const KEY_PANEL_ATTRIBUTES = "panels"; 4704fd306cSNickeau /** 4804fd306cSNickeau * Type tabs 4904fd306cSNickeau */ 5004fd306cSNickeau public const TABS_TYPE = "tabs"; 5104fd306cSNickeau public const PILLS_SKIN = "pills"; 5204fd306cSNickeau 5304fd306cSNickeau public static function closeNavigationalHeaderComponent($type) 5404fd306cSNickeau { 5504fd306cSNickeau $html = "</ul>" . DOKU_LF; 5604fd306cSNickeau switch ($type) { 5704fd306cSNickeau case self::ENCLOSED_PILLS_TYPE: 5804fd306cSNickeau case self::ENCLOSED_TABS_TYPE: 5904fd306cSNickeau $html .= "</div>" . DOKU_LF; 6004fd306cSNickeau } 6104fd306cSNickeau return $html; 6204fd306cSNickeau 6304fd306cSNickeau } 6404fd306cSNickeau 6504fd306cSNickeau /** 6604fd306cSNickeau * @param $tagAttributes 6704fd306cSNickeau * @return string - the opening HTML code of the tab navigational header 6804fd306cSNickeau */ 6904fd306cSNickeau public static function openNavigationalTabsElement(TagAttributes $tagAttributes): string 7004fd306cSNickeau { 7104fd306cSNickeau 7204fd306cSNickeau /** 7304fd306cSNickeau * Unset non-html attributes 7404fd306cSNickeau */ 7504fd306cSNickeau $tagAttributes->removeComponentAttributeIfPresent(self::KEY_PANEL_ATTRIBUTES); 7604fd306cSNickeau 7704fd306cSNickeau /** 7804fd306cSNickeau * Type (Skin determination) 7904fd306cSNickeau */ 8004fd306cSNickeau $type = self::getComponentType($tagAttributes); 8104fd306cSNickeau 8204fd306cSNickeau /** 8304fd306cSNickeau * $skin (tabs or pills) 8404fd306cSNickeau */ 8504fd306cSNickeau $skin = self::TABS_TYPE; 8604fd306cSNickeau switch ($type) { 8704fd306cSNickeau case self::TABS_TYPE: 8804fd306cSNickeau case self::ENCLOSED_TABS_TYPE: 8904fd306cSNickeau $skin = self::TABS_SKIN; 9004fd306cSNickeau break; 9104fd306cSNickeau case self::PILLS_TYPE: 9204fd306cSNickeau case self::ENCLOSED_PILLS_TYPE: 9304fd306cSNickeau $skin = self::PILLS_SKIN; 9404fd306cSNickeau break; 9504fd306cSNickeau default: 9604fd306cSNickeau LogUtility::warning("The tabs type ($type) has an unknown skin", self::TAG); 9704fd306cSNickeau } 9804fd306cSNickeau 9904fd306cSNickeau /** 10004fd306cSNickeau * Creates the panel wrapper element 10104fd306cSNickeau */ 10204fd306cSNickeau $html = ""; 10304fd306cSNickeau switch ($type) { 10404fd306cSNickeau case self::TABS_TYPE: 10504fd306cSNickeau case self::PILLS_TYPE: 10604fd306cSNickeau if (!$tagAttributes->hasAttribute(Spacing::SPACING_ATTRIBUTE)) { 10704fd306cSNickeau $tagAttributes->addComponentAttributeValue(Spacing::SPACING_ATTRIBUTE, "mb-3"); 10804fd306cSNickeau } 10904fd306cSNickeau $tagAttributes->addClassName("nav") 11004fd306cSNickeau ->addClassName("nav-$skin"); 11104fd306cSNickeau $tagAttributes->addOutputAttributeValue('role', 'tablist'); 11204fd306cSNickeau $html = $tagAttributes->toHtmlEnterTag("ul"); 11304fd306cSNickeau break; 11404fd306cSNickeau case self::ENCLOSED_TABS_TYPE: 11504fd306cSNickeau case self::ENCLOSED_PILLS_TYPE: 11604fd306cSNickeau /** 11704fd306cSNickeau * The HTML opening for cards 11804fd306cSNickeau */ 11904fd306cSNickeau $tagAttributes->addClassName("card"); 12004fd306cSNickeau $html = $tagAttributes->toHtmlEnterTag("div") . 12104fd306cSNickeau "<div class=\"card-header\">"; 12204fd306cSNickeau /** 12304fd306cSNickeau * The HTML opening for the menu (UL) 12404fd306cSNickeau */ 12504fd306cSNickeau $html .= TagAttributes::createEmpty() 12604fd306cSNickeau ->addClassName("nav") 12704fd306cSNickeau ->addClassName("nav-$skin") 12804fd306cSNickeau ->addClassName("card-header-$skin") 12904fd306cSNickeau ->toHtmlEnterTag("ul"); 13004fd306cSNickeau break; 13104fd306cSNickeau default: 13204fd306cSNickeau LogUtility::error("The tabs type ($type) is unknown", self::TAG); 13304fd306cSNickeau } 13404fd306cSNickeau return $html; 13504fd306cSNickeau 13604fd306cSNickeau } 13704fd306cSNickeau 13804fd306cSNickeau /** 13904fd306cSNickeau * @param array $attributes 14004fd306cSNickeau * @return string 14104fd306cSNickeau */ 14204fd306cSNickeau public static function openNavigationalTabElement(array $attributes): string 14304fd306cSNickeau { 14404fd306cSNickeau $liTagAttributes = TagAttributes::createFromCallStackArray($attributes); 14504fd306cSNickeau 14604fd306cSNickeau /** 14704fd306cSNickeau * Check all attributes for the link (not the li) 14804fd306cSNickeau * and delete them 14904fd306cSNickeau */ 15004fd306cSNickeau $active = PanelTag::getSelectedValue($liTagAttributes); 15104fd306cSNickeau $panel = ""; 15204fd306cSNickeau 15304fd306cSNickeau 15404fd306cSNickeau $panel = $liTagAttributes->getValueAndRemoveIfPresent("panel"); 15504fd306cSNickeau if ($panel === null && $liTagAttributes->hasComponentAttribute("id")) { 15604fd306cSNickeau $panel = $liTagAttributes->getValueAndRemoveIfPresent("id"); 15704fd306cSNickeau } 15804fd306cSNickeau if ($panel === null) { 15904fd306cSNickeau LogUtility::msg("A id attribute is missing on a panel tag", LogUtility::LVL_MSG_ERROR, TabsTag::TAG); 16004fd306cSNickeau } 16104fd306cSNickeau 16204fd306cSNickeau 16304fd306cSNickeau /** 16404fd306cSNickeau * Creating the li element 16504fd306cSNickeau */ 16604fd306cSNickeau $html = $liTagAttributes->addClassName("nav-item") 16704fd306cSNickeau ->addOutputAttributeValue("role", "presentation") 16804fd306cSNickeau ->toHtmlEnterTag("li"); 16904fd306cSNickeau 17004fd306cSNickeau /** 17104fd306cSNickeau * Creating the a element 17204fd306cSNickeau */ 17304fd306cSNickeau $namespace = Bootstrap::getDataNamespace(); 17404fd306cSNickeau $htmlAttributes = TagAttributes::createEmpty(); 17504fd306cSNickeau if ($active === true) { 17604fd306cSNickeau $htmlAttributes->addClassName("active"); 17704fd306cSNickeau $htmlAttributes->addOutputAttributeValue("aria-selected", "true"); 17804fd306cSNickeau } 17904fd306cSNickeau $html .= $htmlAttributes 18004fd306cSNickeau ->addClassName("nav-link") 18104fd306cSNickeau ->addOutputAttributeValue('id', $panel . "-tab") 18204fd306cSNickeau ->addOutputAttributeValue("data{$namespace}-toggle", "tab") 18304fd306cSNickeau ->addOutputAttributeValue('aria-controls', $panel) 18404fd306cSNickeau ->addOutputAttributeValue("role", "tab") 18504fd306cSNickeau ->addOutputAttributeValue('href', "#$panel") 18604fd306cSNickeau ->toHtmlEnterTag("a"); 18704fd306cSNickeau 18804fd306cSNickeau return $html; 18904fd306cSNickeau } 19004fd306cSNickeau 19104fd306cSNickeau public static function closeNavigationalTabElement(): string 19204fd306cSNickeau { 19304fd306cSNickeau return "</a></li>"; 19404fd306cSNickeau } 19504fd306cSNickeau 19604fd306cSNickeau /** 19704fd306cSNickeau * @param TagAttributes $tagAttributes 19804fd306cSNickeau * @return string - return the HTML open tags of the panels (not the navigation) 19904fd306cSNickeau */ 20004fd306cSNickeau public static function openTabPanelsElement(TagAttributes $tagAttributes): string 20104fd306cSNickeau { 20204fd306cSNickeau 20304fd306cSNickeau $tagAttributes->addClassName("tab-content"); 20404fd306cSNickeau 20504fd306cSNickeau /** 20604fd306cSNickeau * In preview with only one panel 20704fd306cSNickeau */ 20804fd306cSNickeau global $ACT; 20904fd306cSNickeau if ($ACT === "preview" && $tagAttributes->hasComponentAttribute(self::SELECTED_ATTRIBUTE)) { 21004fd306cSNickeau $tagAttributes->removeComponentAttribute(self::SELECTED_ATTRIBUTE); 21104fd306cSNickeau } 21204fd306cSNickeau 21304fd306cSNickeau $html = $tagAttributes->toHtmlEnterTag("div"); 21404fd306cSNickeau $type = self::getComponentType($tagAttributes); 21504fd306cSNickeau switch ($type) { 21604fd306cSNickeau case self::ENCLOSED_TABS_TYPE: 21704fd306cSNickeau case self::ENCLOSED_PILLS_TYPE: 21804fd306cSNickeau $html = "<div class=\"card-body\">" . $html; 21904fd306cSNickeau break; 22004fd306cSNickeau } 22104fd306cSNickeau return $html; 22204fd306cSNickeau 22304fd306cSNickeau } 22404fd306cSNickeau 22504fd306cSNickeau public static function getComponentType(TagAttributes $tagAttributes) 22604fd306cSNickeau { 22704fd306cSNickeau 22804fd306cSNickeau $skin = $tagAttributes->getValueAndRemoveIfPresent("skin"); 22904fd306cSNickeau if ($skin !== null) { 23004fd306cSNickeau return $skin; 23104fd306cSNickeau } 23204fd306cSNickeau 23304fd306cSNickeau $type = $tagAttributes->getType(); 23404fd306cSNickeau if ($type !== null) { 23504fd306cSNickeau return $type; 23604fd306cSNickeau } 23704fd306cSNickeau return self::TABS_TYPE; 23804fd306cSNickeau } 23904fd306cSNickeau 24004fd306cSNickeau public static function closeTabPanelsElement(TagAttributes $tagAttributes): string 24104fd306cSNickeau { 24204fd306cSNickeau $html = "</div>"; 24304fd306cSNickeau $type = self::getComponentType($tagAttributes); 24404fd306cSNickeau switch ($type) { 24504fd306cSNickeau case self::ENCLOSED_TABS_TYPE: 24604fd306cSNickeau case self::ENCLOSED_PILLS_TYPE: 24704fd306cSNickeau $html .= "</div>"; 24804fd306cSNickeau $html .= "</div>"; 24904fd306cSNickeau break; 25004fd306cSNickeau } 25104fd306cSNickeau return $html; 25204fd306cSNickeau } 25304fd306cSNickeau 25404fd306cSNickeau public static function handleExit($handler): array 25504fd306cSNickeau { 25604fd306cSNickeau $callStack = CallStack::createFromHandler($handler); 25704fd306cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 25804fd306cSNickeau if ($openingTag === false) { 25904fd306cSNickeau LogUtility::error("A tabs tag had no opening tag and was discarded"); 26004fd306cSNickeau return array( 26104fd306cSNickeau PluginUtility::CONTEXT => "root", 26204fd306cSNickeau PluginUtility::ATTRIBUTES => [] 26304fd306cSNickeau ); 26404fd306cSNickeau } 26504fd306cSNickeau $previousOpeningTag = $callStack->previous(); 26604fd306cSNickeau $callStack->next(); 26704fd306cSNickeau $firstChild = $callStack->moveToFirstChildTag(); 26804fd306cSNickeau $context = null; 26904fd306cSNickeau if ($firstChild !== false) { 27004fd306cSNickeau /** 27104fd306cSNickeau * Add the context to the opening and ending tag 27204fd306cSNickeau */ 27304fd306cSNickeau $context = $firstChild->getTagName(); 27404fd306cSNickeau $openingTag->setContext($context); 27504fd306cSNickeau /** 27604fd306cSNickeau * Does tabs enclosed Panel (new syntax) 27704fd306cSNickeau */ 27804fd306cSNickeau if ($context == PanelTag::PANEL_LOGICAL_MARKUP) { 27904fd306cSNickeau 28004fd306cSNickeau /** 28104fd306cSNickeau * We scan the tabs and derived: 28204fd306cSNickeau * * the navigation tabs element (ie ul/li nav-tab) 28304fd306cSNickeau * * the tab pane element 28404fd306cSNickeau */ 28504fd306cSNickeau 28604fd306cSNickeau /** 28704fd306cSNickeau * This call will create the ul 28804fd306cSNickeau * <ul class="nav nav-tabs mb-3" role="tablist"> 28904fd306cSNickeau * thanks to the {@link TabsTag::NAVIGATION_CONTEXT} 29004fd306cSNickeau */ 29104fd306cSNickeau $navigationalCalls[] = Call::createComboCall( 29204fd306cSNickeau TabsTag::TAG, 29304fd306cSNickeau DOKU_LEXER_ENTER, 29404fd306cSNickeau $openingTag->getAttributes(), 29504fd306cSNickeau TabsTag::NAVIGATION_CONTEXT, 29604fd306cSNickeau null, 29704fd306cSNickeau null, 29804fd306cSNickeau null, 29904fd306cSNickeau \syntax_plugin_combo_xmlblocktag::TAG 30004fd306cSNickeau ); 30104fd306cSNickeau 30204fd306cSNickeau /** 30304fd306cSNickeau * The tab pane elements 30404fd306cSNickeau */ 30504fd306cSNickeau $tabPaneCalls = [$openingTag, $firstChild]; 30604fd306cSNickeau 30704fd306cSNickeau /** 30804fd306cSNickeau * Copy the stack 30904fd306cSNickeau */ 31004fd306cSNickeau $labelState = "label"; 31104fd306cSNickeau $nonLabelState = "non-label"; 31204fd306cSNickeau $scanningState = $nonLabelState; 31304fd306cSNickeau while ($actual = $callStack->next()) { 31404fd306cSNickeau 31504fd306cSNickeau if ( 31604fd306cSNickeau $actual->getTagName() == syntax_plugin_combo_label::TAG 31704fd306cSNickeau && 31804fd306cSNickeau $actual->getState() == DOKU_LEXER_ENTER 31904fd306cSNickeau ) { 32004fd306cSNickeau $scanningState = $labelState; 32104fd306cSNickeau } 32204fd306cSNickeau 32304fd306cSNickeau if ($labelState === $scanningState) { 32404fd306cSNickeau $navigationalCalls[] = $actual; 32504fd306cSNickeau } else { 32604fd306cSNickeau $tabPaneCalls[] = $actual; 32704fd306cSNickeau } 32804fd306cSNickeau 32904fd306cSNickeau if ( 33004fd306cSNickeau $actual->getTagName() == syntax_plugin_combo_label::TAG 33104fd306cSNickeau && 33204fd306cSNickeau $actual->getState() == DOKU_LEXER_EXIT 33304fd306cSNickeau ) { 33404fd306cSNickeau $scanningState = $nonLabelState; 33504fd306cSNickeau } 33604fd306cSNickeau 33704fd306cSNickeau 33804fd306cSNickeau } 33904fd306cSNickeau 34004fd306cSNickeau /** 34104fd306cSNickeau * End navigational tabs 34204fd306cSNickeau */ 34304fd306cSNickeau $navigationalCalls[] = Call::createComboCall( 34404fd306cSNickeau TabsTag::TAG, 34504fd306cSNickeau DOKU_LEXER_EXIT, 34604fd306cSNickeau $openingTag->getAttributes(), 34704fd306cSNickeau TabsTag::NAVIGATION_CONTEXT, 34804fd306cSNickeau null, 34904fd306cSNickeau null, 35004fd306cSNickeau null, 35104fd306cSNickeau \syntax_plugin_combo_xmlblocktag::TAG 35204fd306cSNickeau ); 35304fd306cSNickeau 35404fd306cSNickeau /** 35504fd306cSNickeau * Rebuild 35604fd306cSNickeau */ 35704fd306cSNickeau $callStack->deleteAllCallsAfter($previousOpeningTag); 35804fd306cSNickeau $callStack->appendCallsAtTheEnd($navigationalCalls); 35904fd306cSNickeau $callStack->appendCallsAtTheEnd($tabPaneCalls); 36004fd306cSNickeau 36104fd306cSNickeau } 36204fd306cSNickeau } 36304fd306cSNickeau 36404fd306cSNickeau return array( 36504fd306cSNickeau PluginUtility::CONTEXT => $context, 36604fd306cSNickeau PluginUtility::ATTRIBUTES => $openingTag->getAttributes() 36704fd306cSNickeau ); 36804fd306cSNickeau } 36904fd306cSNickeau 37004fd306cSNickeau public static function renderEnterXhtml(TagAttributes $tagAttributes, array $data): string 37104fd306cSNickeau { 37204fd306cSNickeau $context = $data[PluginUtility::CONTEXT]; 37304fd306cSNickeau switch ($context) { 37404fd306cSNickeau /** 37504fd306cSNickeau * When the tag tabs enclosed the panels 37604fd306cSNickeau */ 37704fd306cSNickeau case PanelTag::PANEL_LOGICAL_MARKUP: 37804fd306cSNickeau return TabsTag::openTabPanelsElement($tagAttributes); 37904fd306cSNickeau /** 38004fd306cSNickeau * When the tag tabs are derived (new syntax) 38104fd306cSNickeau */ 38204fd306cSNickeau case TabsTag::NAVIGATION_CONTEXT: 38304fd306cSNickeau /** 38404fd306cSNickeau * Old syntax, when the tag had to be added specifically 38504fd306cSNickeau */ 38604fd306cSNickeau case syntax_plugin_combo_tab::TAG: 38704fd306cSNickeau return TabsTag::openNavigationalTabsElement($tagAttributes); 38804fd306cSNickeau default: 38904fd306cSNickeau LogUtility::internalError("The context ($context) is unknown in enter", TabsTag::TAG); 39004fd306cSNickeau return ""; 39104fd306cSNickeau 39204fd306cSNickeau } 39304fd306cSNickeau } 39404fd306cSNickeau 39504fd306cSNickeau public static function renderExitXhtml(TagAttributes $tagAttributes, array $data): string 39604fd306cSNickeau { 39704fd306cSNickeau $context = $data[PluginUtility::CONTEXT]; 39804fd306cSNickeau switch ($context) { 39904fd306cSNickeau /** 40004fd306cSNickeau * New syntax (tabpanel enclosing) 40104fd306cSNickeau */ 40204fd306cSNickeau case PanelTag::PANEL_LOGICAL_MARKUP: 40304fd306cSNickeau return TabsTag::closeTabPanelsElement($tagAttributes); 40404fd306cSNickeau /** 40504fd306cSNickeau * Old syntax 40604fd306cSNickeau */ 40704fd306cSNickeau case syntax_plugin_combo_tab::TAG: 40804fd306cSNickeau /** 40904fd306cSNickeau * New syntax (Derived) 41004fd306cSNickeau */ 41104fd306cSNickeau case TabsTag::NAVIGATION_CONTEXT: 41204fd306cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); 41304fd306cSNickeau $type = TabsTag::getComponentType($tagAttributes); 41404fd306cSNickeau return TabsTag::closeNavigationalHeaderComponent($type); 41504fd306cSNickeau default: 41604fd306cSNickeau LogUtility::log2FrontEnd("The context $context is unknown in exit", LogUtility::LVL_MSG_ERROR, TabsTag::TAG); 41704fd306cSNickeau return ""; 41804fd306cSNickeau } 41904fd306cSNickeau } 42004fd306cSNickeau} 42104fd306cSNickeau 422