[SOLVED] Can't edit child node elements

Hallo!

I’ve created two node types (ShowcaseContainer and Showcase). The ShowcaseContainer has a child node where only Showcases can be stored (ContentCollection). So far everything works fine. The child nodes are rendered in the frontend and in the backend I can edit the ShowcaseContainer. But I can’t edit the Showcases (see Screenshot). I can click on them, but the properties won’t be displayed on the right panel. When I move a showcase outside (on the root level) I can edit it. I also repaired my nodes. Does anybody know how to fix this?

This is my node type configuration:

'Foo:ShowcaseContainer':
  superTypes:
    'Neos.Neos:Content': TRUE
  ui:
    label: 'Foo.Website:NodeTypes.Showcase:label.showcaseContainer'
    group: 'foo'
    icon: 'icon-cubes'
    inspector:
      groups:
        main:
          label: 'Foo.Website:Main:label.group.main'
          icon: 'icon-cubes'
          position: 1
  childNodes:
    showcases:
      type: 'Neos.Neos:ContentCollection'
      constraints:
        nodeTypes:
          '*': FALSE
          'Foo:Showcase': TRUE
  properties:
    color:
      type: string
      defaultValue: '#000000'
      validation:
        'Neos.Neos/Validation/RegularExpressionValidator':
          regularExpression: '/(^#([a-fA-F0-9]{3}){1,2}$)|(^rgba?\(\d{1,3}%?,\d{1,3}%?,\d{1,3}%?(,\d{1,3}%?)?\)$)/i'
      ui:
        label: 'Foo.Website:NodeTypes.Showcase:label.color'
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'main'



'Foo:Showcase':
  superTypes:
    'Neos.Neos:Content': TRUE
  ui:
    label: 'Foo.Website:NodeTypes.Showcase:label.showcase'
    group: 'foo'
    icon: 'icon-cube'
    inspector:
      groups:
        main:
          label: 'Foo.Website:Main:label.group.main'
          icon: 'icon-cube'
          position: 1
  properties:
    image:
      type: Neos\Media\Domain\Model\ImageInterface
      ui:
        label: 'Foo.Website:NodeTypes.Showcase:label.image'
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'main'
    text:
      type: string
      defaultValue: ''
      ui:
        label: 'Foo.Website:NodeTypes.Showcase:label.text'
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'main'
    link:
      type: 'reference'
      ui:
        label: 'Foo.Website:NodeTypes.Showcase:label.link'
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'main'
          editorOptions:
            nodeTypes: ['Neos.Neos:Shortcut', 'Neos.Neos:Document']

This is my fusion configuration:

prototype(Foo:ShowcaseContainer) < prototype(Neos.Neos:Content) {
	templatePath = 'resource://Foo.Website/Private/Templates/FusionObjects/ShowcaseContainer.html'

	showcases = Neos.Fusion:Collection {
		collection = ${q(node).children().children()}
		itemName = 'item'
		itemRenderer = Neos.Fusion:Template {
			templatePath = 'resource://Foo.Website/Private/Templates/FusionObjects/Showcase.html'
			item = ${item}
		}
	}

	color = ${q(node).property('color')}
}

prototype(Foo:Showcase) < prototype(Neos.Neos:Content) {
	templatePath = 'resource://Foo.Website/Private/Templates/FusionObjects/Showcase.html'

	image = ${q(node).property('image')}
	text = ${q(node).property('text')}
	link = ${q(node).property('link')}
}

Is there anybody who can help me with this problem? :confused:
Or is my description not understandable?

Hey @lmis,

The problem is in your collection. To access the properties of a Node you need some wrappings around it so Neos knows where to load and store the data. For all prototypes that inherit from Neos.Neos:Content this should normally be done automatically.
The Wrapping is achieved by Neos.Neos:ContentElementWrapping which uses the context-variable node to be created (see file “Neos.Neos/Resources/Fusion/Prototypes/Content.fusion” > @process.contentElementWrapping and “Neos.Neos/Classes/Fusion/ContentElementWrappingImplementation.php” ~line 58).

In your code the context variable for the node is named item (instead of node), so neither the ContentElementWrapping nor the Element itself can access the correct properties.

In short:
To fix this change itemName = ‘item’ to itemName = ‘node’ inside your Neos.Fusion:Collection (and item = ${item} to item = ${node} inside the nested Neos.Fusion:Template if you need it).

Hope you got this.

Best regards,
Benjamin

Hi @BenjaminK,

I don’t use that anymore. Nevertheless: thank You for Your answer :slight_smile: I will give it a try if I use this in the future.

Good morning @BenjaminK,

now I’m using this case again, but unfortunately it doesn’t work, even with Your changes (I’ve changed ‘item’ to ‘node’). I’ve created a Team node type and an Employee node type. But I can’t access the employees within the team node type. The properties won’t be displayed. It would be nice if You (or someone else) can help me with this problem. Here is my setup…

NodeTypes.Team.yaml

