Fusion syntax - how can I use variables in nested fusion declarations?

Hi together,

another basic question:

The following is working just fine:

prototype(MYCOMPANY.Site:Content.ElementListe) < prototype(Neos.Neos:ContentComponent) {
    contentlist = Neos.Neos:ContentCollection {
        tagName = 'main'
        nodePath = 'node'
        attributes.class = Neos.Fusion:Case {
            eins {
                condition = ${q(node).children().count() <= 1}
                renderer = 'einspaltig'
            }
            zwei {
                condition = ${q(node).children().count() <= 2}
                renderer = 'zweispaltig'
            }
            // [...]
        }
    }
    renderer = afx`
        <!-- stuff ... -->
        {props.contentlist}
        <!-- other stuff ... ->
    `
}

For reasons of readability I would like to split this up into

    spaltenklasse = Neos.Fusion:Case {
        eins {
            condition = ${q(node).children().count() <= 1}
            renderer = 'einspaltig'
        }
        zwei {
            condition = ${q(node).children().count() <= 2}
            renderer = 'zweispaltig'
        }
        // [...]
    }

and

    contentlist = Neos.Neos:ContentCollection {
        tagName = 'main'
        nodePath = 'node'
        attributes.class = spaltenklasse
    }

The problem is attributes.class = spaltenklasse - I found no notation that worked. I tried several variations like

  • attributes.class = spaltenklasse
  • attributes.class = ${spaltenklasse}
  • attributes.class = ${props.spaltenklasse}
  • attributes.class = ${q(node).property('spaltenklasse')}
  • attributes.class = ${q(node).parent().property('spaltenklasse')}
  • attributes.class = afx'{spaltenklasse}'
  • attributes.class = afx'{props.spaltenklasse}'
  • and so on and so forth…

None of them worked - they all rendered empty. For control purposes to see if the attributes.class is used at all I added a static string like ${'test ' + props.spaltenklasse}. The “test” string was always correctly rendered into the class attribute.

So how can I use variables in nested fusion declarations?

You have been pretty creative with your experiments ^^ But as you already found out none of those can work.

You need to add spaltenklasse to the context (eg props) somehow so it is available for the children/nested fusion objects.

There are many (hacky) ways to do that but i will show you only the better ones ^^

generally you might want to have a deeper look into separation between integration and presentation, but thats for another time :wink:

Nested component pattern

prototype(MYCOMPANY.Site:Content.ElementListe) < prototype(Neos.Neos:ContentComponent) {

  spaltenklasse = Neos.Fusion:Case {
      eins {
          condition = ${q(node).children().count() <= 1}
          renderer = 'einspaltig'
      }
      zwei {
          condition = ${q(node).children().count() <= 2}
          renderer = 'zweispaltig'
      }
  }

  renderer = Neos.Fusion:Component {
    // next line is optional in this case but this is part of the Nested Component pattern, to use `props.spaltenklasse` also in the afx renderer below ...
    @apply.props = ${props}

    contentlist = Neos.Neos:ContentCollection {
        tagName = 'main'
        nodePath = 'node'
        attributes.class = ${props.spaltenklasse}
    }

  renderer = afx`
      {props.contentlist}
  `
  }
}

Inline into renderer

prototype(MYCOMPANY.Site:Content.ElementListe) < prototype(Neos.Neos:ContentComponent) {

  spaltenklasse = Neos.Fusion:Case {
      eins {
          condition = ${q(node).children().count() <= 1}
          renderer = 'einspaltig'
      }
      zwei {
          condition = ${q(node).children().count() <= 2}
          renderer = 'zweispaltig'
      }
  }

  renderer = afx`
      <Neos.Neos:ContentCollection tagName='main' nodePath='node' attributes.class={props.spaltenklasse}/>
  `
}

This process pattern (not so nice)

prototype(MYCOMPANY.Site:Content.ElementListe) < prototype(Neos.Neos:ContentComponent) {

  spaltenklasse = Neos.Fusion:Case {
      eins {
          condition = ${q(node).children().count() <= 1}
          renderer = 'einspaltig'
      }
      zwei {
          condition = ${q(node).children().count() <= 2}
          renderer = 'zweispaltig'
      }
  }

  contentlist = ${this.spaltenklasse}
  contentlist.@process.useSpaltenklasse = Neos.Neos:ContentCollection {
      tagName = 'main'
      nodePath = 'node'
      attributes.class = ${value}
  }

  renderer = afx`
      {props.contentlist}
  `
}

have fun ^^

you can play around here btw: FusionPen: Build, Test and Discover Fusion Code for Neos CMS (punkt.de)

The nested component pattern with the @apply didn’t work either (even after swapping “renderer” and “spaltenklasse” :wink: )

It worked as soon as I pulled the spaltenklasse definition into the sub-component. It worked like so…

prototype(MYCOMPANY.Site:Content.ElementListe) < prototype(Neos.Neos:ContentComponent) {
    contentlist = Neos.Fusion:Component {
        spaltenklasse = Neos.Fusion:Case {
            eins {
                condition = ${q(node).children().count() <= 1}
                renderer = 'einspaltig'
            }
            // [ ... ]
            mehrAlsVier {
                condition = ${true}
                renderer = 'vierspaltig mitScroller'
            }
        }

        renderer = Neos.Neos:ContentCollection {
            tagName = 'main'
            nodePath = 'node'
            attributes.class = ${'test ' + props.spaltenklasse}
        }
    }
    renderer = afx`
            <!-- ...stuff... -->
            {props.contentlist}
            <!-- ...more stuff... -->
    `
}

But your suggestion Inline into renderer worked and is my favorite - it’s the most compact and has the best readability. I am happy now :grinning:

To accomplish a better separation I would rather prefer to outsource the afx template itself from the fusion file into a separate file than to implement two fusion files where one only holds the afx (which is suggested in Atomic.Fusion - Rendering - Manual - Neos CMS - Neos Docs). But I didn’t find an option to load an afx template file (as is available for fluid templates via Neos.Fusion::Template) :face_with_raised_eyebrow:

you can use this one :wink:

mhsdesign/MhsDesign.Neos.AfxFiles: Provides option to use AFX in separate template files. (github.com)

perfect, works like a charm :slight_smile:
I saved the templates as *.jsx files since the react jsx syntax seems to come closest to afx and my IDE supports jsx syntax highlighting.

Hi,
one way I did this in the past was with @context. Reason for doing it this way was mainly, that it is the first way I found out how to expose variables and because of readability:

prototype(MYCOMPANY.Site:Content.ElementListe) < prototype(Neos.Neos:ContentComponent) {

  @context.spaltenklasse = Neos.Fusion:Case {
      eins {
          condition = ${q(node).children().count() <= 1}
          renderer = 'einspaltig'
      }
      zwei {
          condition = ${q(node).children().count() <= 2}
          renderer = 'zweispaltig'
      }
  }

  renderer = Neos.Fusion:Component {

    contentlist = Neos.Neos:ContentCollection {
        tagName = 'main'
        nodePath = 'node'
        attributes.class = ${spaltenklasse}
    }

  renderer = afx`
      {props.contentlist}
  `
  }
}

Do you have an opinion on that, why would you recommend (not) doing that?

When you use @context, you’ll lose very fast the overview. Especially if you open the files after some time.
It is much cleaner if you use the props only where it is used.
@context is inherited and can cause you e.g. problems with caching. Also, nested elements can get problems if you set e.g. node. I try to avoid it. But I use it for elements where I don’t have direct access to it:

E.G. Jonnitto.ImagesInARow/Container.fusion at master · jonnitto/Jonnitto.ImagesInARow · GitHub

1 Like