For coders TYPO3 Tech Corner

Drop-down menu without load - using multilevel cache

Drop-down menu without load - using multilevel cache

In the article "Menu: Comparison of techniques" we had compared different ways and variants to build a mega menu. For reasons of space, however, we had only presented the variant of the TypoScript-based menu with caching on several levels in terms of numbers and with a rough concept. Now we will go through the menu step by step and optimise it with caching on levels 1 and 2.


Note:
All snippets have been simplified and shortened for the sake of clarity.
The complete code can be found in the Git repository of the test project (variant 5).

Starting point

The menu from my old article "Optimising the mega menu with cache" served as the basis for the considerations. In simplified form (and still without cache) it looks like this:

10 = COA 10 { 10 = HMENU 10 { special = list special.value = 3 1 = TMENU 2 = TMENU } 20 < .10 20.special.value = 189 30 < .10 30.special.value = 4 }

Even back then, one point stood out negatively: "We have to die a small death: we define the main navigation points by page ID.". In the specific project for which the navigation had to be optimised this time, this disadvantage could not be accepted. Because the main pages could also become more or less by the editors.

HMENU is with cache, TMENU is not

The background to the division into individual HMENU objects was that these have a cache property, while TMENU objects do not. The internal (inefficient) cache was also removed from the TMENU in TYPO3 v12 (#98964).

So we needed a flexible solution to get individual HMENUs.

There was once.... doNotLinkIt

After some thought, I came up with the right idea: an HMENU that also generates its level 1 menu items as an HMENU.

Anyone who has built menus with TypoScript in the past™ may remember doNotLinkIt as a property of the TMENUITEM. This can be used to override the "normal" behaviour of the menu generation and you can build individual things to display the menu items (an example would be a menu item that also displays an image from the page properties).

We use this to insert an entire HMENU via stdWrap.cObject instead of a link to the menu item. And we get the initial page for the subordinate HMENU from the "fields" of the current object, i.e. the TMENUITEM:

10 = COA 10 { 10 = HMENU 10 { 1 = TMENU 1 { NO { doNotLinkIt = 1 stdWrap.cObject = COA stdWrap.cObject { 20 = HMENU 20 { special = list special.value.field = uid 1 = TMENU 1 { //... } 2 = TMENU 2 { //... } } } } } } }

As the cache on the first level was not sufficient in our project, we also needed the HMENUs one level below. After the above considerations, this is then just a bit of hard work:

10 = COA 10 { 10 = HMENU 10 { 1 = TMENU 1 { NO { doNotLinkIt = 1 stdWrap.cObject = COA stdWrap.cObject { 20 = HMENU 20 { special = list special.value.field = uid 1 = TMENU 1 { expAll = 1 } 2 = TMENU 2 { NO { doNotLinkIt = 1 stdWrap.cObject = COA stdWrap.cObject { 20 = HMENU 20 { special = list special.value.field = uid 1 = TMENU 1 { //... } 2 = TMENU 2 { //... } } } } } } } } } } }

Cache: Level 1

The sooner we can cache, the better. It would therefore be best if we didn't have to build a main menu item and its sub-items from scratch, but could get them completely from the cache.

As already explained in the old article, we therefore set a cache key in the HMENU for the menus below the main pages (i.e. the first ones with special = list):

10 { 1.NO.stdWrap.cObject.20.cache { key { cObject = TEXT cObject { value.field = uid wrap = mainnav-lvl1-|_{siteLanguage:languageId} } insertData = 1 } } }

Cache: Level 2

What is new is that we can now do the same for the level below, because there are now also HMENUs there:

10 { 1.NO.stdWrap.cObject.20.2 { NO { stdWrap { cObject { 20 { cache { key { cObject = TEXT cObject { value.field = uid wrap = mainnav-lvl2-|_{siteLanguage:languageId} } insertData = 1 } } } } } } } }

What about the active path?

If you have been paying attention, you will have noticed that we have been caching the submenus purely on the basis of their entry point - without taking into account whether a page in the menu is active.

So let's save the rootline of the current page in a register again and overwrite the cache key if necessary. This is always necessary if the entry point is in the rootline of the page currently being accessed:

10 { 5 = LOAD_REGISTER 5.currentRootlinePids.cObject = HMENU 5.currentRootlinePids.cObject { special = rootline special.range = 0|-1 1 = TMENU 1 { NO { doNotLinkIt = 1 stdWrap.cObject = TEXT stdWrap.cObject.field = uid wrapItemAndSub = |, |*| |, |*| | } } } 10 { // Level 1 1.NO.stdWrap.cObject.20.cache { key.cObject.override { data = page:uid wrap = |_cur if { value.data = register:currentRootlinePids isInList.field = uid } } } // Level 2 1.NO.stdWrap.cObject.20.2 { NO { stdWrap.cObject.20.cache { key.cObject.override { data = page:uid wrap = |_cur if { value.data = register:currentRootlinePids isInList.field = uid } } } } } } }

95% less database transactions

With this TypoScript solution, we could save 95% of the SQL queries - the only better solution from our comparison (which also requires programming expertise) managed to save 99% with a lot of PHP code.

Again the figures in comparison:

| Page | Fluid-based | TS-based, 2 Caches | with b13/menus (pimped) | |------------|---------------|----------------------|-------------------------| | Home | 6094 | 5206 | 7436 | | Impressum | 1259 | 56 | 18 | | Page 1 | 1251 | 116 | 11 | | Page 2 | 1251 | 113 | 11 | | Page 2.1 | 1250 | 116 | 11 |

TypoScript - as powerful as ever

TypoScript has been around for ages. And, yes, not every construct in it is catchy. Some things take experience (and sometimes pain) to understand or use correctly. However, this example shows that TypoScript can still have its raison d'être (and certain advantages) today - even if it may seem a little more unwieldy.


The point that should not be underestimated is that you don't need any programming knowledge. It is almost ingenious that an integrator (who is not a programmer according to the TYPO3 role definition) can optimise the menu so much with TypoScript alone.

After almost 20 years with TypoScript, I am always surprised and amazed at what is possible with a little configuration. That's why I'm definitely part of #teamTypoScript.

I would have loved to see this menu variant at the top of the winners' podium in our menu comparison... :-(

Back

"Code faster, look at the time" - does this sound familiar to you?

How about time and respect for code quality? Working in a team? Automated tests?

Join us