'Foo.Website:Team':
  superTypes:
    'Neos.Neos:Content': TRUE
  ui:
    label: i18n
    group: 'collectiveContent'
    icon: 'icon-users'
    position: 400
  childNodes:
    employees:
      type: 'Neos.Neos:ContentCollection'
      constraints:
        nodeTypes:
          '*': FALSE
          'Foo.Website:Employee': TRUE
  properties:
    headlineTag: []
    subheadlineTag: []
    sortProperty:
      type: string
      defaultValue: 'date'
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'options'
          position: 2
          editor: Content/Inspector/Editors/SelectBoxEditor
          editorOptions:
            placeholder: 'Foo.Website:NodeTypes.Team:properties.sortProperty.editor.placeholder'
            values:
              firstname:
                label: 'Foo.Website:NodeTypes.Team:properties.sortProperty.editor.values.firstname'
                position: 1
              lastname:
                label: 'Foo.Website:NodeTypes.Team:properties.sortProperty.editor.values.lastname'
                position: 2
              _index:
                label: 'Foo.Website:NodeTypes.Team:properties.sortProperty.editor.values.sortingindex'
                position: 3
    sortOrder:
      type: string
      defaultValue: 'desc'
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'options'
          position: 3
          editor: Content/Inspector/Editors/SelectBoxEditor
          editorOptions:
            placeholder: 'Foo.Website:NodeTypes.Team:properties.sortOrder.editor.placeholder'
            values:
              asc:
                label: 'Foo.Website:NodeTypes.Team:properties.sortOrder.editor.values.asc'
                position: 1
              desc:
                label: 'Foo.Website:NodeTypes.Team:properties.sortOrder.editor.values.desc'
                position: 2

NodeTypes.Employee.yaml

'Foo.Website:Employee':
  superTypes:
    'Neos.Neos:Content': TRUE
  ui:
    label: i18n
    group: 'collectiveContent'
    icon: 'icon-user'
    position: 500
  properties:
    fullWidth: []
    headlineTag: []
    subheadlineTag: []
    spacingTop: []
    spacingBottom: []
    tags:
      type: 'references'
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'editing'
          editorOptions:
            nodeTypes: ['Foo.Website:Tag']
    image:
      type: Neos\Media\Domain\Model\ImageInterface
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'image'
    title:
      type: string
      defaultValue: ''
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'text'
      validation:
        'Neos.Neos/Validation/NotEmptyValidator': []
    firstname:
      type: string
      defaultValue: ''
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'text'
      validation:
        'Neos.Neos/Validation/NotEmptyValidator': []
      search:
        fulltextExtractor: ${Indexing.extractInto('h1', value)}
    lastname:
      type: string
      defaultValue: ''
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'text'
      validation:
        'Neos.Neos/Validation/NotEmptyValidator': []
      search:
        fulltextExtractor: ${Indexing.extractInto('h1', value)}
    position:
      type: string
      defaultValue: ''
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'text'
      validation:
        'Neos.Neos/Validation/NotEmptyValidator': []
      search:
        fulltextExtractor: ${Indexing.extractInto('h2', value)}
    email:
      type: string
      defaultValue: ''
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'text'
      search:
        fulltextExtractor: ${Indexing.extractInto('h3', value)}
    telephone:
      type: string
      defaultValue: ''
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'text'
      search:
        fulltextExtractor: ${Indexing.extractInto('h4', value)}
    alignment:
      type: string
      defaultValue: 'date'
      ui:
        label: i18n
        inlineEditable: FALSE
        reloadIfChanged: TRUE
        inspector:
          group: 'design'
          editor: Content/Inspector/Editors/SelectBoxEditor
          editorOptions:
            placeholder: 'Foo.Website:NodeTypes.Employee:properties.alignment.editor.placeholder'
            values:
              left:
                label: 'Foo.Website:NodeTypes.Employee:properties.alignment.editor.values.left'
                position: 1
              right:
                label: 'Foo.Website:NodeTypes.Employee:properties.alignment.editor.values.right'
                position: 2
              random:
                label: 'Foo.Website:NodeTypes.Employee:properties.alignment.editor.values.random'
                position: 3

Team.fusion

prototype(Foo.Website:Team) < prototype(Neos.Neos:Content) {
	templatePath = 'resource://Foo.Website/Private/Templates/NodeTypes/Team.html'

	# Get Team container properties
	sortProperty = ${q(node).property('sortProperty')}
	sortOrder = ${q(node).property('sortOrder')}

	# Get all employees
    employees = Neos.Fusion:Collection {
        collection = ${q(node).children().children()}
        itemName = 'node'
        itemRenderer = Neos.Fusion:Template {
            templatePath = 'resource://Foo.Website/Private/Templates/NodeTypes/Partials/Employee.html'
            node = ${node}
        }
    }

    # Setup caching
    @cache {
        mode = 'cached'
        entryIdentifier {
            node = ${node}
        }
        entryTags {
            1 = 'NodeType_Foo.Website:Employee'
            2 = ${'Node_' + node.identifier}
        }
    }
}

Employee.fusion

