AEM Get linked Content fragments content

Problem Statement:

We have the following content fragment as part of the AEM

  1. Car details
  2. Agent details

And each car can have multiple agents or agents will be selling multiple cars. How can we link between Cars and Agents CF? and how can we get the linked content onto the page?

Requirement:

Link the Car and Agent CF to maintain the relationship between the content fragments and the same can be pulled into the page and exported.

Introduction:

Content Fragments (CF) allow us to manage page-independent content. They help us prepare content for use in multiple locations/over multiple channels. These are text-based editorial content that may include some structured data elements that are considered pure content without design or layout information. Content Fragments are intended to be used and reused across channels.

Usage

  • Highly structured data-entry/form-based content
  • Long-form editorial content (multi-line elements)
  • Content managed outside the life cycle of the channels delivering it

Create the Car and Agents content fragment models as shown below:

Agent Content fragment
Car Content Fragment

Create a custom component called has linkedcontentfragment as shown below:

Based on the element name condition call the LinkedContentFragment Sling model and also pass the elements to be rendered (based on element names, element data will be pulled).

<template data-sly-template.element="${@ element='The content fragment element'}">
    <div class="cmp-contentfragment__element cmp-contentfragment__element--${element.name}" data-cmp-contentfragment-element-type="${element.dataType}">
        <dd class="cmp-contentfragment__element-value">
            <sly data-sly-test="${element.dataType == 'calendar'}" data-sly-use.tpl="core/wcm/components/contentfragment/v1/contentfragment/calendar.html"
                 data-sly-call="${tpl.element @ date = element.value}"></sly>
            <sly data-sly-test="${element.dataType == 'boolean'}">${element.value ? "true" : "false"}</sly>
            <sly data-sly-test="${element.dataType != 'calendar' && element.dataType != 'boolean' && element.name != 'agentDetails'}">${(element.value) @join='<br/>', context='html'}</sly>
            <sly data-sly-test="${element.name == 'agentDetails'}"
                 data-sly-use.linkedFragment="${'com.mysite.core.models.LinkedContentFragment' @elementValue=element, elements='agentTitle,agentDescription,agentAddress'}">
                data-sly-list.agentData="${linkedFragment.agentsList}">
                <dl data-cmp-data-layer="${agentData.data.json}"
                    data-sly-list.element="${agentData.elements}"
                    data-sly-use.elementTemplate="mysite/components/linkedcontentfragment/element.html">
                    <sly data-sly-call="${elementTemplate.element @ element=element}">
                    </sly>
                </dl>
            </sly>
        </dd>
    </div>
</template>

Create Sling model interface LinkedContentFragment as shown below:

package com.mysite.core.models;

import com.adobe.cq.wcm.core.components.models.Component;
import com.adobe.cq.wcm.core.components.models.contentfragment.ContentFragment;
import java.util.List;

/**
 * Defines the {@code Agent CF Model} Sling Model for the {@code /apps/mysite/components/linkedcontentfragment} component.
 */
public interface LinkedContentFragment extends Component {
    /**
     * Returns the Agents List
     *
     * @return the Content Fragment
     */
    default List<ContentFragment> getAgentsList() {
        throw new UnsupportedOperationException();
    }
}

Create model implementation class called LinkedContentFragmentImpl as shown below, get the element data (String array of paths) and elements to be pulled create a synthetic resource and adapt to core component Content fragment model to pull the element details as well as datalayer (tracking purposes)

package com.mysite.core.models.impl;

import com.adobe.cq.wcm.core.components.models.contentfragment.ContentFragment;
import com.adobe.cq.wcm.core.components.models.contentfragment.DAMContentFragment.DAMContentElement;
import com.adobe.cq.wcm.core.components.util.AbstractComponentImpl;
import com.adobe.granite.ui.components.ds.ValueMapResource;
import com.mysite.core.models.LinkedContentFragment;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.RequestAttribute;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.apache.sling.models.factory.ModelFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

@Model(adaptables = SlingHttpServletRequest.class, adapters = {LinkedContentFragment.class}, resourceType = LinkedContentFragmentImpl.RESOURCE_TYPE)
public class LinkedContentFragmentImpl extends AbstractComponentImpl implements LinkedContentFragment {

    public static final String RESOURCE_TYPE = "mysite/components/linkedcontentfragment";
    private static final String CF_DISPLAY_MODE = "displayMode";
    private static final String CF_ELEMENTS = "elementNames";
    private static final String CF_FRAGMENT_PATH = "fragmentPath";

    @RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
    private DAMContentElement elementValue;

    @RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
    private String elements;

    @SlingObject
    private ResourceResolver resourceResolver;

    @OSGiService
    private ModelFactory modelFactory;

    @Override
    public List<ContentFragment> getAgentsList() {
        String[] elementList = elements.split(",");
        String[] agentPaths = elementValue.isMultiValue() ? (String[]) elementValue.getValue() : new String[] { (String) elementValue.getValue() };
        List<ContentFragment> elementsFragmentList = new ArrayList<>();
        Arrays.stream(agentPaths).forEach(agentPath -> {
            elementsFragmentList.add(modelFactory.getModelFromWrappedRequest(request, createLinkedSyntheticResource(agentPath, elementList), ContentFragment.class));
        });
        return elementsFragmentList;
    }

    private ValueMapResource createLinkedSyntheticResource(String path, String ... elementList) {
        ValueMap properties = new ValueMapDecorator(new HashMap<>());
        properties.put(CF_ELEMENTS, elementList);
        properties.put(CF_DISPLAY_MODE, "multi");
        properties.put(CF_FRAGMENT_PATH, path);
        return new ValueMapResource(resourceResolver, resource.getPath(), RESOURCE_TYPE, properties);
    }
}

Once we author the car model, we will be pulling the linked agent details as well as shown below:

Component Authoring
Content Fragment rendering

You can also refer to the below link to download the working code

https://github.com/kiransg89/LinkedContentFragment