typo3, typoscript, TMENU/HMEN: Special formatting for item after current item - typo3

It there any way to apply special formatting for the menu item immediately after the current item,
that is CUR + 1. Something like:
lib.menu = HMENU
lib.menu {
1 = TMENU {
NO = 1
NO = {
...
}
...
# The currently selected item
CUR = 1
CUR {
allWrap = ...
}
# The next item
CUR + 1 = 1
CUR + 1 {
allWrap = ...
}
}
}
I would appreciate any feedback you might have. Also alternatives: Can write a PHP class/function instead of writing this in typoscript.

Unfortunately this is not possible with just TypoScript. I don't really see a good solution with PHP really either. You could however do it with PHP, but you will need to load the same menu items like in your menu itself inside PHP. Which makes it very inefficient and not flexible at all.
I would say add a special class attribute to the link or wrap and perform your formatting with JavaScript. Where the item coming after the active one is modified.

So I manage to solve this myself. The answers is to use a so-called register to mark if we've passed the current object or not. For example:
lib.menu = HMENU
lib.menu {
1 = TMENU {
NO = 1
NO = {
# Render using Common Object Array (COA)
stdWrap.cObject = COA
stdWrap.cObject {
# Normal Case (However the an item should normally be rendered
10 = TEXT
10 {
if {
isTrue.data = register:cid
value = NORMAL RENDERING
}
}
20 = TEXT
20 {
if {
isTrue.data = register:data
value = RENDERING (IMMEDIATELY) AFTER THE CURRENT ITEM
}
}
# Unset the register (after we've done with our special formatting)
30 = LOAD_REGISTER
30.cid= 0
}
}
...
# The currently selected item
CUR = 1
CUR {
# Render using Common Object Array (COA)
stdWrap.cObject = COA
stdWrap.cObject {
# However the Current item should normally be rendered
10 = TEXT
10.field = title
# Mark that we've reached the current item
20 = LOAD_REGISTER
20.cid= 1
}
}
}
}
A register, set using LOAD_REGISTER, is basically a type of run-time variable, which can be set and reset in the course of iterating through the menu items (or whatever). As such it can be used to note our progress through the menu items, particularly noting if we've passed the current menu item (CUR) or not.
rant begin Hardly an elegant solution considering that typoscript is domain-specific language which is mainly used for this normatting stuff like menus./rant end

Related

Pass a variable to a content object containing a tx_news plugin