prototype(Foo.Website:Employee) < prototype(Neos.Neos:Content) {
	templatePath = 'resource://Foo.Website/Private/Templates/NodeTypes/Partials/Employee.html'

	# Get Employee properties
	tags = ${q(node).property('tags')}
	image = ${q(node).property('image')}
	title = ${q(node).property('title')}
	firstname = ${q(node).property('firstname')}
	lastname = ${q(node).property('lastname')}
	position = ${q(node).property('position')}
	email = ${q(node).property('email')}
	telephone = ${q(node).property('telephone')}
	alignment = ${q(node).property('alignment')}
}

Team.html

{namespace neos=Neos\Neos\ViewHelpers}
{namespace media=Neos\Media\ViewHelpers}
{namespace laudert=Foo\Website\ViewHelpers}

<f:if condition="{employees}">
	<f:then>
		<div class="container {class}" data-spacing="{spacingTop}-{spacingBottom}">
		<div class="team">
			<ul class="team-list" data-filter-content="withSquares">
				{employees -> f:format.raw()}
			</ul>
		</div>
		</div>
	</f:then>
	<f:else>
		<f:if condition="{neos:rendering.inEditMode()}">
			<div class="neos-backend-information">
				{f:translate(id: 'frontend.label.team.noItems', package: 'Foo.Website', source: 'Main')}
			</div>
		</f:if>
	</f:else>
</f:if>

Employee.html

{namespace neos=Neos\Neos\ViewHelpers}
{namespace media=Neos\Media\ViewHelpers}
{namespace laudert=Foo\Website\ViewHelpers}

<li class="team-item" data-filter-string="{node.properties.firstname} {node.properties.lastname}"
    data-tags="{f:if(condition:i.isLast, then:'{tag.properties.value}', else:'{tag.properties.value},') -> f:for(each:node.properties.tags, as: 'tag', iteration: 'i')}">
    <div class="employee employee-{laudert:employeeAlignment(sAlignment:node.properties.alignment)}">
        <div class="employee-image">
            <f:if condition="{node.properties.image}">
                <f:if condition="{neos:rendering.inEditMode()}">
                    <f:then>
                        <img src="{f:uri.resource(resource: node.properties.image.resource)}"
                             alt="{node.properties.image.caption}"
                             title="{node.properties.image.title}">
                    </f:then>
                    <f:else>
                        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
                             data-src="{f:uri.resource(resource: node.properties.image.resource)}"
                             alt="{node.properties.image.caption}"
                             title="{node.properties.image.title}">
                    </f:else>
                </f:if>
            </f:if>
        </div>
        <div class="employee-name">
            <span class="employee-name-first">{node.properties.firstname -> f:format.raw()}</span><br>
            <span class="employee-name-last">{node.properties.lastname -> f:format.raw()}</span>
        </div>
        <div class="employee-info">
            <div class="employee-info-content">
                <strong class="employee-info-position">{node.properties.position -> f:format.raw()}</strong>
                <span class="employee-info-mail">{node.properties.email -> f:format.raw()}</span>
                <span class="employee-info-tel">{node.properties.telephone}</span>
            </div>
        </div>
    </div>
</li>

Backend

If you only want to output the employees here, you should either change the itemRenderer in Team.fusion to Foo.Website:Employee or add all the properties inside the renderer again (as done inside Employee.fusion.

But i think you want to be able to change the values of the editors in the backend. Therefore you should use this inside your Team.fusion as it automatically wraps the required properties to the html output in the backend:

employees = Neos.Neos:ContentCollection {
    nodePath = 'employees'

    tagName = 'ul'
    attributes {
        class = 'team-list'
        data-filter-content = 'withSquares'
    }
}

But just one more note: Sometimes when using a list (ul here) for ContentCollections, some strange things happened on some of my sites in the backend like the height of the container increases till infinity. If this is the case for you, you should maybe change the tags when in backend or for both, backend and frontend.

And i also think that you need to switch your functions in Employee.html for the attribute data-tags, so that f:for comes first, but i could be wrong here.

Hope you get it working.

Your welcome :wink:

Hey Benjamin,

now it works, thank you! :slight_smile:
I’m experiencing that sometimes the content (employees) are missing (in the backend). But after a reload they are back again.

I have one more question. Is it possible to apply the sorting of my Team node type to the employee result?

Something like this:

employees = ${q(site).find('#' + this.node.identifier).find('[instanceof Laudert.Website:Employee]').sort(this.sortProperty, this.sortOrder).get()}

This should work as you wrote it, but sortProperty and sortOrder need to be on the same nesting level then. Otherwise you need to use @context.sortProperty. Also i would suggest to directly start with the node instead of starting with the side and search for the node first:

@context {
    sortProperty = ${q(node).property('sortProperty')}
    sortOrder = ${q(node).property('sortOrder')}
}

employees = Neos.Fusion:Collection {
    collection = ${q(node).find('[instanceof Laudert.Website:Employee]').sort(sortProperty, sortOrder)}
    ...
}