Expand my Community achievements bar.

AEM Nested Multifield Sling Model

Avatar

Level 3

I'm attempting to write a multifield navigation component in AEM following this guide here:

https://blogs.perficient.com/2018/08/24/using-sling-models-with-nested-composite-mulitifields-in-aem...

I have it working fairly well using interfaces, but when I try to implement a class based approach AEM no longer seems to pick up my primaryLinks / secondaryLinks / tertiaryLinks content. From what I can tell in the AEM documentation here: https://sling.apache.org/documentation/bundles/models.html#collections it seems like it should be possible for the Sling Model to pick up the List just by using @inject. Am I missing something here? I've also attached an image of the content I'm attempting to work with along w/ code below.

 

CRX: 

 

Model w/ Interfaces (works) :

@Model(
adaptables = {Resource.class},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public interface Navigation {
@Inject
List<PrimaryLink> getPrimaryLinks();
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
interface PrimaryLink {
@Inject
String getName();
@Inject
List<SecondaryLink> getSecondaryLinks();
}
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
interface SecondaryLink {
@Inject
String getName();
@Inject
List<TertiaryLink> getTertiaryLinks();
}
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
interface TertiaryLink {
@Inject
String getName();
}
}



HTL:

 

<sly data-sly-use.model="com.havertys.core.models.Navigation"/>
<sly data-sly-test.empty="${!model.primaryLinks}" />
<div data-sly-test="${wcmmode.edit && empty}" class="cq-placeholder" data-emptytext="${component.title}"></div>
<sly data-sly-test="${!empty}">
<div>
<ul data-sly-list.primaryLink="${model.primaryLinks}">
<li>${primaryLink.name}
<ul data-sly-list.secondaryLink="${primaryLink.secondaryLinks}">
<li>
<b>Secondary:</b> ${secondaryLink.name} <br/>
<ul data-sly-list.tertiaryLink="${secondaryLink.tertiaryLinks}">
<li>
<b>Tertiary: </b> ${tertiaryLink.name}
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</sly>



Class Based Model (Doesn't Work). I've also tried adding getters but that fails as well.

@Model(
adaptables = {Resource.class},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class Navigation {
@Inject
private List<PrimaryLink> primaryLinks;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class PrimaryLink {
@Inject
private String name;
@Inject
private List<SecondaryLink> secondaryLinks;
}
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class SecondaryLink {
@Inject
private String name;
@Inject
private List<TertiaryLink> tertiaryLinks;
}
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TertiaryLink {
@Inject
private String name;
}
}



Dialog:

<?xml version="1.0" encoding="UTF-8"?>
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2021 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:granite="http://www.adobe.com/jcr/granite/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Navigation"
sling:resourceType="cq/gui/components/authoring/dialog"
extraClientlibs="[core.wcm.components.navigation.v1.editor]"
helpPath="https://www.adobe.com/go/aem_cmp_navigation_v2"
trackingFeature="core-components:navigation:v2">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<tabs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/tabs"
maximized="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<navigationLinksTab
jcr:primaryType="nt:unstructured"
jcr:title="Navigation Links"
sling:resourceType="granite/ui/components/coral/foundation/container"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<primaryLinks
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
composite="{Boolean}true"
fieldLabel="Primary Links">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
name="./primaryLinks">
<items jcr:primaryType="nt:unstructured">
<name
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
emptyText="Name"
fieldLabel="Primary Link Name"
name="./name"/>
<secondaryLinks
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
composite="{Boolean}true"
fieldLabel="Secondary Links">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
name="./secondaryLinks">
<items jcr:primaryType="nt:unstructured">
<name
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
emptyText="Name"
fieldLabel="Secondary Link Name"
name="./name"/>
<tertiaryLinks
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
composite="{Boolean}true"
fieldLabel="Tertiary Links">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
name="./tertiaryLinks">
<items jcr:primaryType="nt:unstructured">
<name
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
emptyText="Name"
fieldLabel="Tertiary Link Name"
name="./name"/>
</items>
</field>
</tertiaryLinks>
</items>
</field>
</secondaryLinks>
</items>
</field>
</primaryLinks>
</items>
</navigationLinksTab>
<promotionTab
jcr:primaryType="nt:unstructured"
jcr:title="Products"
sling:resourceType="granite/ui/components/coral/foundation/container"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<columns
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<accessibilityLabel
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldDescription="Value of an aria-label attribute for the navigation. Should be added if there is more than one navigation on the page."
fieldLabel="Label"
name="./accessibilityLabel"
value=""/>
</items>
</column>
</items>
</columns>
</items>
</promotionTab>
</items>
</tabs>
</items>
</content>
</jcr:root>





4 Replies

Avatar

Community Advisor

Hi @user00928, could you please try below implementation

 

@Model(adaptables = { Resource.class },
  defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class Navigation {

    @Inject
    private List<PrimaryLink> primaryLinks;

    @Model(adaptables = Resource.class,
      defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
    public static class PrimaryLink {
        @Inject
        private String name;

        @Inject
        private List<SecondaryLink> secondaryLinks;
    }

    @Model(adaptables = Resource.class,
      defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
    public static class SecondaryLink {
        @Inject
        private String name;

        @Inject
        private List<TertiaryLink> tertiaryLinks;
    }

    @Model(adaptables = Resource.class,
      defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
    public static class TertiaryLink {
        @Inject
        private String name;
    }
}

 

Avatar

Level 3

Hey lukaszm -- thank you for the response! Unfortunately I'm getting the same result even after making classes static (picture attached of the value my debugger is showing for primaryLinks). 

 

primaryLinksNull.PNG

Avatar

Community Advisor

@user00928, I have run the code on my local instance and it looks to be working fine. It seems that you've set your debugger break-point on class level - which is too early. The new object of the class is not initialized yet. What you could try, is to set your break-point on some method inside your class, or temporary you could add below method and set the break-point inside of it, e.g. on if condition

 

@PostConstruct
public void init() {
  if (primaryLinks != null) {
    System.out.println(primaryLinks.size());
  }
}

 

I hope this will allow you to verify if primaryLinks and other elements are created correctly. Also you can check what is the resource path that is used to create Navigation model.

Avatar

Level 3

Ah you're right! My bad -- your code is actually working I had just set the breakpoint too early. Thanks for the help!