Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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]
@julrich

This comment has been minimized.

Copy link
Owner Author

commented Jun 6, 2018

This is based on: https://gist.github.com/pgampe/cb29bc0fc1111d1370cc
It's generalised to work for 'current' and 'active' classes, and menus of variable depth (max depth 5).

Also handles cases like the special pages (index page, search page, pages not inside the root node of the menu, where the replacement RegExp otherwise would be an empty string, replacing everything), logged in users and languages.

@t3easy

This comment has been minimized.

Copy link

commented May 28, 2019

I have another solution for the cache problem of logged in feusers:

lib.menu.cache {
  key.cObject = COA
  key.cObject.10 = TEXT
  key.cObject.10.value = lib_menu_site{$page.uid.root}_lang{TSFE:sys_language_uid}
  key.cObject.10.insertData = 1
  key.cObject.20 = TEXT
  key.cObject.20.data = TSFE:fe_user|user|usergroup
  key.cObject.20.wrap = _groups|
  key.cObject.20.insertData = 1
  key.cObject.20.replacement.10.search = ,
  key.cObject.20.replacement.10.replace = -
  key.cObject.20.if.isTrue.data = TSFE:fe_user|user|usergroup
  lifetime = default
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.