This is very similar to my question Counter for mask elements in a TYPO3 column
I need to pass a variable (in my case, the value of cObj:parentRecordNumber which is the counter for the current item in it's column) to the template.
In the main page template:
<f:cObject typoscriptObjectPath="lib.content.pageteasers" />
In typoscript:
lib.content {
pageteasers < styles.content.get
pageteasers {
select {
where = colPos=2
max = 8
}
// this passes the variable elementCounter to the fluid template of the rendered mask content element:
renderObj.mask_teaser {
variables {
elementCounter = TEXT
elementCounter.value = {cObj:parentRecordNumber}
elementCounter.insertData = 1
}
}
// this should pass the same value to a rendered tx_news plugin:
variables {
elementCounter = TEXT
elementCounter.value = {cObj:parentRecordNumber}
elementCounter.insertData = 1
}
// it doesn't. what about these trial & error:
renderObj.list < .variables
renderObj.plugin.tx_news < .variables
renderObj.list.20.news_pi1 < .variables
renderObj.news_pi1 < .variables
// none of these seem to work either
}
}
And then in the rendered CE template (News/List.html)
<f:debug title="" inline="1">{_all}</f:debug>
I don't manage to see the above variable in here. What is the correct way to pass the variable from TS to the news fluid template?
PS Another try was using the Good Old Register
pageteasers < styles.content.get
pageteasers {
select {
where = colPos=2
max = 8
}
append = LOAD_REGISTER
append {
elementCounter = TEXT
elementCounter.value = {cObj:parentRecordNumber}
elementCounter.insertData = 1
}
}
And in the template:
{v:variable.register.get(name: 'elementCounter')}
but that is NULL
Your try via register could work. But you have to care, where you are doing what...
Registers
LOAD_REGISTER is of type string/stdWrap and not a cObject per definition. If you wanna use content based on a cObject, you can declare this via the stdWrap-property (as shown in the examples):
1 = LOAD_REGISTER
1.param.cObject = TEXT
1.param.cObject.stdWrap.data = GP:the_id
So, in your case it should like:
elementCounter.cObject = TEXT
elementCounter.cObject.data = cObj:parentRecordNumber
Appending at the right place
You are trying to use append directly as a property of pageteasers (which is a copy of styles.content.get which is of type CONTENT.
CONTENT does not have an append-property. In addition, you would put the register in front of the entire content, not the individual content elements.
=> It's needed as part of the renderObj, so it's rendered per CE.
The renderObj of CONTENT is of type CASE, which also has no stdWrap-properties directly. But it has a property stdWrap with stdWrap-properties...
Conclusion
So, you can end up with this snippet:
lib.content {
pageteasers {
renderObj {
stdWrap {
append = LOAD_REGISTER
append {
elementCounter.cObject = TEXT
elementCounter.cObject.data = cObj:parentRecordNumber
}
}
}
}
}

How to escape variables in Typoscript setup?

I'm trying to wrap an image with additional HTML elements and parameters, using Typoscript in a Setup Template.
So far, I've managed to wrap elements with HTML, but I'd like to add some parameters as well. The problem occurs, when I try to escape variables in variables - I can't pass UID from one of the parameters.
Below is the sample:
lib.parseFunc_RTE {
tags.img = TEXT
tags.img {
current = 1
preUserFunc = Netresearch\RteCKEditorImage\Controller\ImageRenderingController->renderImageAttributes
dataWrap = |
}
}
The problematic part is here: title="file:{parameters:data-htmlarea-file-uid}:title"
I've tried using {file:{parameters:data-htmlarea-file-uid}:title}, but it still doesn't work and shows error within rendered element:
The only working example is when I hardcode the UID ({file:120:title}), but it should be added dynamically, as there are many images.
How can I escape this "double variables"? Or maybe there is another solution to make it work?
==================================================
Here's the full solution of my problem, thanks to #Jo Hasenau
lib.parseFunc_RTE {
tags.img = COA
tags.img {
10 = TEXT
10 {
dataWrap = file:{parameters:data-htmlarea-file-uid}:title
wrap3 = <a href="{parameters:src}" class="lightbox" title="{|}"
insertData = 1
}
20 = TEXT
20 {
dataWrap = file:{parameters:data-htmlarea-file-uid}:description
wrap3 = data-lightbox-caption="{|}" data-lightbox-width="{parameters:width}" data-lightbox-height="{parameters:height}" rel="lightbox-group">
insertData = 1
}
30 = TEXT
30 {
current = 1
preUserFunc = Netresearch\RteCKEditorImage\Controller\ImageRenderingController->renderImageAttributes
wrap = |</a>
}
}
}
You can use dataWrap and/or insertData multiple times either by using the official order of stdWrap functions or by squeezing in another "level" of stdWrap functions with stdWrap.functionName
In this case it's the order of functions combined with a COA to separate the original wrap into smaller chunks and some additional braces provided by another wrap.
lib.parseFunc_RTE {
tags.img = COA
tags.img {
10 = TEXT
10 {
preUserFunc = Netresearch\RteCKEditorImage\Controller\ImageRenderingController->renderImageAttributes
dataWrap = file:{parameters:data-htmlarea-file-uid}:title
wrap3 = <a href="{parameters:src}" class="lightbox" title="{|}" data-lightbox-caption="{file:120:title}" data-lightbox-width="{parameters:width}" data-lightbox-height="{parameters:height}" rel="lightbox-group">
insertData=1
}
20 = TEXT
20.current = 1
20.wrap = |</a>
}
}
preUserFunc will be executed first, then the first dataWrap will create the actual variable name, followed by wrap3 to get the necessary curly braces, finalized by insertData to fill in all the other variables together with the generated variable.
The order in this code is just for better readability, but the actual order of function calls within stdWrap is predefined in the PHP code and follows exactly the order of function descriptions in the official docs: https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Stdwrap.html

How to get "leveltitle" for HMENU items via TypoScript?

I have a menu (HMENU with special = updated) that gives me the latest sub and sub-sub pages from 3 categories.
The page structure looks like this in the backend:
In addition to the title, I would like to output the name of the respective category (the parent level-1 page).
This is my TypoScript attempt:
lib.myMenu = HMENU
lib.myMenu {
special = updated
special{
value.field = 10,11,12
beginAtLevel = 1
limit = 99
}
1 = TMENU
1{
NO{
doNotLinkIt = 1
stdWrap.cObject = COA
stdWrap.cObject {
10 = TEXT
10{
wrap = <h3>|</h3>
field = seo_title // title
typolink.parameter.field = uid
}
20 = HMENU
20{
wrap = <div class="category attempt-1">|</div>
special = rootline
special.range = 1|1
special.value.field = uid # does not work
1 = TMENU
1.NO.allWrap = |
}
30 = TEXT
30{
wrap = <div class="category attempt-2">|</div>
data = leveltitle : 1 # does not work as expected
}
}
}
}
}
Unfortunately it does not work, because …
special = rootline does not support special.value.
data = leveltitle : 1 uses the ID of the current page, instead of the TMENU item ID.
Does anyone have another approach how I can get the title of the respective category using TypoScript?
Edit: Background information / what this is needed for
With this menu I intend to replace the news module ext:news of an existing project. Instead of news records, pages are now used and this menu creates the list view. Of course a TypoScript page browser will be added.
I would not rebuild the complete menu item generation (NO.doNotLinkIt = 1).
Just use NO.after.cObject = COA.
leveltitle : 1 is correct if you want to have the title for the current page.
The same if you show a rootline menu: it is generated for the current page.
If you want the levelfield for another page you need to build it by yourself.
In typoscript you might use a userfunction. (there is a core function for getting the rootline for a given page id)
If you generate your menu with FLUID you might use a viewhelper. (You might get inspired from this option of viewhelper menu.directory or this option of VH page.breadCrumb in ext:vhs.)
Edit:
you might store the needed information directly in the pagesrecord.
add a new field to the record (or use any unused).
then make sure that each category page contains some page TS_config:
TCADefaults.pages.<yourfield> = CategoryName
With this configuration each new page below will get this value set automatically.
Of course you need to set these values for all existing pages by hand or by some manual queries.
And if the editors are to be prevented from changing this value you need to remove the field from the edit form with this TSConfig on the top-page:
TCEForm.pages.<yourfield>.hide= 1
I have found a pure TypoScript solution that may not be very elegant, but it works:
lib.myMenu = HMENU
lib.myMenu {
special = updated
special{
value.field = 10,11,12
beginAtLevel = 1
limit = 99
}
1 = TMENU
1{
NO{
doNotLinkIt = 1
stdWrap.cObject = COA
stdWrap.cObject {
10 = TEXT
10{
wrap = <h3>|</h3>
field = seo_title // title
typolink.parameter.field = uid
}
20 = HMENU
20{
wrap = <div class="category">|</div>
special = list
special.value.field = pid
1 = TMENU
1{
NO{
doNotLinkIt = 1
stdWrap.override{
# Overwrite it if we are not yet at the category level
if{
# The ID of the page parent to the categories ("Website") is 1618
equals = 1618
value.field = pid
negate = 1
}
cObject = HMENU
cObject{
special = list
special.value.field = pid
1 = TMENU
1.NO.doNotLinkIt = 1
}
}
}
}
}
}
}
}
}
If the menu is to go over further levels, the override must of course be nested even deeper.
Edit – Improvements:
RECORDS instead of HMENU: Bernd Wilke suggested in the comments to use CONTENT instead of HMENU to generate the category title. This caused problems in my tests when sysfolders (as subpages of the categories) were used to structure the items. This could be related to the bug/feature #20933 (Enable working with SysFolders in CONTENT). But RECORDS might be comparable. I tried it and was able to improve the render times a little bit (see table below).
lib.myMenu.1.NO.stdWrap.cObject.20 = RECORDS
lib.myMenu.1.NO.stdWrap.cObject.20{
stdWrap.wrap = <div class="category">|</div>
source.field = pid
tables = pages
conf.pages = TEXT
conf.pages {
field = seo_title // title
stdWrap.override{
# Overwrite it if we are not yet at the category level
if{
# The ID of the page parent to the categories ("Website") is 1618
equals = 1618
value.field = pid
negate = 1
}
cObject = RECORDS
cObject{
source.field = pid
tables = pages
conf.pages = TEXT
conf.pages.field = seo_title // title
}
}
}
}
CONTENT instead of HMENU: if no sysfolders are needed (as of TYPO3 10.4 sysfolders should also work)
lib.myMenu.1.NO.stdWrap.cObject.20 = CONTENT
lib.myMenu.1.NO.stdWrap.cObject.20{
stdWrap.wrap = <div class="category">|</div>
table = pages
select {
uidInList.field = pid
pidInList = 0
selectFields = uid, pid, seo_title, title
}
renderObj = TEXT
renderObj {
field = seo_title // title
stdWrap.override{
# Overwrite it if we are not yet at the category level
if{
# The ID of the page parent to the categories ("Website") is 1618
equals = 1618
value.field = pid
negate = 1
}
cObject = CONTENT
cObject{
table = pages
select {
uidInList.field = pid
pidInList = 0
selectFields = uid, pid, seo_title, title
}
renderObj = TEXT
renderObj.field = seo_title // title
}
}
}
}
userFunc instead of HMENU: Bernd Wilke suggested in his answer to use a userfunction. Even if this is not a pure TypoScript solution, I would have liked to test it to be able to compare the performance. The method getRootLine() has unfortunately been marked as deprecated. Since I'm not an extension developer, it's not clear to me yet how to read the category via a userFunc and if this is actually more effective. If I still come across a solution regarding this, it will be added here.
Rendertime: I have test-implemented the menu in the live site (in the existing template) and compared the website render times with and without the output of the categories to see how much the nested menu affects the performance. The values are average values from 10 measurements. I also limited the output to 20 items, which should be a more realistic value per page later on:
mode
20 items
100 items
without category
99 ms
218 ms
categories via HMENU
133 ms
353 ms
categories via RECORDS
132 ms
331 ms
categories via CONTENT
121 ms
255 ms
categories via userFunc
TBD
TBD
You should neither use a separate HMENU nor a userFunc but a simple LOAD_REGISTER within the menu items of the parent level. This generates register entries, that can be added, changed or restored while looping through the different levels of the HMENU structure.
So you don't need the custom code of a PHP function and you won't get the performance penalty of a nested HMENU.
Basically it's shown in the official documentation, but with a CSS class instead of a title. But of course you could hand over other information from the parent to its children the same way.
Here is the example:
10 = COA
10 {
### left menu table column
10 = LOAD_REGISTER
10 {
ulClass = col-left
}
### right menu table column
20 = LOAD_REGISTER
20 {
ulClass = col-right
}
30 = HMENU
30 {
special = list
special.value = 1
1 = TMENU
# ...
3 = TMENU
3 {
stdWrap {
preCObject = COA
preCObject {
10 = RESTORE_REGISTER
}
dataWrap = <ul class="{register:ulClass}">|</ul>
}
wrap =
SPC = 1
SPC {
allStdWrap {
replacement {
10 {
search = ---
replace =
}
}
dataWrap = </ul>|<ul class="{register:ulClass}">
}
}
}
}
}
And here the link to the full documentation page:
https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/UsingSetting/Register.html

Assign register value to TYPO3 CASE key

Am making some complex menus and would like to use CASE (or similar) to determine the number of submenus in a given branch in order to determine the style of menu to use.
Code:
5 = HMENU
5 {
entryLevel = -1
1 = TMENU
1 {
expAll = 1
NO = 1
NO {
...
}
IFSUB = 1
IFSUB {
10 = CASE
10 {
key.data = {register:count_menuItems}
1 = COA
1 {
data = {field:title}
data.insertData = 1
}
2 = COA
2 {
data = {field:title}
data.insertData = 1
}
default = COA
default {
data = {field:title}
data.insertData = 1
}
}
wrapItemAndSub = |
}
}
2 = TMENU
2 {
maxItems = 2
expAll = 1
...
}
}
How can I get CASE to work? I've tried it with and without the braces.
you should get more information how to access fields, register and other data in typoscript.
if you have a property you mostly can modify the way to get other information than a constant text.
In your example it is the key property where constants are not meaningful.
if you want to access a field of the 'current' record/data you just use key.field = fieldname
if it is other data you modify it to key.data = register:registername
accessing a field can be done with key.data = field:fieldname
If you want these data connected to other information you could use a wrap:
key.data = register:registername
key.wrap = prefix- | -suffix
Notice: the parts of the wrap are trimmed before they are connected
another way would be an inline notation where you even can use multiple values:
key = {register:registername}-with-{field:fieldname}
key.insertData = 1
here you have two replacements. each has to be wrapped in braces {} and you need to tell TYPO3 that there are replacements to do: insertData = 1
special case TEXTobject:
10 = TEXT
10.value = constant Text
20 = TEXT
20.field = fieldname
30 = TEXT
30.data = register:registername
40 = TEXT
40.value = register is '{register:registername}' and field is '{field:fieldname}'
40.insertData = 1
ADDED:
see the manual of the typoscript data type getText where you can find what else can be used instead of register:
then the manual entry for data which is a property of the function .stdWrap and of type getText.
This entry is followed by the property field stating, it is a shortcut for data = field:
(This explaines why your COA with .data results in anything, as doing a .stdWrap.data on any object will replace the object's content.)
be aware that field (either as property or as key of getText) will select
a field of the current record, which might vary dependent on context:
for page rendering it is the record of the current page (table pages),
for rendering a content element it is the element (table tt_content),
inside a filesProcessor it is a file (table sys_file_reference`),
in the renderObj of CONTENT, RECORDS, or split it is the selction you define.
Found the answer. As far as I can tell, CASE works on stdwrap.cObjects and so the code
10 = CASE
10 {
key.data = {register:count_menuItems}
...
}
should be
stdWrap.cObject = CASE
stdWrap.cObject {
key.data = register:count_menuItems
if.isTrue.data = register:count_menuItems
...
}
This way it works.

typoscript backend_layout_next_level not working

I have the following config
page = PAGE
page {
typeNum = 0
10 = FLUIDTEMPLATE
10 {
templateRootPath = EXT:folder/Resources/Private/Website/Templates/
partialRootPath = EXT:folder/Resources/Private/Website/Partials/
layoutRootPath = EXT:folder/Resources/Private/Website/Layout/
file.stdWrap.cObject = CASE
file.stdWrap.cObject {
key.data = levelfield:-1, backend_layout_next_level, slide
key.override.field = backend_layout
default = TEXT
default.value = whatever.html
1 < .default
2 = TEXT
2.value = whatever-else.html
}
}
Somehow the 'backend_layout_next_level' is not working; it is not sliding down the tree. As a result I have to set a backend_layout for each page which is not what one should expect.
Is there a way of knowing/debugging/finding out what's causing this? I thought it might be something related to a curly brace being in the wrong place (too early, too late or just plain wrong) inside my typoscript. Therefor I looked inside the Typoscript Template Analyzer and found some errors which I've fixed, but the problem still persists.
Thanks already!
Best regards
You use have to give the full path to the file as a value in the .file property or you should use .templateName instead and only provide the name (case sensitive!!!) of the template file without suffix.
#see https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Fluidtemplate/Index.html
Note that in your current setting, the uid of the template record must be 1 or 2.
And you can try to use levelfield: -2 instead of levelfield: -1.
TBH I never understood this #^$#% CASE syntax... Therefore definitely prefer common condition which works perfect, you must to add custom condition like I showed in other post, so you can use it like:
page {
typeNum = 0
10 = FLUIDTEMPLATE
10 {
templateRootPath = EXT:folder/Resources/Private/Website/Templates/
partialRootPath = EXT:folder/Resources/Private/Website/Partials/
layoutRootPath = EXT:folder/Resources/Private/Website/Layout/
file = whatever.html
}
}
[userFunc = user_beLayout(2)]
page.10.file = whatever-else.html
[userFunc = user_beLayout(3)]
page.10.file = yet-other.html
[userFunc = user_beLayout(4)]
page.10.file = etc.html
[end]
Edit - to satisfy some comments
Note: When I say that I don't understand CASE syntax, that doesn't mean that I don't know it... especially that I studied it in details not once, here's the conclusion...
About performance:
Custom conditions solution is faster than using CASE and slide combination as it doesn't involve additional DB queries (slide does), it iterates $GLOBALS['TSFE']->page array, which is already collected, and stops further checks ASAP, so it's cheaper than slide which makes additional queries if it finds slide in TS (doesn't matter if it's required or not) - (examine the code for slide mechanics), so actually conditions are performance-saver not killer :)
Yet more
Using conditions blocks you can change behavior of multiple elements at once (instead writing separate CASE for each) i.e.:
[userFunc = user_beLayout(2)]
page.10.file = whatever-else.html
lib.genericMenu >
config.absRefPrefix = /layout-specific-abs/
// etc...
[end]
Using CASE syntax you'll need to repeat all these checks for each element... (where's DRY?)
Finally
Both solutions are ... usable especially when using with cached pages, (difference between them will be about teens or maybe in drastic situation hundred milliseconds -conditions will be faster), for uncached pages most probably it will be good idea to set be_layout directly for each pages record in both cases.
It is quite some time ago, you probably resolved your issue but here is a working approach:
page = PAGE
page {
10 = FLUIDTEMPLATE
10 {
# select different html files for layouts - ref: backend_layout
file.stdWrap.cObject = TEXT
file.stdWrap.cObject {
data = levelfield:-2,backend_layout_next_level,slide
override.field = backend_layout
split {
token = pagets__
1.current = 1
1.wrap = |
}
wrap = EXT:folder/Resources/Private/Templates/|.html
}
layoutRootPath = EXT:folder/Resources/Private/Layouts/
partialRootPath = EXT:folder/Resources/Private/Partials/
}
}
or you can pass it as a variable:
page = PAGE
page {
10 = FLUIDTEMPLATE
10 {
file = EXT:folder/Resources/Private/Templates/Main.html
layoutRootPath = EXT:folder/Resources/Private/Layouts/
partialRootPath = EXT:folder/Resources/Private/Partials/
variables {
# BE_Layout
BE_Layout = COA
BE_Layout {
stdWrap.cObject = TEXT
stdWrap.cObject {
data = levelfield:-2,backend_layout_next_level,slide
override.field = backend_layout
split {
token = pagets__
1.current = 1
1.wrap = |
}
wrap = |.html
}
}
}
}