Feature request: @else and @elseif for Fusion

i like the straight forward syntax of vues v-if, v-else-if and v-else: Directives | Vue.js

currently, to archive such logic, one would write in AFX multiple ‘@if’ keys:

<div>
<h1 @if.has={props.title}>...</h1>
<h2 @if.hasNot={!props.title}>fallback</h2>
</div>

with support for ‘@else’, rewriting the condition wouldn’t be necessary:

<div>
<h1 @if.has={props.title}>...</h1>
<h2 @else.has>fallback</h2>
</div>

the above AFX transpiles to this fusion:

Neos.Fusion:Tag {
    tagName = 'div'
    content = Neos.Fusion:Array {
        item_1 = Neos.Fusion:Tag {
            tagName = 'h1'
            @if.has = ${props.title}
            content = '...'
        }
        item_2 = Neos.Fusion:Tag {
            tagName = 'h2'
            @else.has = true
            content = 'fallback'
        }
    }
}

identifying which else belongs to what if could be done by the sub part ‘has’ and they must be in the following relation to each other

commonParrentPath.someSubpath.@if.commonId
commonParrentPath.someSubpath2.@else.commonId

(alternatively ‘@else’ could work maybe without commonId and would take into account all '@if’s)

Technically, i think one could archive this by caching the result of each ‘@if’ evaluation by path in the runtime - or better caching the result if the ‘@if’ has an ‘@else’ key nearby - or the Fusion AST will list in a first level path ‘__else’ all used ‘@else’ keys with their commonParrentPath and commonId so that on occurrence of an ‘@if’ it could be easily checked if its a standalone if or if there is a matching ‘@else’ key and cache it.

On the occurrence of the ‘@else’ one would look in the cache the correct ‘@if’ by path and then render accordingly.

What do you think?

1 Like

Not a fan because of hte required coupling. Also in programming I like to follow this: Don’t use ‘else {}’. let’s use something ‘else instead | by Georges Jamous | Medium
(more articles about it exist, just randomly picked one)
and I find it makes sense to apply this thought process here too. Above could easily be solved by the fusion Case object, alternatively the tagName could be a ternary EEL expression deciding between H1 and H2. So I don’t even see a sensible use case (well for AFX maybe but that needs to stick to the rules outlined by Fusion)

2 Likes

This is either equivalent to e negation expression via @if which is syntax sugar. @ifNot would be a better name for that but i doubt the advantage justifies the effort.

If the @else would affect a sibling path to the @if statement it would entangle the rendering of those fusion pathes which breaks the partial evaluation fusion usually allows.

2 Likes

I don’t agree with @christianm on the argument in the link, because in fusion there is no execution flow… but I agree, that Case would be better here and makes the fusion code more readable.

Neos.Fusion:Tag {
    tagName = 'div'
    content = Neos.Fusion:Case {
        item_1 {
            condition = ${props.title}
            renderer = Neos.Fusion:Tag {
                tagName = 'h1'
                content = '...'
            }
        }
        item_2 {
            @position = 'end'
            condition = true
            renderer = Neos.Fusion:Tag {
                tagName = 'h2'
                content = 'fallback'
            }
        }
    }
}

With the case element you see at first sight, that this is a decision point (like an if() statement) but when the @if is hidden in the option, it’s not that clear.

but to make usage a bit easier, would this work?

prototype(Neos.Fusion:CaseDefault) < prototype(Neos.Fusion:Matcher) {
    @position = 'end'
    condition = true
}

then you could use

item_2 = Neos.Fusion:CaseDefault {
    renderer = Neos.Fusion:Tag {
        tagName = 'h2'
        content = 'fallback'
    }
}
1 Like

I do like that idea, probably would think hard about the naming and if it should be in the fusion package but the idea to make that pre configured seems sensible yeah!

2 Likes

Yes, probably there is a better name for is, I didn’t think too hard about it yet. :wink: I just thought Neos.Fusion:Default is too generic…

But where would you place it , when not in the fusion package? It’s not Neos CMS specific, it is useful whenever you use Fusion. (I recently started to use Fusion templates for pure Flow packages and I like it.)

Thanks for all your feedback :wink:

When i thought about @else i was only thinking about AFX part and not really how that would affect how Fusion should be written. And i do agree that @else doesnt make sense in plain Fusion as it will be hard to keep track.

Thanks @christianm for the thing about using else - i never really thought about it but it does increase readability if you can remove some of the elses :wink:

But i do think in fusion one need to rely on some kind of else, whether eel ternary or a fusion case, as i cannot controll the flow of the code with an early return… (as you pointed out @till.kleisli )

Speaking of ternary and afx maybe this would be the road to go:
{props.title ? <h1>...</h1> : <h2>fallback</h2>}
AFX in Eel Expressions · Issue #28 · neos/fusion-afx (github.com)

Or AFX could implement in the transpilation phase a rewrite of @else and @elseif to fusions @ifs

Or make the fusion case useable in afx (i tried that once i think but ich habe mir da die zähne ausgebissen)

1 Like

Yeah good ideas, I think we will investigate that. Thanks for the discussion here!

2 Likes

@christianm Great, I was already thinking about whether I should open a Ticket on Github, make a Pull Request, or both of it…

So where is the discussion continuing now? I’d like to join it.

1 Like

@christianm you didn’t reply yet… is the discussion about Neos.Fusion:CaseDefault continued somewhere?

I like the idea but why not define the default case directly like this:

prototype(Neos.Fusion:Case) {
    @class = 'Neos\\Fusion\\FusionObjects\\CaseImplementation'

    default {
       @position = "end"
       condition = true
       renderer = null 
    } 
}

The example would then look like:

content = Neos.Fusion:Case {
    ... 
    default {
        renderer = Neos.Fusion:Tag {
            tagName = 'h2'
            content = 'fallback'
        }
    }
}
1 Like

Thinking a bit further the DefaultMatcher really makes lot of sense sense for afx:

<Neos.Fusion:Case>
    <Neos.Fusion:Matcher condition={fancy expression}>
         foo 
    </Neos.Fusion:Matcher>
    <Neos.Fusion:DefaultMatcher>
         bar
    </Neos.Fusion:DefaultMatcher>
</Neos.Fusion:Case>

And Neos.Fusion:Case would look like:

prototype(Neos.Fusion:Case) {
    @class = 'Neos\\Fusion\\FusionObjects\\CaseImplementation'
    default = Neos.Fusion:DefaultMatcher
} 

Note: The suggested afx syntax would also require a fallback from renderer to content in the Matcher implementation. We do this already for Map and Loop where we have a fallback from itemRenderer to content so this may make sense.

Note after testing: This is quite cumbersome to implement as both Case and Matcher would have to fallback to content.

I just realised that Neos.Neos:ContentCase already has that default case. :slight_smile:

True!