Skip to content

Instantly share code, notes, and snippets.

@julrich
Last active August 4, 2020 12:51
Show Gist options
  • Select an option

  • Save julrich/d91c1ac1968418e6ae8290f2a2e90afb to your computer and use it in GitHub Desktop.

Select an option

Save julrich/d91c1ac1968418e6ae8290f2a2e90afb to your computer and use it in GitHub Desktop.

Revisions

  1. julrich revised this gist Jun 6, 2018. No changes.
  2. julrich created this gist Jun 6, 2018.
    151 changes: 151 additions & 0 deletions lib.navSidebar.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,151 @@
    #
    # 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]