Neos.Fusion:Case in afx or should we have a Neos.Fusion:If?

As discussed in Feature request: @else and @elseif for Fusion - #9 by till.kleisli the @if attributes might not be the best way to implement conditional Output.

Now I tried to implement conditional output in afx

<div @if.matches={condition1}>
    One
</div>

<div @if.matches={condition2}>
    Two
</div>

this works fine, but the @if statements are quite hidden when you add more attributes like class or id. so I tried

<Neos.Fusion:Case>
    <Neos.Fusion:Matcher condition={condition1}>
        <div>One</div>
    </Neos.Fusion:Matcher>
    <Neos.Fusion:Matcher condition={condition2}>
        <div>Two</div>
    </Neos.Fusion:Matcher>
</Neos.Fusion:Case>

but when contition1 matches and condition2 not, the output is

<div>One</div>_____________NO_MATCH_RESULT_____________

For this example, with no default value, it even might be cool to have an IF object like the <f:if> Viewhelper in fluid. on the other hand, there might be a reason, why it was not implemented yet. :slight_smile:

<Neos.Fusion:If condition={condition1}>
    <div>One</div>
</Neos.Fusion:If>

Reason is that Matcher expects the fusion attribute renderer but afx places children in content.

You can do this:
!!! Does not work, see below !!!

<Neos.Fusion:Case>
    <Neos.Fusion:Matcher condition={condition1} @children="renderer">
        <div>One</div>
    </Neos.Fusion:Matcher>
    <Neos.Fusion:Matcher condition={condition2} @children="renderer">
        <div>Two</div>
    </Neos.Fusion:Matcher>
</Neos.Fusion:Case>

Also it might make sense to add a fallback to content if renderer cannot be rendered like for Map and Loop.

Tested further and this approach does not work:

<Neos.Fusion:Case>
    <Neos.Fusion:Matcher condition={condition1}>
        <div>One</div>
    </Neos.Fusion:Matcher>
    <Neos.Fusion:Matcher condition={condition2}>
        <div>Two</div>
    </Neos.Fusion:Matcher>
</Neos.Fusion:Case>

because it would transpile as:

item_1 = Neos.Fusion:Case {
   content = Neos.Fusion:Join {
       item_1 = Neos.Fusion:Matcher {
           condition = ${condition1}
           content = Neos.Fusion:Tag {
               ...
           }
        }
       item_2 = Neos.Fusion:Matcher {
           condition = ${condition2}
           content = Neos.Fusion:Tag {
               ...
           }
        }
   }

Problem is that the path content is added on two levels. The second one inside the matcher can be circumvented via @children=renderer but there is no such sytax that adds afx children as properties to the parent object directly which would be required for the Neos.Fusion:Case so unless we come up with a smart solution for that afx will not work with Case (yet)

1 Like

Because fusion ignores whitespaces between attributes, i usally write @ifs like that:

<div
    @if.matches={condition}
    class="my-class"
    id="id"
    data-sth="abc"
    to-be-continued
>
    Content
</div>

So the @if in the beginning is seen from the first view.

2 Likes

@mficzel Thank you for diving into it. I’ll try to think about a smart solution… :slight_smile:
Is there an easy way to display the fusion code that is generated by afx? Or did you just do it by hand?

@benjamink yes, it’s definitely easier to read, when the @if is in a seperate line. But I still think there should be a dedicated fusion object for it.

you can easily build your own: Vendor.Site:If

either by using pure Fusion and compose some features or by having an own php implementation, which will do something like this:

public function evaluate()
{

    if ($this->fusionValue('condition')) {
        return $this->fusionValue('content')
    }

}

pure fusion:

prototype(Vendor.Site:If) < prototype(Neos.Fusion:Value) {
   condition = ${true}
   @if.evaluate = ${this.condition}
   content = ''
   value = ${this.content}
}

im not so sure if this should be a native feature, as the @if thing is specifically made for this reason… and then there would be two ways to handle one thing. otherwise there is no @for meta attribute but one needs to use Neos.Fusion:Loop - seems a bit inconsistend…

technically this is working, really ugly and not recommendet:

and i dont know if we should add a @directChildItems='true' for afx customization to avoid the Join. (Since we have already @path and @children and those are somewhat not so cool ^^)

prototype(YourVendor:FusionPrototype) < prototype(Neos.Fusion:Renderer) {
    
    renderer = afx`
        <Neos.Fusion:Case><Neos.Fusion:Matcher
            @path="1"
            @children="renderer"
            condition={true}
        >
            working
        </Neos.Fusion:Matcher><Neos.Fusion:Matcher
            @path="2"
            @children="renderer"
            condition={false}
        >
            foo
        </Neos.Fusion:Matcher></Neos.Fusion:Case>
    `
}

Edit actually an even more ugly way was discovered see slack :P

I found a pattern based on the Neos.Fusion:Match prototype that is very similar to the request above. The only limitation is that the discriminator has to be a valid fusion path.

prototype(YourVendor:FusionPrototype) < prototype(Neos.Fusion:Component) {
    
    discriminator = "foo"
    
    renderer = afx`
        <Neos.Fusion:Match @subject={props.discriminator} >
            <Neos.Fusion:Fragment @path="foo">
                case foo
            </Neos.Fusion:Fragment>
            <Neos.Fusion:Fragment @path="bar">
                case bar
            </Neos.Fusion:Fragment>
            <Neos.Fusion:Fragment @path="@default">
                default
            </Neos.Fusion:Fragment>
        </Neos.Fusion:Match>
    `
}
2 Likes