Instantly share code, notes, and snippets.
Last active
August 4, 2020 12:51
-
Star
6
(6)
You must be signed in to star a gist -
Fork
2
(2)
You must be signed in to fork a gist
-
Save julrich/d91c1ac1968418e6ae8290f2a2e90afb to your computer and use it in GitHub Desktop.
Fully cached TYPO3 HMENU navigation example with expAll and 'active', 'current' states
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Main navigation in Sidebar | |
# | |
# General idea: Don't render & cache 'active' and 'current' states in 'expAll' menu, so it becomes cacheable | |
# over all pages. To regain 'active' and 'current' states, the result of the cached menu is parsed by | |
# 'stdWrap.replacement', utilizing specific information about the resulting menu item markup to insert them. | |
# Use COA to decouple the stdWrap ('lib.navSidebar.stdWrap.replacement') needed for RegExp replacement from | |
# the cached menu ('lib.navSidebar.10'). This way the complete menu can be generically cached without current | |
# and active states, but the stdWrap is still run, re-adding those | |
lib.navSidebar = COA | |
# Definition of the general menu object | |
lib.navSidebar.10 = HMENU | |
lib.navSidebar.10 { | |
cache { | |
# Use unique key for combination of '$page.uid.root' (current instance, multi-site specific) | |
# and the chosen language ('TSFE:sys_language_uid') | |
key = navSidebar-{$page.uid.root}-{TSFE:sys_language_uid} | |
key.insertData = 1 | |
# 'lifetime = default' in this case refers to config.cache_period = 43200 (12 hours) | |
lifetime = default | |
} | |
# Start at the root of the page | |
entryLevel = 0 | |
# Actual menu, crucially we render 'page_{field:uid}', e.g. 'page_123', into each relevant | |
# item. This gives us the option to later replace those uniquely identifiable strings to include | |
# additional classes like 'active' or 'current' | |
1 = TMENU | |
1 { | |
# 'expAll = 1' to expand all nodes recursively | |
expAll = 1 | |
# Markup for items that have no submenu-items | |
NO = 1 | |
NO { | |
wrapItemAndSub = <li class="page_{field:uid} nav-sidebar__list__item">|</li> | |
wrapItemAndSub.insertData = 1 | |
ATagTitle.field = title // subtitle | |
ATagParams = tabindex="0" | |
} | |
# Markup for items that have submenu-items | |
IFSUB = 1 | |
IFSUB { | |
wrapItemAndSub = <li class="page_{field:uid} nav-sidebar__list__item nav-sidebar__list__item--has-submenu">|</li> | |
wrapItemAndSub.insertData = 1 | |
before = <span id="nav-sidebar_{field:uid}" role="button" aria-haspopup="true" aria-owns="nav-sidebar__submenu_{field:uid}" aria-controls="nav-sidebar__submenu_{field:uid}" aria-expanded="false"> | |
before.insertData = 1 | |
after = <svg class="nav-sidebar__icon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-arrow-down"></use></svg></span> | |
after.insertData = 1 | |
doNotLinkIt = 1 | |
} | |
} | |
# Additionally add a class denoting the level for subsequent menu levels (5 supported overall right now) | |
# Submenu Level 2 | |
2 < .1 | |
2 = TMENU | |
2 { | |
stdWrap.dataWrap = <ul class="nav-sidebar__submenu nav-sidebar__submenu--level-2">|</ul> | |
} | |
# Submenu Level 3 | |
3 < .1 | |
3 = TMENU | |
3 { | |
stdWrap.dataWrap = <ul class="nav-sidebar__submenu nav-sidebar__submenu--level-3">|</ul> | |
} | |
# Submenu Level 4 | |
4 < .1 | |
4 = TMENU | |
4 { | |
stdWrap.dataWrap = <ul class="nav-sidebar__submenu nav-sidebar__submenu--level-4">|</ul> | |
} | |
# Submenu Level 5 | |
5 < .1 | |
5 = TMENU | |
5 { | |
stdWrap.dataWrap = <ul class="nav-sidebar__submenu nav-sidebar__submenu--level-5">|</ul> | |
} | |
} | |
# Add replacement stdWrap, to augment the uniquely identifable strings ('page_{field:uid} nav', e.g. 'page_123 nav'), | |
# embedded with the classes=".." of each menuitem, with additional classes for 'active' and 'current' states. | |
# the classes | |
# Don't add replacement function for pages where the resulting regular expression would be empty, triggering an error | |
# Those currently are the root page itself, and all publicly visible pages, that are not children of the root page. | |
[globalVar = TSFE:id={$page.uid.root}] || [globalVar = TSFE:id={$page.uid.404}] || [globalVar = TSFE:id={$page.uid.noTranslation}] || [globalVar = TSFE:id={$page.uid.search}] | |
# Do nothing here, we just need the negation | |
[else] | |
# Also see 'replacement' TypoScript reference: | |
# https://docs.typo3.org/typo3cms/TyposcriptReference/8.7/Functions/Replacement/ | |
lib.navSidebar.stdWrap.replacement.10 { | |
# Construct search string of the form '#a (Cat|Dog|Tiger)#i', Cat/Dog/Tiger in this case being all the values | |
# we want to replace. All the menu items, to be precise their unique string (e.g. 'page_123 nav'), | |
# that are in the rootline need replacement here. | |
search.cObject = COA | |
search.cObject.10 = HMENU | |
search.cObject.10 { | |
# Construct a rootline menu, including all pages from the current page to the root page ('1|-1') | |
special = rootline | |
special.range = 1|-1 | |
# Wrap the whole menu with the structure we need for the 'replacement.10.search' RegExp ('#(...)#i') | |
wrap = #(|)#i | |
# For all menuitems of this rootline, discard the actual output ('<li><a>...</a></li>') | |
# by setting 'doNotLinkIt = 1' and 'doNotShowLink = 1'. Generate inner part of RegExp, | |
# e.g. 'page_123 nav|page_1231 nav|page_2123 nav', using 'before' | |
1 = TMENU | |
1 { | |
NO { | |
# Use option split, because we don't want a '|' after the last item | |
before = page_{field:uid} nav| |*| page_{field:uid} nav| |*| page_{field:uid} nav | |
before.insertData = 1 | |
doNotLinkIt = 1 | |
doNotShowLink = 1 | |
} | |
} | |
} | |
# Rootline looks something like this: 'root (uid: 1) > page1 (uid: 10) > page10 (uid:100) > page100 (uid:1000)' | |
# Only the last item in the rootline is the current item, all the items before it are active items. | |
# Thus we option split again, for us 'nav-sidebar__list__item--submenu-is-open' equals 'active', | |
# 'nav-sidebar__list__item--current' equal 'current'. | |
replace = nav-sidebar__list__item--submenu-is-open ${1} |*| nav-sidebar__list__item--submenu-is-open ${1} |*| nav-sidebar__list__item--current ${1} | |
# Enable option split for replace and RegExp for search | |
useRegExp = 1 | |
useOptionSplitReplace = 1 | |
} | |
[global] | |
# If there is a user logged in to the specific (current) instance, use a different cache key, which in addition to | |
# '$page.uid.root' and 'TSFE:sys_language_uid' also encodes the user uid of the logged in user, because every user | |
# might have his own set of visible pages, resulting in menu cache entry unique per user + instance + language. | |
[usergroup = {$page.uid.frontendUserGroupUid}] | |
lib.navSidebar.10 { | |
cache { | |
key = navSidebarLoggedIn-{$page.uid.root}-{TSFE:sys_language_uid}-{TSFE:fe_user|user|uid} | |
key.insertData = 1 | |
} | |
} | |
[global] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In order to avoid listing of all pages where the resulting regular expression would be empty, like it is here done
[globalVar = TSFE:id={$page.uid.root}] || [globalVar = TSFE:id={$page.uid.404}] || [globalVar = TSFE:id={$page.uid.noTranslation}] || [globalVar = TSFE:id={$page.uid.search}]
maybe the better solution would be
page.stdWrap.replacement.10 {
search.cObject = COA
search.cObject.10 = HMENU
search.cObject.10 {
....
stdWrap.ifEmpty.cObject = TEXT
stdWrap.ifEmpty.cObject.value = somethingwhatever
stdWrap.wrap = #(|)#i
}
....
}
works for me, at least :)