Skip to content

Instantly share code, notes, and snippets.

@DavidBruchmann
Forked from julrich/lib.navSidebar.ts
Created June 16, 2018 07:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DavidBruchmann/cf27eb309e48e0df326b3bafce2b30e3 to your computer and use it in GitHub Desktop.
Save DavidBruchmann/cf27eb309e48e0df326b3bafce2b30e3 to your computer and use it in GitHub Desktop.
Fully cached TYPO3 HMENU navigation example with expAll and 'active', 'current' states
#
# 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]
@DavidBruchmann
Copy link
Author

Thanks a lot for this insightful explanation, that's very interesting and especially for larger menus quite important!
Concerning any extension creating menus (like b13/menu) I suppose it could be opmizied probably by reducing the db-queries. There are certainly possibilities.
The linked code does concern only rootline menus probably but it's hopefully a clear example: https://stackoverflow.com/a/65613200/1019850

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment